fix: cover every tracked release input

This commit is contained in:
DongHyeonka
2026-08-02 05:26:36 +09:00
parent d6c98489ee
commit 76d0ab0f62
14 changed files with 357 additions and 73 deletions
@@ -4,6 +4,9 @@
"tests/fixtures/security/secret-detection/forbidden"
],
"generatedRoots": [],
"includedPaths": [
"tests/fixtures/security/secret-detection/forbidden"
],
"excludedPaths": [],
"allowlist": []
}
+47
View File
@@ -130,6 +130,20 @@ describe("release artifact contracts", () => {
},
);
it("requires the canonical Vite manifest declared by the build writer", async () => {
const mismatches = await verifyBuildManifestOutputs(
{
...buildManifest,
outputs: {
...buildManifest.outputs,
viteManifest: "dist/.vite/tampered-manifest.json",
},
},
{ repositoryRoot: process.cwd() },
);
expect(mismatches).toContain("buildManifest:viteManifest:path");
});
it("rejects a realpath escape from a declared build output", async () => {
const mismatches = await verifyBuildManifestOutputs(buildManifest, {
repositoryRoot: "/repo",
@@ -167,6 +181,39 @@ describe("release artifact contracts", () => {
expect(mismatches).toContain("buildManifest:routeChunk:route-home:path");
});
it("allows a legal dist child whose name begins with two dots", async () => {
const moduleInventoryBytes = Buffer.from(
'{"schemaVersion":1,"chunks":[]}\n',
);
const files = new Map<string, Buffer>([
["/repo/dist/.vite/manifest.json", Buffer.from("{}\n")],
["/repo/artifacts/quality/vite-module-inventory.json", moduleInventoryBytes],
["/repo/dist/runtime-config.schema.json", Buffer.from("{}\n")],
["/repo/dist/..assets/home.js", Buffer.from("chunk\n")],
]);
const mismatches = await verifyBuildManifestOutputs(
{
...buildManifest,
moduleInventoryHash: createHash("sha256")
.update(moduleInventoryBytes)
.digest("hex"),
outputs: {
...buildManifest.outputs,
routeChunks: { "route-home": "..assets/home.js" },
},
},
{
repositoryRoot: "/repo",
readBytes: async (target) =>
files.get(target) ?? Promise.reject(new Error("missing")),
realpathPath: async (target) => target,
assertRegularFile: async () => undefined,
assertDirectory: async () => undefined,
},
);
expect(mismatches).toEqual([]);
});
it.each([
["viteManifest", "package.json"],
["runtimeConfigSchema", "schemas/artifacts/build-manifest.schema.json"],
@@ -26,8 +26,11 @@ function gitResult(
async function repositoryFixture() {
const root = await mkdtemp(path.join(tmpdir(), "repository-inventory-"));
await mkdir(path.join(root, "src"));
await mkdir(path.join(root, "docs"));
await writeFile(path.join(root, "src", "tracked.ts"), "tracked\n");
await writeFile(path.join(root, "src", "untracked.ts"), "untracked\n");
await writeFile(path.join(root, "README.md"), "readme\n");
await writeFile(path.join(root, "docs", "outside-policy.md"), "docs\n");
return root;
}
@@ -90,6 +93,23 @@ describe("repository file inventory", () => {
expect(inventory.files).not.toContain("src/untracked.ts");
});
it("includes every Git-tracked file outside mandatory policy roots", async () => {
const repositoryRoot = await repositoryFixture();
const inventory = await buildRepositoryFileInventory({
repositoryRoot,
trackedRoots: ["src"],
runGit: () =>
gitResult("src/tracked.ts\0README.md\0docs/outside-policy.md\0"),
});
expect(inventory.trackedFiles).toEqual([
"README.md",
"docs/outside-policy.md",
"src/tracked.ts",
]);
expect(inventory.files).toEqual(inventory.trackedFiles);
});
it("rejects duplicate tracked paths instead of silently deduplicating", async () => {
const repositoryRoot = await repositoryFixture();
await expect(
@@ -214,6 +234,34 @@ describe("repository file inventory", () => {
expect(inventory.files).toEqual(["dist/asset.js", "src/tracked.ts"]);
});
it("rejects an exact tracked and generated path collision", async () => {
const repositoryRoot = await repositoryFixture();
await writeFile(path.join(repositoryRoot, "package.json"), "{}\n");
await expect(
buildRepositoryFileInventory({
repositoryRoot,
trackedRoots: ["package.json"],
generatedRoots: ["package.json"],
runGit: () => gitResult("package.json\0"),
}),
).rejects.toThrow(/tracked.*generated.*package\.json/u);
});
it("accepts a legal child whose name begins with two dots", async () => {
const repositoryRoot = await repositoryFixture();
await mkdir(path.join(repositoryRoot, "..assets"));
await writeFile(path.join(repositoryRoot, "..assets", "legal.ts"), "legal\n");
await expect(
buildRepositoryFileInventory({
repositoryRoot,
trackedRoots: ["..assets"],
runGit: () => gitResult("..assets/legal.ts\0"),
}),
).resolves.toMatchObject({ trackedFiles: ["..assets/legal.ts"] });
});
it("fails closed when an inventoried file cannot be read", async () => {
const repositoryRoot = await repositoryFixture();
await expect(
+40
View File
@@ -10,6 +10,8 @@ import {
validateDependencyReview,
validateLicensePolicy,
} from "../../scripts/lib/supply-chain.ts";
import { digestReleaseInputFiles } from "../../scripts/lib/release-input-evidence.ts";
import { findSecretMatches } from "../../scripts/lib/secret-scan.ts";
const integrity = `sha512-${Buffer.alloc(64, 7).toString("base64")}`;
const dependency = {
@@ -35,6 +37,42 @@ describe("supply-chain policy", () => {
}
});
it("binds provenance digest behavior to tracked files outside policy roots", async () => {
const contents = new Map([
["src/app.ts", Buffer.from("app\n")],
["README.md", Buffer.from("one\n")],
]);
const first = await digestReleaseInputFiles(
["README.md", "src/app.ts"],
async (file) => contents.get(file)!,
);
contents.set("README.md", Buffer.from("two\n"));
const second = await digestReleaseInputFiles(
["README.md", "src/app.ts"],
async (file) => contents.get(file)!,
);
expect(second).not.toBe(first);
});
it("detects every forbidden secret fixture, including quoted JSON keys", async () => {
const fixtureRoot = "tests/fixtures/security/secret-detection/forbidden";
const findings = (
await Promise.all(
["source.ts", "dist.ts", "config.json"].map(async (file) =>
findSecretMatches(
`${fixtureRoot}/${file}`,
await readFile(`${fixtureRoot}/${file}`, "utf8"),
),
),
)
).flat();
expect(findings.map((finding) => [finding.file, finding.ruleId])).toEqual([
[`${fixtureRoot}/source.ts`, "aws-access-key"],
[`${fixtureRoot}/dist.ts`, "assigned-secret"],
[`${fixtureRoot}/config.json`, "assigned-secret"],
]);
});
it("covers every mandatory release input in the secret scan policy", async () => {
const policy = JSON.parse(
await readFile("config/security/secret-scan-policy.json", "utf8"),
@@ -55,6 +93,8 @@ describe("supply-chain policy", () => {
".gitea/workflows/quality-gates.yml",
"vite.config.ts",
"vite.service-worker.config.ts",
"vitest.config.ts",
"playwright.config.ts",
"playwright.capabilities.config.ts",
"playwright.dev.config.ts",
"playwright.storybook.config.ts",