fix: cover every tracked release input
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
"tests/fixtures/security/secret-detection/forbidden"
|
||||
],
|
||||
"generatedRoots": [],
|
||||
"includedPaths": [
|
||||
"tests/fixtures/security/secret-detection/forbidden"
|
||||
],
|
||||
"excludedPaths": [],
|
||||
"allowlist": []
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user