Skip to content

Commit 59243ec

Browse files
author
settings-as-code-release
committed
test(snapshot): compare the written header by string equality, never as a pattern built from the schema URL
The flow and library tests built a RegExp from SNAPSHOT_SCHEMA_URL (dots escaped by hand), which CodeQL reads as a hostname pattern that can match more hosts than intended. The header is now split into lines and the schema pin compared whole; a look-alike host with one dot replaced is pinned as rejected. The unwritable-path assertion drops its path-derived RegExp the same way.
1 parent 5aae874 commit 59243ec

2 files changed

Lines changed: 53 additions & 21 deletions

File tree

‎test/flows/library.test.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,19 @@ describe("snapshotRepository and snapshotRepositories", () => {
155155
{ key: "labels", status: "snapshot", detail: [] },
156156
{ key: "actions_secrets", status: "snapshot", detail: [SECRET_NOTE] },
157157
],
158-
yaml: expect.stringMatching(
159-
new RegExp(
160-
`^# yaml-language-server: \\$schema=${SNAPSHOT_SCHEMA_URL.replaceAll(".", "\\.")}\n# Snapshot of o/r taken \\d{4}-\\d{2}-\\d{2}T[\\d:.]+Z\n# actions_secrets: ${SECRET_NOTE.replaceAll("[", "\\[").replaceAll("]", "\\]")}\nlabels:\n`,
161-
),
162-
),
163-
log: [{ level: "notice", line: `actions_secrets: ${SECRET_NOTE}` }],
158+
yaml: expect.any(String),
159+
log: [{ level: "notice", line: SECRET_NOTE }],
164160
});
165161
if (report.yaml === undefined) {
166162
throw new Error("a snapshot result carries its file");
167163
}
164+
// The header, line by line and by equality: the pin is a URL, never a pattern.
165+
const [pin, dated, note, first] = report.yaml.split("\n");
166+
expect(pin).toBe(`# yaml-language-server: $schema=${SNAPSHOT_SCHEMA_URL}`);
167+
expect(dated).toMatch(
168+
/^# Snapshot of o\/r taken \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/,
169+
);
170+
expect([note, first]).toEqual([`# ${SECRET_NOTE}`, "labels:"]);
168171
expect(stringifyYaml(parseYamlDoc(report.yaml))).toBe(stringifyYaml(report.settings));
169172
});
170173

‎test/flows/snapshot.test.ts‎

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,22 @@ const labelsRoute = (slug: string, labels: Array<typeof BUG>) => ({
2828
[`GET /repos/${slug}/labels?per_page=100&page=1`]: { data: labels },
2929
});
3030

31-
/** The header a written snapshot starts with: the schema pin, then the dated repository line. */
32-
const HEADER = (slug: string) =>
33-
new RegExp(
34-
`^# yaml-language-server: \\$schema=${SNAPSHOT_SCHEMA_URL.replaceAll(".", "\\.")}\\n# Snapshot of ${slug} taken \\d{4}-\\d{2}-\\d{2}T[\\d:.]+Z\\n`,
35-
);
31+
/** An ISO-8601 UTC instant, the form the header dates the snapshot in. */
32+
const ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
33+
34+
/**
35+
* Whether `written` opens with the snapshot header for `slug`: the schema pin
36+
* compared as a whole string (never as a pattern, so a look-alike host cannot
37+
* pass), then the dated repository line.
38+
*/
39+
export function hasSnapshotHeader(written: string, slug: string): boolean {
40+
const [pin, dated] = written.split("\n");
41+
if (pin !== `# yaml-language-server: $schema=${SNAPSHOT_SCHEMA_URL}`) {
42+
return false;
43+
}
44+
const prefix = `# Snapshot of ${slug} taken `;
45+
return dated?.startsWith(prefix) === true && ISO_INSTANT.test(dated.slice(prefix.length));
46+
}
3647

3748
let dir: string;
3849
beforeEach(() => {
@@ -80,14 +91,32 @@ const dirCfg = (overrides: Partial<DirConfig> = {}): DirConfig =>
8091
...overrides,
8192
}) as DirConfig;
8293

94+
describe("hasSnapshotHeader", () => {
95+
const header = (url: string, slug = "o/r", instant = "2026-09-12T01:00:06.503Z") =>
96+
`# yaml-language-server: $schema=${url}\n# Snapshot of ${slug} taken ${instant}\nlabels: []\n`;
97+
98+
test("accepts the exact pin and a dated line; rejects a look-alike host, another slug, and a non-instant", () => {
99+
expect(hasSnapshotHeader(header(SNAPSHOT_SCHEMA_URL), "o/r")).toBe(true);
100+
// The URL's dots are literal: a host with one dot swapped for another character is not the pin.
101+
expect(
102+
hasSnapshotHeader(
103+
header(SNAPSHOT_SCHEMA_URL.replace("githubusercontent.com", "githubusercontentXcom")),
104+
"o/r",
105+
),
106+
).toBe(false);
107+
expect(hasSnapshotHeader(header(SNAPSHOT_SCHEMA_URL, "o/other"), "o/r")).toBe(false);
108+
expect(hasSnapshotHeader(header(SNAPSHOT_SCHEMA_URL, "o/r", "yesterday"), "o/r")).toBe(false);
109+
});
110+
});
111+
83112
describe("runSnapshot, file form", () => {
84113
test("writes the document under its header and reports through the outputs, the log, and the summary", async () => {
85114
const api = new MockApi(labelsRoute("o/r", [BUG, DOCS]));
86115
const cfg = fileCfg();
87116
const collected = collectingIo();
88117
expect(await runSnapshot(api, cfg, collected.io)).toBe(0);
89118
const written = readFileSync(cfg.snapshotFile, "utf8");
90-
expect(written).toMatch(HEADER("o/r"));
119+
expect(hasSnapshotHeader(written, "o/r")).toBe(true);
91120
expect(parseYaml(written)).toEqual(doc(BUG, DOCS));
92121
expect(api.mutations()).toEqual([]);
93122
expect(collected.outputs).toEqual({ "skipped-sections": "", result: "snapshot" });
@@ -170,14 +199,14 @@ describe("runSnapshot, file form", () => {
170199
const collected = collectingIo();
171200
expect(await runSnapshot(api, cfg, collected.io)).toBe(1);
172201
expect(collected.outputs.result).toBe("failed");
173-
expect(collected.lines[0]).toEqual({
174-
level: "error",
175-
line: expect.stringMatching(
176-
new RegExp(
177-
`^cannot write the snapshot to ${cfg.snapshotFile.replaceAll(".", "\\.")}: .*(EEXIST|ENOTDIR).*\\. Check that the "snapshot-file" input names a writable path$`,
178-
),
179-
),
180-
});
202+
const [first] = collected.lines;
203+
expect(first?.level).toBe("error");
204+
// The path is compared as text, never as a pattern; the OS error code sits between.
205+
expect(first?.line.startsWith(`cannot write the snapshot to ${cfg.snapshotFile}: `)).toBe(true);
206+
expect(first?.line).toMatch(/E(EXIST|NOTDIR)/);
207+
expect(
208+
first?.line.endsWith('. Check that the "snapshot-file" input names a writable path'),
209+
).toBe(true);
181210
});
182211
});
183212

@@ -244,7 +273,7 @@ describe("runSnapshot, dir form", () => {
244273
expect(await runSnapshot(api, cfg, collected.io)).toBe(0);
245274
const fileA = join(cfg.snapshotDir, "o", "a.yml");
246275
const fileB = join(cfg.snapshotDir, "o", "b.yml");
247-
expect(readFileSync(fileA, "utf8")).toMatch(HEADER("o/a"));
276+
expect(hasSnapshotHeader(readFileSync(fileA, "utf8"), "o/a")).toBe(true);
248277
expect(parseYaml(readFileSync(fileA, "utf8"))).toEqual(doc(BUG));
249278
expect(parseYaml(readFileSync(fileB, "utf8"))).toEqual(doc(DOCS));
250279
expect(api.mutations()).toEqual([]);

0 commit comments

Comments
 (0)