返回 CodeWhale
cloud-facts.test.ts
根目录 / web / lib / cloud-facts.test.ts
1 import { createHash, generateKeyPairSync, sign } from "node:crypto";
2 import { execFileSync, spawnSync } from "node:child_process";
3 import { readFileSync, mkdtempSync, writeFileSync, rmSync, symlinkSync, linkSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { fileURLToPath } from "node:url";
7 import { describe, expect, it, vi } from "vitest";
8 import {
9 etagFor, resolveCloudFacts, verifyEnvelope, signingMessage, responseFor,
10 MAX_ENVELOPE_BYTES, type CloudFactsEnvelope, type FactsCurrentRow, type CloudFactsResult,
11 } from "./cloud-facts";
12 import { TRUSTED_KEYS, type TrustedKey } from "./cloud-facts/keys";
13 import { GET, HEAD } from "../app/api/facts/v1/[channel]/route";
14 import { activePublishingKey, emitSql, readBoundedFile, readBoundedResponse, verifyEnvelope as verifyForPublisher } from "../scripts/facts-publish.mjs";
15 import { parseRustKeys, parseTsKeys } from "../scripts/check-cloud-facts.mjs";
16
17 const fixturePath = new URL("../../docs/cloud-facts/fixtures/envelope-stable-v7.json", import.meta.url);
18 const fixture = JSON.parse(readFileSync(fixturePath, "utf8")) as CloudFactsEnvelope;
19 const futureFixture = JSON.parse(readFileSync(new URL("../../docs/cloud-facts/fixtures/envelope-future-only-v8.json", import.meta.url), "utf8")) as CloudFactsEnvelope;
20 const TEST_KEY: TrustedKey = { keyId: "cwf-test-only", publicKey: "8+FLDW4OorUETUVks0hpQAi5Lj4wg3kjKjfYFzLbJ7U=", status: "active" };
21 const NOW = Date.parse("2026-09-07T00:00:00Z");
22 const basePayload = JSON.parse(Buffer.from(fixture.payload_b64, "base64").toString("utf8"));
23 // Ephemeral test keys exist only in memory and never enter production anchors.
24 const ephemeral = generateKeyPairSync("ed25519");
25 const EPHEMERAL_KEY: TrustedKey = { keyId: "cwf-ephemeral-test", publicKey: ephemeral.publicKey.export({ type: "spki", format: "der" }).subarray(-32).toString("base64"), status: "active" };
26
27 function signed(overrides: Record<string, unknown> = {}): CloudFactsEnvelope {
28 const payload = { ...basePayload, ...overrides };
29 const bytes = Buffer.from(JSON.stringify(payload));
30 return {
31 envelope: 1, channel: payload.channel, facts_version: payload.facts_version,
32 schema_version: payload.schema_version, key_id: EPHEMERAL_KEY.keyId, alg: "ed25519",
33 applies_to: payload.applies_to, published_at: payload.published_at,
34 not_after: payload.not_after ?? null, payload_b64: bytes.toString("base64"),
35 sig_b64: sign(null, signingMessage(EPHEMERAL_KEY.keyId, bytes), ephemeral.privateKey).toString("base64"),
36 sigs: [], sha256: createHash("sha256").update(bytes).digest("hex"),
37 };
38 }
39
40 function rowFrom(envelope = fixture, overrides: Partial<FactsCurrentRow> = {}): FactsCurrentRow {
41 return { channel: envelope.channel, release_id: "00000000-0000-0000-0000-000000000001",
42 facts_version: envelope.facts_version, schema_version: envelope.schema_version,
43 envelope_version: envelope.envelope, applies_to: envelope.applies_to, key_id: envelope.key_id,
44 payload_b64: envelope.payload_b64, sig_b64: envelope.sig_b64, sigs: envelope.sigs,
45 payload_sha256: envelope.sha256, published_at: envelope.published_at,
46 not_after: envelope.not_after ?? null, ...overrides };
47 }
48
49 function supabaseFetch(rows: unknown, status = 200): typeof fetch {
50 return vi.fn(async () => new Response(JSON.stringify(rows), { status, headers: { "Content-Type": "application/json" } })) as typeof fetch;
51 }
52
53 class MemKV {
54 store = new Map<string, string>();
55 async get(key: string, type: "stream") {
56 expect(type).toBe("stream");
57 const raw = this.store.get(key);
58 return raw === undefined ? null : new Response(raw).body;
59 }
60 async put(key: string, value: string) { this.store.set(key, value); }
61 }
62 const env = { SUPABASE_URL: "https://example.supabase.co", SUPABASE_PUBLISHABLE_KEY: "sb_publishable_test" };
63 const opts = { keys: [TEST_KEY], now: () => NOW };
64 const failing = supabaseFetch(null, 503);
65
66 function overflowingStream() {
67 let pulls = 0;
68 const cancel = vi.fn();
69 return { cancel, pulls: () => pulls, stream: new ReadableStream<Uint8Array>({
70 pull(controller) { pulls += 1; controller.enqueue(new Uint8Array(MAX_ENVELOPE_BYTES / 2 + 1)); },
71 cancel,
72 }) };
73 }
74
75 describe("cloud facts verification", () => {
76 it("authenticates public fixtures, including a future-only client applicability range", async () => {
77 for (const envelope of [fixture, futureFixture]) {
78 expect(await verifyEnvelope(envelope, [TEST_KEY], { channel: "stable", now: NOW })).toEqual({ ok: true, keyId: TEST_KEY.keyId, mode: "verified" });
79 expect(verifyForPublisher(envelope, TEST_KEY.publicKey).ok).toBe(true);
80 }
81 const result = await resolveCloudFacts("stable", env, { ...opts, fetchImpl: supabaseFetch([rowFrom(futureFixture)]) });
82 expect(result).toMatchObject({ kind: "ok", envelope: { applies_to: ">=99.0.0", facts_version: 8 } });
83 });
84
85 it("pins a well-formed production anchor and refuses empty or retired-only trust before any reads", async () => {
86 // The anchor itself is checked for shape, not for a specific key: pinning a
87 // second key or rotating must not fail this test, but a malformed one must.
88 // Byte-for-byte agreement with the Rust table is `check-cloud-facts.mjs`.
89 expect(TRUSTED_KEYS.length).toBeGreaterThan(0);
90 for (const key of TRUSTED_KEYS) {
91 expect(key.keyId).toMatch(/^cwf-[A-Za-z0-9._-]+$/);
92 expect(["active", "retired"]).toContain(key.status);
93 // Standard base64 of a raw 32-byte Ed25519 public key.
94 expect(Buffer.from(key.publicKey, "base64")).toHaveLength(32);
95 }
96 expect(TRUSTED_KEYS.some((key) => key.status === "active")).toBe(true);
97
98 // The property that actually matters is unchanged: with no usable key the
99 // layer fails closed *before* any network or cache read.
100 const fetchImpl = vi.fn();
101 const get = vi.fn();
102 for (const keys of [[], [{ ...TEST_KEY, status: "retired" as const }]]) {
103 expect(await verifyEnvelope(fixture, keys)).toEqual({ ok: false, reason: "no-active-keys" });
104 expect(await resolveCloudFacts("stable", { ...env, CURATED_KV: { get, put: vi.fn() } }, { keys, fetchImpl })).toEqual({ kind: "unavailable", reason: "no-active-keys" });
105 }
106 expect(fetchImpl).not.toHaveBeenCalled();
107 expect(get).not.toHaveBeenCalled();
108 });
109
110 it("rejects invalid signatures, unknown keys, retired keys and ambiguous key tables", async () => {
111 expect(await verifyEnvelope({ ...fixture, sig_b64: `A${fixture.sig_b64.slice(1)}` }, [TEST_KEY])).toEqual({ ok: false, reason: "bad-signature" });
112 expect(await verifyEnvelope(fixture, [EPHEMERAL_KEY])).toEqual({ ok: false, reason: "unknown-key" });
113 expect(await verifyEnvelope(fixture, [{ ...TEST_KEY, status: "retired" }, EPHEMERAL_KEY])).toEqual({ ok: false, reason: "retired-key" });
114 expect(await verifyEnvelope(fixture, [TEST_KEY, TEST_KEY])).toEqual({ ok: false, reason: "no-active-keys" });
115 });
116
117 it("cross-checks every unsigned metadata field with the signed payload", async () => {
118 for (const change of [{ channel: "beta" }, { facts_version: 8 }, { schema_version: 2 },
119 { applies_to: ">=99.0.0" }, { published_at: "2026-09-01T00:00:00Z" },
120 { not_after: "2026-10-01T00:00:00Z" }, { sha256: "0".repeat(64) }]) {
121 expect((await verifyEnvelope({ ...fixture, ...change }, [TEST_KEY], { channel: "stable", now: NOW })).ok).toBe(false);
122 expect(verifyForPublisher({ ...fixture, ...change }, TEST_KEY.publicKey).ok).toBe(false);
123 }
124 expect(await verifyEnvelope(signed({ channel: "beta" }), [EPHEMERAL_KEY], { channel: "stable", now: NOW })).toEqual({ ok: false, reason: "wrong-channel" });
125 });
126
127 it("rejects signed bad versions, applicability, schema and UTC dates", async () => {
128 for (const change of [{ facts_version: "7; DROP TABLE public.facts_key;" }, { facts_version: 0 },
129 { facts_version: Number.MAX_SAFE_INTEGER + 1 }, { schema_version: 2 }, { applies_to: "><=3" },
130 { published_at: "2026-02-30T00:00:00Z" }, { published_at: "not-a-date" }]) {
131 const envelope = signed(change);
132 expect((await verifyEnvelope(envelope, [EPHEMERAL_KEY], { now: NOW })).ok).toBe(false);
133 expect(verifyForPublisher(envelope, EPHEMERAL_KEY.publicKey).ok).toBe(false);
134 }
135 });
136
137 it("rejects future publication, expiry and reversed signed time windows", async () => {
138 expect(await verifyEnvelope(fixture, [TEST_KEY], { now: Date.parse("2026-08-29T00:00:00Z") })).toEqual({ ok: false, reason: "bad-payload" });
139 const expired = signed({ not_after: "2026-09-06T00:00:00Z" });
140 expect(await verifyEnvelope(expired, [EPHEMERAL_KEY], { now: NOW })).toEqual({ ok: false, reason: "expired" });
141 expect((await resolveCloudFacts("stable", env, { keys: [EPHEMERAL_KEY], now: () => NOW, fetchImpl: supabaseFetch([rowFrom(expired)]) })).kind).toBe("unverifiable");
142 expect((await verifyEnvelope(signed({ not_after: "2026-08-29T00:00:00Z" }), [EPHEMERAL_KEY], { now: NOW })).ok).toBe(false);
143 });
144
145 it("rejects oversized/noncanonical base64 and hostile signature shapes without throwing", async () => {
146 for (const value of [null, [], {}, { ...fixture, payload_b64: "A".repeat(MAX_ENVELOPE_BYTES) },
147 { ...fixture, payload_b64: `${fixture.payload_b64}\n` }, { ...fixture, sig_b64: `${fixture.sig_b64}garbage` },
148 { ...fixture, sigs: "not-an-array" }, { ...fixture, sigs: Array(9).fill({ key_id: TEST_KEY.keyId, sig_b64: fixture.sig_b64 }) },
149 { ...fixture, sigs: [null] }]) {
150 expect((await verifyEnvelope(value, [TEST_KEY])).ok).toBe(false);
151 expect(verifyForPublisher(value, TEST_KEY.publicKey).ok).toBe(false);
152 }
153 });
154
155 it("reports the authenticating rotation key and changes ETag for signature-only updates", async () => {
156 const rotated = { ...fixture, sigs: [{ key_id: EPHEMERAL_KEY.keyId,
157 sig_b64: sign(null, signingMessage(EPHEMERAL_KEY.keyId, Buffer.from(fixture.payload_b64, "base64")), ephemeral.privateKey).toString("base64") }] };
158 const keys = [{ ...TEST_KEY, status: "retired" as const }, EPHEMERAL_KEY];
159 expect(await verifyEnvelope(rotated, keys, { now: NOW })).toEqual({ ok: true, keyId: EPHEMERAL_KEY.keyId, mode: "verified" });
160 expect(await etagFor(rotated)).not.toBe(await etagFor(fixture));
161 const result = await resolveCloudFacts("stable", env, { keys, now: () => NOW, fetchImpl: supabaseFetch([rowFrom(rotated)]) });
162 expect(result).toMatchObject({ kind: "ok", keyId: EPHEMERAL_KEY.keyId });
163 const request = new Request("https://example.test", { headers: { "if-none-match": await etagFor(fixture) } });
164 const response = responseFor(result, request, "stable", "GET");
165 expect(response.status).toBe(200);
166 expect(response.headers.get("x-facts-key")).toBe(EPHEMERAL_KEY.keyId);
167 });
168 });
169
170 describe("cloud facts transport", () => {
171 it("uses one global channel query with a publishable credential and writes only a verified cache", async () => {
172 const kv = new MemKV();
173 const fetchImpl = supabaseFetch([rowFrom(fixture, { published_at: "2026-08-30T00:00:00+00:00" })]);
174 const result = await resolveCloudFacts("stable", { ...env, CURATED_KV: kv }, { ...opts, fetchImpl });
175 expect(result).toMatchObject({ kind: "ok", source: "supabase", verified: "verified" });
176 const [url, init] = vi.mocked(fetchImpl).mock.calls[0];
177 const parsed = new URL(String(url));
178 expect(parsed.origin).toBe(env.SUPABASE_URL);
179 expect(parsed.pathname).toBe("/rest/v1/facts_current");
180 expect(Object.fromEntries(parsed.searchParams)).toMatchObject({ channel: "eq.stable", scope: "eq.global", limit: "1" });
181 expect(init?.headers).toMatchObject({ apikey: env.SUPABASE_PUBLISHABLE_KEY, Authorization: `Bearer ${env.SUPABASE_PUBLISHABLE_KEY}` });
182 expect(init?.redirect).toBe("error");
183 if (result.kind !== "ok") throw new Error("expected verified result");
184 expect(kv.store.get("facts:cloud:stable")).toBe(result.body);
185 expect(result.etag).toBe(`"${createHash("sha256").update(result.body).digest("hex")}"`);
186 });
187
188 it("rejects service/secret credentials before dispatch", async () => {
189 const fetchImpl = vi.fn();
190 const serviceJwt = `a.${Buffer.from(JSON.stringify({ role: "service_role" })).toString("base64url")}.b`;
191 for (const key of ["sb_secret_do-not-send", serviceJwt]) {
192 expect((await resolveCloudFacts("stable", { ...env, SUPABASE_PUBLISHABLE_KEY: key }, { ...opts, fetchImpl })).kind).toBe("unavailable");
193 }
194 expect(fetchImpl).not.toHaveBeenCalled();
195 });
196
197 it("distinguishes no row and invalid channel and refuses a mismatched signed channel", async () => {
198 const fetchImpl = supabaseFetch([]);
199 expect((await resolveCloudFacts("Bad Slug", env, { ...opts, fetchImpl })).kind).toBe("none");
200 expect(fetchImpl).not.toHaveBeenCalled();
201 expect((await resolveCloudFacts("stable", env, { ...opts, fetchImpl })).kind).toBe("none");
202 const beta = signed({ channel: "beta" });
203 expect(await resolveCloudFacts("stable", env, { keys: [EPHEMERAL_KEY], now: () => NOW, fetchImpl: supabaseFetch([rowFrom(beta)]) })).toMatchObject({ kind: "unverifiable", reason: "wrong-channel" });
204 });
205
206 it("never caches digest/signature failures and does not amplify them through a 304", async () => {
207 for (const change of [{ payload_sha256: "0".repeat(64) }, { sig_b64: `A${fixture.sig_b64.slice(1)}` }]) {
208 const kv = new MemKV();
209 const result = await resolveCloudFacts("stable", { ...env, CURATED_KV: kv }, { ...opts, fetchImpl: supabaseFetch([rowFrom(fixture, change)]) });
210 expect(["sha-mismatch", "unverifiable"]).toContain(result.kind);
211 expect(kv.store.size).toBe(0);
212 expect(responseFor(result, new Request("https://example.test", { headers: { "if-none-match": "*" } }), "stable", "GET").status).not.toBe(304);
213 }
214 });
215
216 it("revalidates cached digest, channel, expiry and current trust after an outage", async () => {
217 const kv = new MemKV();
218 await kv.put("facts:cloud:stable", JSON.stringify(fixture));
219 expect(await resolveCloudFacts("stable", { ...env, CURATED_KV: kv }, { ...opts, fetchImpl: failing })).toMatchObject({ kind: "ok", source: "kv-stale" });
220 for (const envelope of [{ ...fixture, sha256: "0".repeat(64) }, signed({ channel: "beta" }), signed({ not_after: "2026-09-06T00:00:00Z" })]) {
221 await kv.put("facts:cloud:stable", JSON.stringify(envelope));
222 expect((await resolveCloudFacts("stable", { ...env, CURATED_KV: kv }, { keys: [TEST_KEY, EPHEMERAL_KEY], now: () => NOW, fetchImpl: failing })).kind).toBe("unavailable");
223 }
224 await kv.put("facts:cloud:stable", JSON.stringify(fixture));
225 expect((await resolveCloudFacts("stable", { ...env, CURATED_KV: kv }, { keys: [EPHEMERAL_KEY], now: () => NOW, fetchImpl: failing })).kind).toBe("unavailable");
226 });
227
228 it("caps and cancels PostgREST streaming bodies before JSON parsing", async () => {
229 const oversized = overflowingStream();
230 const fetchImpl = vi.fn(async () => new Response(oversized.stream));
231 expect((await resolveCloudFacts("stable", env, { ...opts, fetchImpl })).kind).toBe("unavailable");
232 expect(oversized.cancel).toHaveBeenCalledOnce();
233 expect(oversized.pulls()).toBeLessThanOrEqual(3);
234 const declared = overflowingStream();
235 expect((await resolveCloudFacts("stable", env, { ...opts, fetchImpl: vi.fn(async () => new Response(declared.stream, { headers: { "content-length": String(MAX_ENVELOPE_BYTES + 1) } })) })).kind).toBe("unavailable");
236 expect(declared.cancel).toHaveBeenCalledOnce();
237 });
238
239 it("caps KV streams and isolates malformed cache objects", async () => {
240 const oversized = overflowingStream();
241 const get = vi.fn(async () => oversized.stream);
242 expect((await resolveCloudFacts("stable", { ...env, CURATED_KV: { get, put: vi.fn() } }, { ...opts, fetchImpl: failing })).kind).toBe("unavailable");
243 expect(get).toHaveBeenCalledWith("facts:cloud:stable", "stream");
244 expect(oversized.cancel).toHaveBeenCalledOnce();
245 const kv = new MemKV();
246 for (const bad of ["not-json", "null", JSON.stringify({ sigs: null })]) {
247 await kv.put("facts:cloud:stable", bad);
248 expect((await resolveCloudFacts("stable", { ...env, CURATED_KV: kv }, { ...opts, fetchImpl: failing })).kind).toBe("unavailable");
249 }
250 });
251 });
252
253 describe("facts response protocol", () => {
254 const req = (headers: Record<string, string> = {}) => new Request("https://codewhale.net/api/facts/v1/stable", { headers });
255 async function ok(): Promise<CloudFactsResult> { return { kind: "ok", envelope: fixture, body: JSON.stringify(fixture), etag: await etagFor(fixture), source: "supabase", verified: "verified", keyId: TEST_KEY.keyId }; }
256
257 it("serves cacheable GET, conditional 304 and bodyless successful HEAD", async () => {
258 const result = await ok();
259 if (result.kind !== "ok") throw new Error("fixture result");
260 const get = responseFor(result, req(), "stable", "GET");
261 expect(get.status).toBe(200);
262 expect(get.headers.get("cache-control")).toContain("s-maxage=300");
263 expect(get.headers.get("set-cookie")).toBeNull();
264 expect(get.headers.get("vary")).toBeNull();
265 expect(await get.json()).toEqual(fixture);
266 for (const tag of [result.etag, `W/${result.etag}`, "*"]) {
267 const response = responseFor(result, req({ "if-none-match": tag }), "stable", "GET");
268 expect(response.status).toBe(304);
269 expect(await response.text()).toBe("");
270 }
271 const head = responseFor(result, req(), "stable", "HEAD");
272 expect(head.headers.get("content-length")).toBe(String(Buffer.byteLength(result.body)));
273 expect(await head.text()).toBe("");
274 });
275
276 it("returns bodyless HEAD for every error and the invalid-channel route", async () => {
277 const cases: CloudFactsResult[] = [{ kind: "none" }, { kind: "sha-mismatch", channel: "stable", factsVersion: 7 },
278 { kind: "unverifiable", channel: "stable", factsVersion: 7, reason: "bad-signature" }, { kind: "unavailable", reason: "no-active-keys" }];
279 for (const result of cases) {
280 const response = responseFor(result, req(), "stable", "HEAD");
281 expect([404, 502, 503]).toContain(response.status);
282 expect(await response.text()).toBe("");
283 }
284 expect(await (await HEAD(req(), { params: Promise.resolve({ channel: "Bad Slug" }) })).text()).toBe("");
285 const get = await GET(req(), { params: Promise.resolve({ channel: "Bad Slug" }) });
286 expect(get.status).toBe(404);
287 });
288
289 it("keeps CDN freshness inside a signed expiry without stale-serving extensions", async () => {
290 const envelope = signed({ not_after: new Date(Date.now() + 90_000).toISOString() });
291 const result = await resolveCloudFacts("stable", env, { keys: [EPHEMERAL_KEY], fetchImpl: supabaseFetch([rowFrom(envelope)]) });
292 expect(result.kind).toBe("ok");
293 const response = responseFor(result, req(), "stable", "GET");
294 const control = response.headers.get("cache-control")!;
295 expect(control).toContain("must-revalidate");
296 expect(control).not.toContain("stale-");
297 expect(Number(control.match(/s-maxage=(\d+)/)?.[1])).toBeLessThanOrEqual(90);
298 });
299 });
300
301 describe("facts publisher boundaries", () => {
302 const script = fileURLToPath(new URL("../scripts/facts-publish.mjs", import.meta.url));
303
304 it("requires an active pinned primary key and cannot publish with an explicit fixture public key", () => {
305 expect(() => activePublishingKey(fixture, [])).toThrow("not pinned and active");
306 expect(() => activePublishingKey(fixture, [{ ...TEST_KEY, status: "retired" }])).toThrow("not pinned and active");
307 expect(activePublishingKey(fixture, [TEST_KEY], NOW).check.ok).toBe(true);
308 const result = spawnSync(process.execPath, [script, "publish", fileURLToPath(fixturePath), "--dry-run", "--public-key", TEST_KEY.publicKey], { encoding: "utf8" });
309 expect(result.status).toBe(1);
310 expect(result.stderr).toContain("publication requires the active pinned table");
311 });
312
313 it("refuses to publish authentically signed future or expired facts", () => {
314 expect(() => activePublishingKey(signed({ not_after: "2026-09-06T00:00:00Z" }), [EPHEMERAL_KEY], NOW)).toThrow("future or expired");
315 expect(() => activePublishingKey(signed({ published_at: "2026-09-08T00:00:00Z" }), [EPHEMERAL_KEY], NOW)).toThrow("future or expired");
316 });
317
318 it("refuses CI signing before reading a source or private credential path", () => {
319 const result = spawnSync(process.execPath, [script, "sign", "--source", "/nonexistent-source-must-not-be-read"], {
320 encoding: "utf8", env: { ...process.env, CI: "true", CODEWHALE_FACTS_SIGNING_KEY_FILE: "/nonexistent-key-must-not-be-read" },
321 });
322 expect(result.status).toBe(1);
323 expect(result.stderr).toContain("refusing to run with a secret under CI");
324 expect(result.stderr).not.toContain("ENOENT");
325 });
326
327 it("creates private keys exclusively without overwriting an existing file or symlink", () => {
328 const dir = mkdtempSync(join(tmpdir(), "facts-key-exclusion-"));
329 try {
330 const target = join(dir, "existing.key");
331 writeFileSync(target, "preserve-existing-file");
332 const cleanEnv = { ...process.env };
333 for (const marker of ["CI", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "CIRCLECI", "JENKINS_URL", "TF_BUILD"]) delete cleanEnv[marker];
334 const result = spawnSync(process.execPath, [script, "keygen", "--key-id", "cwf-test-exclusive", "--out", target], { encoding: "utf8", env: cleanEnv });
335 expect(result.status).toBe(1);
336 expect(readFileSync(target, "utf8")).toBe("preserve-existing-file");
337 if (process.platform !== "win32") {
338 const link = join(dir, "linked.key");
339 symlinkSync(target, link);
340 expect(spawnSync(process.execPath, [script, "keygen", "--key-id", "cwf-test-exclusive", "--out", link], { encoding: "utf8", env: cleanEnv }).status).toBe(1);
341 expect(readFileSync(target, "utf8")).toBe("preserve-existing-file");
342 }
343 } finally { rmSync(dir, { recursive: true, force: true }); }
344 });
345
346 it("rejects signed numeric SQL injection and emits escaped SQL only after verification", () => {
347 const malicious = signed({ facts_version: "7; DROP TABLE public.facts_key;" });
348 expect(() => emitSql(malicious, { publicKeyB64: EPHEMERAL_KEY.publicKey })).toThrow("positive safe integer");
349 const sql = emitSql(fixture, { publicKeyB64: TEST_KEY.publicKey, publishedBy: "O'Hara" });
350 expect(sql).toContain("select c.id, 7, 1, 1");
351 expect(sql).toContain("O''Hara");
352 });
353
354 it("parses intentional empty key tables but fails closed on unknown syntax or duplicate/invalid anchors", () => {
355 expect(parseTsKeys("export const TRUSTED_KEYS: readonly TrustedKey[] = [];")).toEqual([]);
356 expect(parseRustKeys("pub const TRUSTED_KEYS: &[TrustedKey] = &[];")).toEqual([]);
357 for (const text of ["no table", "export const TRUSTED_KEYS: readonly TrustedKey[] = [makeKey()];"]) expect(() => parseTsKeys(text)).toThrow();
358 expect(() => parseRustKeys("pub const TRUSTED_KEYS: &[TrustedKey] = &[make_key()];")).toThrow();
359 expect(() => parseTsKeys("// export const TRUSTED_KEYS: readonly TrustedKey[] = [];\nexport const TRUSTED_KEYS = makeKeys();")).toThrow();
360 expect(() => parseRustKeys("// pub const TRUSTED_KEYS: &[TrustedKey] = &[];\npub const TRUSTED_KEYS: &[TrustedKey] = &[make_key()];")).toThrow();
361 const entry = `{ keyId: "${TEST_KEY.keyId}", publicKey: "${TEST_KEY.publicKey}", status: "active" }`;
362 expect(() => parseTsKeys(`export const TRUSTED_KEYS: readonly TrustedKey[] = [${entry}, ${entry}];`)).toThrow();
363 expect(() => parseTsKeys(`export const TRUSTED_KEYS: readonly TrustedKey[] = [${entry.replace(TEST_KEY.publicKey, "bad")}];`)).toThrow();
364 const rust = `pub const TRUSTED_KEYS: &[TrustedKey] = &[TrustedKey { key_id: "${TEST_KEY.keyId}", public_key: [${[...Buffer.from(TEST_KEY.publicKey, "base64")].join(",")}], status: KeyStatus::Active }];`;
365 expect(parseRustKeys(rust)).toEqual([TEST_KEY]);
366 });
367
368 it("bounds publisher file/response reads and rejects symlink and hardlink inputs", async () => {
369 const dir = mkdtempSync(join(tmpdir(), "facts-bounded-read-"));
370 try {
371 const target = join(dir, "source.json");
372 writeFileSync(target, "12345");
373 expect(() => readBoundedFile(target, 4)).toThrow();
374 if (process.platform !== "win32") {
375 const link = join(dir, "symlink.json");
376 symlinkSync(target, link);
377 expect(() => readBoundedFile(link)).toThrow();
378 linkSync(target, join(dir, "hardlink.json"));
379 expect(() => readBoundedFile(target)).toThrow();
380 }
381 const oversized = overflowingStream();
382 await expect(readBoundedResponse(new Response(oversized.stream))).rejects.toThrow("size limit");
383 expect(oversized.cancel).toHaveBeenCalledOnce();
384 } finally { rmSync(dir, { recursive: true, force: true }); }
385 });
386
387 it("the local facts gate verifies both public fixtures and reports the pinned anchor count", () => {
388 const checker = fileURLToPath(new URL("../scripts/check-cloud-facts.mjs", import.meta.url));
389 // Derived from the table rather than hardcoded, so rotating or adding an
390 // anchor does not require editing this assertion — only a gate that has
391 // drifted out of step with the table will fail it.
392 const active = TRUSTED_KEYS.filter((key) => key.status === "active").length;
393 expect(execFileSync(process.execPath, [checker], { encoding: "utf8" })).toContain(
394 `${active} active production keys`,
395 );
396 });
397 });
398
398 lines TYPESCRIPT