fix: fail closed on release input discovery

This commit is contained in:
DongHyeonka
2026-08-02 05:11:30 +09:00
parent 381d5549e2
commit d6c98489ee
9 changed files with 975 additions and 69 deletions
+18 -30
View File
@@ -1,7 +1,12 @@
import { createHash } from "node:crypto";
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import {
buildRepositoryFileInventory,
parseRepositoryFileInventoryPolicy,
} from "./lib/repository-file-inventory.ts";
type SecretFinding = Readonly<{
ruleId: string;
file: string;
@@ -19,6 +24,7 @@ type SecretPolicy = Readonly<{
excludedPaths: readonly string[];
trackedRoots: readonly string[];
generatedRoots: readonly string[];
optionalRoots: readonly string[];
allowlist: readonly AllowlistEntry[];
}>;
@@ -41,6 +47,7 @@ function strings(value: unknown): string[] {
function parsePolicy(value: unknown): SecretPolicy {
const document = isRecord(value) ? value : {};
const inventoryPolicy = parseRepositoryFileInventoryPolicy(value);
const allowlist = Array.isArray(document.allowlist)
? document.allowlist.map((rawEntry) => {
const entry = isRecord(rawEntry) ? rawEntry : {};
@@ -56,8 +63,9 @@ function parsePolicy(value: unknown): SecretPolicy {
: [];
return Object.freeze({
excludedPaths: Object.freeze(strings(document.excludedPaths)),
trackedRoots: Object.freeze(strings(document.trackedRoots)),
generatedRoots: Object.freeze(strings(document.generatedRoots)),
trackedRoots: inventoryPolicy.trackedRoots,
generatedRoots: inventoryPolicy.generatedRoots,
optionalRoots: inventoryPolicy.optionalRoots,
allowlist: Object.freeze(allowlist),
});
}
@@ -88,23 +96,6 @@ const patterns: readonly Readonly<{ id: string; expression: RegExp }>[] = [
},
];
async function filesWithin(target: string): Promise<string[]> {
try {
const metadata = await stat(target);
if (metadata.isFile()) return [target];
const entries = await readdir(target, { withFileTypes: true });
const nested: string[][] = await Promise.all(
entries.map((entry) => {
const child = path.join(target, entry.name);
return entry.isDirectory() ? filesWithin(child) : [child];
}),
);
return nested.flat();
} catch {
return [];
}
}
const excluded = new Set(
policy.excludedPaths.map((entry) => entry.replaceAll("\\", "/")),
);
@@ -124,10 +115,12 @@ for (const entry of allowlist) {
}
}
const roots = [...policy.trackedRoots, ...policy.generatedRoots];
const scanFiles = (
await Promise.all(roots.map((root) => filesWithin(root)))
).flat();
const inventory = await buildRepositoryFileInventory({
trackedRoots: policy.trackedRoots,
generatedRoots: policy.generatedRoots,
optionalRoots: policy.optionalRoots,
});
const scanFiles = inventory.files;
for (const scanFile of [...new Set(scanFiles)].sort()) {
const normalized = scanFile.replaceAll("\\", "/");
if (
@@ -138,12 +131,7 @@ for (const scanFile of [...new Set(scanFiles)].sort()) {
) {
continue;
}
let content: string;
try {
content = await readFile(scanFile, "utf8");
} catch {
continue;
}
const content = await readFile(scanFile, "utf8");
for (const pattern of patterns) {
pattern.expression.lastIndex = 0;
for (const match of content.matchAll(pattern.expression)) {