import { mkdir, readFile, readdir, writeFile } from "node:fs/promises"; import path from "node:path"; const scanRoots = ["src", "dist"]; const findings = /** @type {Array<{ruleId: string, file: string}>} */ ([]); const patterns = [ { id: "private-key", expression: /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/g }, { id: "aws-access-key", expression: /\bAKIA[0-9A-Z]{16}\b/g }, { id: "github-token", expression: /\bgh[pousr]_[A-Za-z0-9_]{30,}\b/g }, { id: "assigned-secret", expression: /\b(?:client_secret|password|private_key)\s*[:=]\s*["'][^"'${}]{12,}["']/gi, }, ]; /** @param {string} directory @returns {Promise} */ async function filesWithin(directory) { const entries = await readdir(directory, { withFileTypes: true }); const nested = /** @type {string[][]} */ (await Promise.all( entries.map((entry) => { const target = path.join(directory, entry.name); return entry.isDirectory() ? filesWithin(target) : [target]; }), )); return nested.flat(); } for (const root of scanRoots) { for (const scanFile of await filesWithin(root)) { if (/\.(png|jpg|jpeg|gif|woff2?|zip)$/i.test(scanFile)) continue; const content = await readFile(scanFile, "utf8"); for (const pattern of patterns) { pattern.expression.lastIndex = 0; if (pattern.expression.test(content)) { findings.push({ ruleId: pattern.id, file: scanFile }); } } } } const sarif = { version: "2.1.0", $schema: "https://json.schemastore.org/sarif-2.1.0.json", runs: [ { tool: { driver: { name: "ca-frontend-secret-scan", rules: patterns.map((pattern) => ({ id: pattern.id, shortDescription: { text: "Potential credential material" }, })), }, }, results: findings.map((finding) => ({ ruleId: finding.ruleId, message: { text: "Potential secret material must be removed." }, locations: [ { physicalLocation: { artifactLocation: { uri: finding.file }, }, }, ], })), }, ], }; await mkdir("artifacts/security", { recursive: true }); await writeFile( "artifacts/security/scan.sarif", `${JSON.stringify(sarif, null, 2)}\n`, ); if (findings.length > 0) { process.stderr.write(`Security scan found ${findings.length} blocking result(s).\n`); process.exit(1); } process.stdout.write("Source and built-asset secret scan: PASS\n");