ci(security): add secret scan and SBOM
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SPEC = importlib.util.spec_from_file_location("supply_chain", ROOT / "scripts" / "supply-chain.py")
|
||||
assert SPEC and SPEC.loader
|
||||
SUPPLY_CHAIN = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(SUPPLY_CHAIN)
|
||||
|
||||
|
||||
class SecretScanTests(unittest.TestCase):
|
||||
def test_detects_canaries_without_returning_secret_values(self):
|
||||
canaries = [
|
||||
"-----BEGIN " + "PRIVATE KEY-----",
|
||||
"eyJ" + "a" * 12 + "." + "b" * 12 + "." + "c" * 12,
|
||||
"AKIA" + "A" * 16,
|
||||
"ghp_" + "a" * 36,
|
||||
"AIza" + "a" * 35,
|
||||
"xoxb-" + "a" * 24,
|
||||
"sk_" + "live_" + "a" * 20,
|
||||
]
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
(root / "fixture.txt").write_text("\n".join(canaries), encoding="utf-8")
|
||||
findings = SUPPLY_CHAIN.scan_files(root, ["fixture.txt"])
|
||||
|
||||
self.assertEqual(len(SUPPLY_CHAIN.SECRET_PATTERNS), len(findings))
|
||||
rendered = "\n".join(f"{kind} {path}:{line}" for kind, path, line in findings)
|
||||
for canary in canaries:
|
||||
self.assertNotIn(canary, rendered)
|
||||
|
||||
def test_ignores_binary_and_oversized_files(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
binary_canary = b"\0-----BEGIN " + b"PRIVATE KEY-----"
|
||||
(root / "binary.dat").write_bytes(binary_canary)
|
||||
(root / "large.txt").write_bytes(b"a" * (SUPPLY_CHAIN.MAX_SCANNED_FILE_BYTES + 1))
|
||||
self.assertEqual([], SUPPLY_CHAIN.scan_files(root, ["binary.dat", "large.txt"]))
|
||||
|
||||
|
||||
class SbomTests(unittest.TestCase):
|
||||
def test_generates_deterministic_multi_ecosystem_cyclonedx(self):
|
||||
first = SUPPLY_CHAIN.generate_sbom(ROOT)
|
||||
second = SUPPLY_CHAIN.generate_sbom(ROOT)
|
||||
self.assertEqual(first, second)
|
||||
self.assertEqual("CycloneDX", first["bomFormat"])
|
||||
self.assertEqual("1.5", first["specVersion"])
|
||||
|
||||
components = first["components"]
|
||||
purls = {component["purl"] for component in components}
|
||||
self.assertTrue(any(purl.startswith("pkg:npm/react@") for purl in purls))
|
||||
self.assertTrue(any(purl.startswith("pkg:nuget/Microsoft.EntityFrameworkCore@") for purl in purls))
|
||||
self.assertTrue(any(purl.startswith("pkg:pypi/fastapi@") for purl in purls))
|
||||
self.assertEqual(len(purls), len(components))
|
||||
json.dumps(first)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user