83 lines
2.4 KiB
JavaScript
83 lines
2.4 KiB
JavaScript
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<string[]>} */
|
|
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");
|