fix: apply the recorded private Cache-Control matrix to image probes
TR-RR-09. The documented BT-IMG-02 contract requires a private response to carry no-store and fail closed when any directive that describes cacheability accompanies it. The probe checked only that no-store was present and public was absent, and the suite pinned the contradictory "private, no-store" as a success. Both now follow the recorded contract: only no-store and syntactically valid unknown extensions are admitted, and public, private, immutable, max-age, s-maxage, no-cache, must-revalidate and proxy-revalidate each fail closed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
efc577de63
commit
fb5b449031
@@ -310,9 +310,13 @@ function validResponseHeaders(
|
||||
);
|
||||
if (!directives) return false;
|
||||
if (request.delivery === "PRIVATE_SIGNED") {
|
||||
return (
|
||||
directives.get("no-store") === true &&
|
||||
!directives.has("public")
|
||||
// TR-RR-09. The recorded BT-IMG-02 contract is a fail-closed matrix, not a
|
||||
// pair of checks: a private response must carry `no-store` and nothing else
|
||||
// that describes cacheability. Only a syntactically valid unknown extension
|
||||
// is ignored, so a contradictory pairing can never read as acceptable.
|
||||
if (directives.get("no-store") !== true) return false;
|
||||
return !PRIVATE_FORBIDDEN_DIRECTIVES.some((name) =>
|
||||
directives.has(name),
|
||||
);
|
||||
}
|
||||
const maxAge = directives.get("max-age");
|
||||
@@ -336,6 +340,21 @@ function validResponseHeaders(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TR-RR-09. Every cacheability directive a `PRIVATE_SIGNED` response may not
|
||||
* carry alongside `no-store`.
|
||||
*/
|
||||
const PRIVATE_FORBIDDEN_DIRECTIVES: readonly string[] = Object.freeze([
|
||||
"public",
|
||||
"private",
|
||||
"immutable",
|
||||
"max-age",
|
||||
"s-maxage",
|
||||
"no-cache",
|
||||
"must-revalidate",
|
||||
"proxy-revalidate",
|
||||
]);
|
||||
|
||||
/**
|
||||
* BT-IMG-02. Quote- and escape-aware Cache-Control tokenizer.
|
||||
*
|
||||
|
||||
@@ -1658,7 +1658,9 @@ describe("browser image probe", () => {
|
||||
responseAt(imageUrl, png, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"cache-control": "private, no-store",
|
||||
// TR-RR-09. A private response carries `no-store` and nothing else
|
||||
// that describes cacheability.
|
||||
"cache-control": "no-store",
|
||||
"content-type": "image/png",
|
||||
},
|
||||
}),
|
||||
@@ -1748,6 +1750,67 @@ describe("browser image probe", () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* TR-RR-09. The recorded BT-IMG-02 contract for a private response is a
|
||||
* fail-closed matrix. Accepting `no-store` next to a directive that describes
|
||||
* cacheability lets a self-contradictory policy read as acceptable.
|
||||
*/
|
||||
it("applies the full private Cache-Control matrix", async () => {
|
||||
const png = pngBytes(640, 360);
|
||||
const probeWith = async (cacheControl: string) => {
|
||||
const probe = createBrowserImageProbe({
|
||||
fetcher: (async () =>
|
||||
responseAt(imageUrl, png, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"cache-control": cacheControl,
|
||||
"content-type": "image/png",
|
||||
},
|
||||
})) as typeof fetch,
|
||||
createBitmap: async () => ({
|
||||
width: 640,
|
||||
height: 360,
|
||||
close: vi.fn(),
|
||||
}),
|
||||
});
|
||||
return await probe.probe(
|
||||
request({
|
||||
delivery: "PRIVATE_SIGNED",
|
||||
minimumPublicMaxAgeSeconds: 0,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// Only `no-store`, plus a syntactically valid unknown extension.
|
||||
expect(await probeWith("no-store")).toMatchObject({ ok: true });
|
||||
expect(await probeWith('no-store, x-vendor="a,b"')).toMatchObject({
|
||||
ok: true,
|
||||
});
|
||||
|
||||
for (const companion of [
|
||||
"public",
|
||||
"private",
|
||||
"immutable",
|
||||
"max-age=60",
|
||||
"s-maxage=60",
|
||||
"no-cache",
|
||||
"must-revalidate",
|
||||
"proxy-revalidate",
|
||||
]) {
|
||||
expect(await probeWith(`no-store, ${companion}`)).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "POLICY_REJECTED" },
|
||||
});
|
||||
}
|
||||
|
||||
for (const withoutNoStore of ["private", "no-cache", "max-age=0"]) {
|
||||
expect(await probeWith(withoutNoStore)).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "POLICY_REJECTED" },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("times out a stalled body, aborts the composed signal and cancels its reader", async () => {
|
||||
const manual = manualImageProbeScheduler();
|
||||
const cancel = vi.fn(async () => undefined);
|
||||
|
||||
Reference in New Issue
Block a user