Files
clean-architecture-frontend…/tests/unit/image-cdn-runtime.test.ts
T
DongHyeonkaandClaude Opus 5 000a2581af fix: complete presigned capability and upload transport contracts
BT-PRE-02: add the top-level PRESIGNED_TRANSFER_V1 protocol literal to the
capability request and response. A missing, V0 or V2 envelope is closed as
POLICY_REJECTED before the vault registers anything. The server negotiates by
request shape; fields are never dual-emitted into a strict decoder, and the
nested PRESIGNED_MULTIPART_V1 binding protocol is unchanged.

BT-PRE-03: aborting a controller does not settle a fetch that ignores its
signal, so both presigned scopes now race the task, cancel a late response body
and survive a throwing scheduler without leaking the external abort listener.

BT-PRE-04: the vault owns its registration invariants, re-checking method,
href/origin/path agreement, embedded credentials, byte bounds, digest shape and
expiry, so a second issuer cannot register a weaker capability of the same type.

BT-PRE-05: decode each path segment once and require it to round-trip through
the canonical uppercase percent encoder, closing %2f, %5c, %252e%252e, mixed-case
escapes and encoded NUL while still admitting valid opaque UTF-8 segments.

BT-UP-02: inject and snapshot the upload transport clock and scheduler, so
Retry-After delta-seconds and HTTP-date resolve against the same captured now
and a clock rollback clamps to zero instead of producing a negative delay.

BT-IMG-01: make the image resolve() lifetime signal required, replacing the
hidden PRIMARY_REQUIRED preset precondition with a type-level one, and add the
negative typecheck fixture and gate that prove it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 02:19:00 +09:00

2235 lines
62 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import type {
BackendIssuedImageAsset,
ImageCapabilityVerifier,
ImagePresetReference,
ImageProbeRequest,
PublicImmutableImageAsset,
} from "../../src/application/ports/browser-transfer/image-cdn.ts";
import {
createBrowserImageProbe,
type ImageProbeScheduler,
} from "../../src/adapters/browser-transfer/image-cdn/browser-image-probe.ts";
import {
IMAGE_CDN_IMPLEMENTATION_CEILINGS,
ImageCdnPolicyRegistry,
imageCdnPresetReference,
type ImageCdnHardLimits,
type ImageCdnPresetPolicy,
} from "../../src/adapters/browser-transfer/image-cdn/image-cdn-policy.ts";
import {
computeImageCapabilityBindingDigestHex,
createImageCdnRuntime,
DEFAULT_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
type ImageCapabilityVerificationScheduler,
} from "../../src/adapters/browser-transfer/image-cdn/image-cdn-runtime.ts";
import { createP256ImageCapabilityVerifier } from "../../src/adapters/browser-transfer/image-cdn/p256-image-capability-verifier.ts";
const hardLimits: ImageCdnHardLimits = Object.freeze({
maxIntrinsicWidth: 4_096,
maxIntrinsicHeight: 4_096,
maxSourcePixels: 16_777_216,
maxCssDimension: 2_048,
maxDpr: 2,
maxQuality: 90,
maxCandidateCount: 8,
maxTransformedPixels: 1_048_576,
maxDecodedBytes: 4_194_304,
maxEncodedBytes: 524_288,
maxUrlLength: 2_048,
maxCapabilityLifetimeMs: 3_600_000,
maxClockSkewMs: 60_000,
minCapabilityRemainingMs: 30_000,
maxPresetBindingsPerCapability: 8,
maxConcurrentCapabilityVerifications: 8,
allowedSourceMediaTypes: [
"image/avif",
"image/jpeg",
"image/png",
"image/webp",
] as const,
formatQualityCeilings: {
avif: 80,
jpeg: 85,
png: 90,
webp: 85,
},
});
function acceptsTestImageKey(keyId: string): boolean {
return keyId === "image-signing-2026-01";
}
function createPreset(
reference: ImagePresetReference,
overrides: Partial<ImageCdnPresetPolicy> = {},
): ImageCdnPresetPolicy {
return {
reference,
bindingId: "card-landscape-v1",
width: 640,
height: 360,
fit: "cover",
dprs: [2, 1],
responsiveWidths: [640, 320],
quality: 80,
formats: ["avif", "webp", "jpeg"],
sizes: "(max-width: 640px) 100vw, 640px",
loading: "eager",
decoding: "async",
fetchPriority: "high",
referrerPolicy: "strict-origin-when-cross-origin",
probeMode: "NONE",
allowUpscale: false,
maxTransformedPixels: 1_048_576,
maxDecodedBytes: 4_194_304,
maxEncodedBytes: 524_288,
...overrides,
};
}
function createPolicies(
presets: readonly ImageCdnPresetPolicy[],
overrides: Readonly<{
hardLimits?: ImageCdnHardLimits;
acceptedKeyIds?: readonly string[];
}> = {},
): ImageCdnPolicyRegistry {
return new ImageCdnPolicyRegistry({
applicationOrigin: "https://app.example.test",
origins: [
{
originKey: "product-images",
origin: "https://images.example.test",
assetPathPrefix: "/v1/assets/",
minimumPublicMaxAgeSeconds: 31_536_000,
},
],
presets,
hardLimits: overrides.hardLimits ?? hardLimits,
capability: {
issuer: "image-bff",
acceptedKeyIds: overrides.acceptedKeyIds ?? [
"image-signing-2026-01",
],
},
});
}
function publicAsset(
overrides: Partial<PublicImmutableImageAsset> = {},
): PublicImmutableImageAsset {
return {
kind: "ALLOWLISTED_PUBLIC",
originKey: "product-images",
assetId: "asset_Q3x8pL",
revision: "rev_a8N2kP4z",
mediaType: "image/jpeg",
contentKind: "RASTER_STATIC",
intrinsicWidth: 2_048,
intrinsicHeight: 1_152,
...overrides,
};
}
async function privateAsset(
overrides: Partial<BackendIssuedImageAsset> = {},
): Promise<BackendIssuedImageAsset> {
const base: BackendIssuedImageAsset = {
kind: "BACKEND_ISSUED_PRIVATE",
issuer: "image-bff",
originKey: "product-images",
assetId: "asset_Q3x8pL",
revision: "rev_a8N2kP4z",
mediaType: "image/jpeg",
contentKind: "RASTER_STATIC",
intrinsicWidth: 2_048,
intrinsicHeight: 1_152,
capabilityId: "cap_Z8m2Q7pR",
issuedAtEpochMs: 1_000_000,
expiresAtEpochMs: 1_300_000,
allowedPresetBindingIds: ["card-landscape-v1"],
signature: {
algorithm: "ECDSA_P256_SHA256",
keyId: "image-signing-2026-01",
capabilityBindingDigestHex: "0".repeat(64),
valueBase64Url: "A".repeat(86),
},
...overrides,
};
const descriptor = {
...base,
signature: {
...base.signature,
...(overrides.signature ?? {}),
},
};
const digest = await computeImageCapabilityBindingDigestHex(
globalThis.crypto.subtle,
descriptor,
);
return {
...descriptor,
signature: {
...descriptor.signature,
capabilityBindingDigestHex: digest,
},
};
}
describe("production image CDN runtime", () => {
it("builds sorted, duplicate-free responsive candidates from a named preset", async () => {
const preset = imageCdnPresetReference(
"card-landscape",
"render-public-product-image",
);
const runtime = createImageCdnRuntime({
policies: createPolicies([createPreset(preset)]),
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: vi.fn().mockResolvedValue(true),
},
});
const accepted = runtime.assets.acceptPublicImmutable(
publicAsset(),
);
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
const resolved = await runtime.presentation.resolve({
asset: accepted.value,
preset,
signal: new AbortController().signal,
});
expect(resolved.ok).toBe(true);
if (!resolved.ok) return;
const widthDescriptors = resolved.value.srcSet
.split(", ")
.map((candidate) =>
Number(candidate.match(/ (\d+)w$/u)?.[1]),
);
expect(widthDescriptors).toEqual([320, 640, 1_280]);
expect(new Set(widthDescriptors).size).toBe(3);
expect(resolved.value.sources.map((source) => source.type)).toEqual(
["image/avif", "image/webp"],
);
expect(resolved.value.fallbackMediaType).toBe("image/jpeg");
expect(resolved.value).toMatchObject({
width: 640,
height: 360,
loading: "eager",
decoding: "async",
fetchPriority: "high",
crossOrigin: "anonymous",
delivery: {
class: "PUBLIC_IMMUTABLE",
browserCache: "PUBLIC_IMMUTABLE",
sharedCache: "PUBLIC_IMMUTABLE",
purge: "REVISION_ROLLOVER",
expiresAtEpochMs: null,
},
decodeBudget: {
maximumCandidatePixels: 921_600,
maximumDecodedBytes: 3_686_400,
},
});
const primary = new URL(resolved.value.src);
expect(primary.origin).toBe("https://images.example.test");
expect(primary.pathname).toBe(
"/v1/assets/asset_Q3x8pL/rev_a8N2kP4z",
);
expect([...primary.searchParams.keys()]).toEqual([
"dpr",
"fit",
"format",
"height",
"preset",
"quality",
"width",
]);
expect(Object.fromEntries(primary.searchParams)).toEqual({
dpr: "1",
fit: "cover",
format: "jpeg",
height: "360",
preset: "card-landscape-v1",
quality: "80",
width: "640",
});
});
it("rejects raw URL/query/transform input and forged preset or asset references", async () => {
const preset = imageCdnPresetReference(
"card-landscape",
"render-public-product-image",
);
const runtime = createImageCdnRuntime({
policies: createPolicies([createPreset(preset)]),
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: vi.fn().mockResolvedValue(true),
},
});
expect(
runtime.assets.acceptPublicImmutable({
...publicAsset(),
sourceUrl: `${"java"}script:alert(1)`,
} as PublicImmutableImageAsset),
).toMatchObject({
ok: false,
error: { code: "INVALID_INPUT" },
});
expect(
runtime.assets.acceptPublicImmutable(
publicAsset({
assetId: "../../admin?width=9999",
}),
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(
runtime.assets.acceptPublicImmutable(
publicAsset({
mediaType: "image/svg+xml",
} as unknown as Partial<PublicImmutableImageAsset>),
),
).toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
const accepted = runtime.assets.acceptPublicImmutable(
publicAsset(),
);
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
await expect(
runtime.presentation.resolve({
asset: accepted.value,
preset,
signal: new AbortController().signal,
width: 9_999,
query: "format=svg",
src: "data:text/html,active",
} as unknown as Parameters<typeof runtime.presentation.resolve>[0]),
).resolves.toMatchObject({
ok: false,
error: { code: "INVALID_INPUT" },
});
await expect(
runtime.presentation.resolve({
asset: accepted.value,
preset: imageCdnPresetReference(
"card-landscape",
"render-public-product-image",
),
signal: new AbortController().signal,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
await expect(
runtime.presentation.resolve({
asset: {} as typeof accepted.value,
preset,
signal: new AbortController().signal,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
});
it("deep-snapshots origin and preset policy against TOCTOU mutation", async () => {
const presetReference = imageCdnPresetReference(
"card-landscape",
"render-public-product-image",
);
const origin = {
originKey: "product-images",
origin: "https://images.example.test",
assetPathPrefix: "/v1/assets/",
minimumPublicMaxAgeSeconds: 31_536_000,
};
const preset = createPreset(presetReference);
const policies = new ImageCdnPolicyRegistry({
applicationOrigin: "https://app.example.test",
origins: [origin],
presets: [preset],
hardLimits,
capability: {
issuer: "image-bff",
acceptedKeyIds: ["image-signing-2026-01"],
},
});
origin.origin = "https://attacker.invalid";
origin.assetPathPrefix = "/stolen/";
(preset.dprs as number[])[0] = 4;
(preset.formats as string[])[0] = "svg";
(preset as { quality: number }).quality = 100;
const runtime = createImageCdnRuntime({ policies });
const accepted = runtime.assets.acceptPublicImmutable(
publicAsset(),
);
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
const resolved = await runtime.presentation.resolve({
asset: accepted.value,
preset: presetReference,
signal: new AbortController().signal,
});
expect(resolved.ok).toBe(true);
if (!resolved.ok) return;
const url = new URL(resolved.value.src);
expect(url.origin).toBe("https://images.example.test");
expect(url.pathname.startsWith("/v1/assets/")).toBe(true);
expect(url.searchParams.get("quality")).toBe("80");
expect(resolved.value.sources[0]?.type).toBe("image/avif");
});
it("rejects unsafe origins and preset budgets at composition time", () => {
const preset = imageCdnPresetReference(
"unsafe",
"reject-policy",
);
expect(
() =>
new ImageCdnPolicyRegistry({
applicationOrigin: "https://app.example.test",
origins: [
{
originKey: "bad",
origin: "http://user:password@example.test/?q=x#x",
assetPathPrefix: "/../assets/",
minimumPublicMaxAgeSeconds: 1,
},
],
presets: [createPreset(preset)],
hardLimits,
capability: {
issuer: "image-bff",
acceptedKeyIds: ["image-signing-2026-01"],
},
}),
).toThrow(/origin policy/u);
expect(
() =>
createPolicies([
createPreset(preset, {
maxTransformedPixels: 100,
maxDecodedBytes: 400,
}),
]),
).toThrow(/pixel budget/u);
const validRegistryOptions = {
origins: [
{
originKey: "product-images",
origin: "https://images.example.test",
assetPathPrefix: "/v1/assets/",
minimumPublicMaxAgeSeconds: 31_536_000,
},
],
presets: [createPreset(preset)],
hardLimits,
capability: {
issuer: "image-bff",
acceptedKeyIds: ["image-signing-2026-01"],
},
};
expect(
() =>
new ImageCdnPolicyRegistry({
...validRegistryOptions,
applicationOrigin: "https://images.example.test",
}),
).toThrow(/cross-origin/u);
for (const applicationOrigin of [
"http://app.example.test",
"https://app.example.test/",
]) {
expect(
() =>
new ImageCdnPolicyRegistry({
...validRegistryOptions,
applicationOrigin,
}),
).toThrow(/application origin/u);
}
});
it("enforces immutable implementation ceilings on every resource-bearing hard limit", () => {
const preset = imageCdnPresetReference(
"absolute-ceilings",
"bound-image-composition",
);
const ceiling = IMAGE_CDN_IMPLEMENTATION_CEILINGS;
const overCeiling: readonly Partial<ImageCdnHardLimits>[] = [
{ maxIntrinsicWidth: ceiling.maxIntrinsicWidth + 1 },
{ maxIntrinsicHeight: ceiling.maxIntrinsicHeight + 1 },
{ maxSourcePixels: ceiling.maxSourcePixels + 1 },
{ maxCssDimension: ceiling.maxCssDimension + 1 },
{ maxDpr: ceiling.maxDpr + 1 },
{ maxQuality: ceiling.maxQuality + 1 },
{ maxCandidateCount: ceiling.maxCandidateCount + 1 },
{
maxTransformedPixels:
ceiling.maxTransformedPixels + 1,
},
{ maxDecodedBytes: ceiling.maxDecodedBytes + 1 },
{ maxEncodedBytes: ceiling.maxEncodedBytes + 1 },
{ maxUrlLength: ceiling.maxUrlLength + 1 },
{
maxCapabilityLifetimeMs:
ceiling.maxCapabilityLifetimeMs + 1,
},
{ maxClockSkewMs: ceiling.maxClockSkewMs + 1 },
{
minCapabilityRemainingMs:
ceiling.maxMinimumCapabilityRemainingMs + 1,
},
{
maxPresetBindingsPerCapability:
ceiling.maxPresetBindingsPerCapability + 1,
},
{
maxConcurrentCapabilityVerifications:
ceiling.maxConcurrentCapabilityVerifications + 1,
},
];
for (const override of overCeiling) {
expect(() =>
createPolicies([createPreset(preset)], {
hardLimits: { ...hardLimits, ...override },
}),
).toThrow(/hard limits/u);
}
const boundaryLimits: ImageCdnHardLimits = {
...hardLimits,
maxIntrinsicWidth: ceiling.maxIntrinsicWidth,
maxIntrinsicHeight: ceiling.maxIntrinsicHeight,
maxSourcePixels: ceiling.maxSourcePixels,
maxCssDimension: ceiling.maxCssDimension,
maxDpr: ceiling.maxDpr,
maxQuality: ceiling.maxQuality,
maxCandidateCount: ceiling.maxCandidateCount,
maxTransformedPixels: ceiling.maxTransformedPixels,
maxDecodedBytes: ceiling.maxDecodedBytes,
maxEncodedBytes: ceiling.maxEncodedBytes,
maxUrlLength: ceiling.maxUrlLength,
maxCapabilityLifetimeMs:
ceiling.maxCapabilityLifetimeMs,
maxClockSkewMs: ceiling.maxClockSkewMs,
minCapabilityRemainingMs:
ceiling.maxMinimumCapabilityRemainingMs,
maxPresetBindingsPerCapability:
ceiling.maxPresetBindingsPerCapability,
maxConcurrentCapabilityVerifications:
ceiling.maxConcurrentCapabilityVerifications,
};
expect(() =>
createPolicies([createPreset(preset)], {
hardLimits: boundaryLimits,
}),
).not.toThrow();
});
it("snapshots a bounded unique capability-key overlap set", () => {
const preset = imageCdnPresetReference(
"key-policy",
"rotate-image-signing-key",
);
for (const acceptedKeyIds of [
[],
["image-key-current", "image-key-current"],
Array.from(
{
length:
IMAGE_CDN_IMPLEMENTATION_CEILINGS.maxAcceptedKeyIds +
1,
},
(_, index) => `image-key-${index}`,
),
["image-key-current", "invalid/key"],
]) {
expect(() =>
createPolicies([createPreset(preset)], {
acceptedKeyIds,
}),
).toThrow(/capability policy/u);
}
const mutableKeyIds = [
"image-signing-2026-02",
"image-signing-2026-01",
];
const policies = createPolicies([createPreset(preset)], {
acceptedKeyIds: mutableKeyIds,
});
mutableKeyIds[0] = "image-signing-attacker";
expect(policies.capabilityPolicy().acceptedKeyIds).toEqual([
"image-signing-2026-02",
"image-signing-2026-01",
]);
expect(
Object.isFrozen(
policies.capabilityPolicy().acceptedKeyIds,
),
).toBe(true);
});
it("accepts old and new overlap keys only when policy and verifier registries both cover them", async () => {
const preset = imageCdnPresetReference(
"key-overlap",
"verify-image-key-rotation",
);
const acceptedKeyIds = [
"image-signing-2026-02",
"image-signing-2026-01",
] as const;
const policies = createPolicies([createPreset(preset)], {
acceptedKeyIds,
});
const verifierKeys = new Set<string>(acceptedKeyIds);
const verify = vi.fn(
async (request: { keyId: string }) =>
verifierKeys.has(request.keyId),
);
const runtime = createImageCdnRuntime({
policies,
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: (keyId) => verifierKeys.has(keyId),
verify,
},
});
const previous = await privateAsset();
const current = await privateAsset({
signature: {
algorithm: "ECDSA_P256_SHA256",
keyId: "image-signing-2026-02",
capabilityBindingDigestHex: "0".repeat(64),
valueBase64Url: "A".repeat(86),
},
});
await expect(
runtime.assets.acceptBackendIssued(previous),
).resolves.toMatchObject({ ok: true });
await expect(
runtime.assets.acceptBackendIssued(current),
).resolves.toMatchObject({ ok: true });
expect(verify.mock.calls.map(([request]) => request.keyId)).toEqual(
["image-signing-2026-01", "image-signing-2026-02"],
);
const unknown = await privateAsset({
signature: {
algorithm: "ECDSA_P256_SHA256",
keyId: "image-signing-2027-01",
capabilityBindingDigestHex: "0".repeat(64),
valueBase64Url: "A".repeat(86),
},
});
await expect(
runtime.assets.acceptBackendIssued(unknown),
).resolves.toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
expect(verify).toHaveBeenCalledTimes(2);
expect(() =>
createImageCdnRuntime({
policies,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: (keyId) =>
keyId === "image-signing-2026-02",
verify: vi.fn().mockResolvedValue(true),
},
}),
).toThrow(/registry does not cover/u);
expect(() =>
createImageCdnRuntime({
policies,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey() {
throw new TypeError("registry unavailable");
},
verify: vi.fn().mockResolvedValue(true),
},
}),
).toThrow(/registry does not cover/u);
});
it("binds a private descriptor to its signed snapshot and exact preset", async () => {
const preset = imageCdnPresetReference(
"card-landscape",
"render-private-product-image",
);
const policies = createPolicies([
createPreset(preset, {
probeMode: "PRIMARY_REQUIRED",
}),
]);
let releaseVerification:
((value: boolean) => void) | undefined;
const verifierMethod = vi.fn(
() =>
new Promise<boolean>((resolve) => {
releaseVerification = resolve;
}),
);
const verifier: ImageCapabilityVerifier = {
acceptsKey: acceptsTestImageKey,
verify: verifierMethod,
};
let now = 1_000_000;
const runtime = createImageCdnRuntime({
policies,
now: () => now,
subtle: globalThis.crypto.subtle,
capabilityVerifier: verifier,
probe: {
async probe(request) {
return {
ok: true,
value: {
absoluteUrl: request.absoluteUrl,
mediaType: request.expectedMediaType,
encodedBytes: 1_024,
decodedWidth: request.expectedWidth,
decodedHeight: request.expectedHeight,
},
};
},
},
});
(
verifier as {
verify: ImageCapabilityVerifier["verify"];
}
).verify = vi.fn().mockResolvedValue(false);
const mutable = (await privateAsset()) as {
-readonly [Key in keyof BackendIssuedImageAsset]:
BackendIssuedImageAsset[Key];
};
const pending = runtime.assets.acceptBackendIssued(mutable);
await vi.waitFor(() => {
expect(verifierMethod).toHaveBeenCalledOnce();
});
mutable.assetId = "asset_ATTACKER";
mutable.revision = "rev_ATTACKER";
mutable.expiresAtEpochMs = 9_999_999;
releaseVerification?.(true);
const accepted = await pending;
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
const resolved = await runtime.presentation.resolve({
asset: accepted.value,
preset,
signal: new AbortController().signal,
});
expect(resolved.ok).toBe(true);
if (!resolved.ok) return;
const url = new URL(resolved.value.src);
expect(url.pathname).toContain(
"/asset_Q3x8pL/rev_a8N2kP4z",
);
expect(url.pathname).not.toContain("ATTACKER");
expect([...url.searchParams.keys()]).toEqual([
"binding",
"capability",
"dpr",
"expires",
"fit",
"format",
"height",
"key",
"preset",
"quality",
"signature",
"width",
]);
expect(resolved.value).toMatchObject({
loading: "eager",
referrerPolicy: "no-referrer",
crossOrigin: "anonymous",
delivery: {
class: "PRIVATE_SIGNED",
browserCache: "NO_STORE",
sharedCache: "FORBIDDEN",
purge: "CAPABILITY_REVOCATION_OR_EXPIRY",
expiresAtEpochMs: 1_300_000,
},
});
now = 1_275_001;
await expect(
runtime.presentation.resolve({
asset: accepted.value,
preset,
signal: new AbortController().signal,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "EXPIRED_RESOURCE" },
});
});
it("rejects invalid binding/signature and private lazy or low-priority presets", async () => {
const eager = imageCdnPresetReference(
"private-eager",
"render-private",
);
const lazy = imageCdnPresetReference(
"private-lazy",
"render-private",
);
const policies = createPolicies([
createPreset(eager, {
bindingId: "private-eager-v1",
}),
createPreset(lazy, {
bindingId: "private-lazy-v1",
loading: "lazy",
fetchPriority: "low",
}),
]);
const runtime = createImageCdnRuntime({
policies,
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: vi.fn().mockResolvedValue(true),
},
});
const issued = await privateAsset({
allowedPresetBindingIds: [
"private-eager-v1",
"private-lazy-v1",
],
});
const accepted = await runtime.assets.acceptBackendIssued(issued);
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
await expect(
runtime.presentation.resolve({
asset: accepted.value,
preset: eager,
signal: new AbortController().signal,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
await expect(
runtime.presentation.resolve({
asset: accepted.value,
preset: lazy,
signal: new AbortController().signal,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
await expect(
runtime.assets.acceptBackendIssued({
...issued,
signature: {
...issued.signature,
capabilityBindingDigestHex: "f".repeat(64),
},
}),
).resolves.toMatchObject({
ok: false,
error: { code: "INTEGRITY_FAILED" },
});
const rejectingRuntime = createImageCdnRuntime({
policies,
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: vi.fn().mockResolvedValue(false),
},
});
await expect(
rejectingRuntime.assets.acceptBackendIssued(issued),
).resolves.toMatchObject({
ok: false,
error: { code: "INTEGRITY_FAILED" },
});
});
it("bounds digest and capability verification with a runtime-owned deadline and caller abort", async () => {
const preset = imageCdnPresetReference(
"deadline",
"verify-private-capability",
);
const policies = createPolicies([createPreset(preset)]);
const issued = await privateAsset();
const observations: Record<string, unknown>[] = [];
const verifierDeadline =
manualCapabilityVerificationScheduler();
const stalledVerifier = vi.fn(
() => new Promise<boolean>(() => undefined),
);
const verifierRuntime = createImageCdnRuntime({
policies,
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: stalledVerifier,
},
capabilityVerificationScheduler:
verifierDeadline.scheduler,
observer: {
record(observation) {
observations.push(observation);
},
},
});
const verifierPending =
verifierRuntime.assets.acceptBackendIssued(issued);
await vi.waitFor(() => {
expect(stalledVerifier).toHaveBeenCalledOnce();
});
expect(verifierDeadline.delays).toEqual([
DEFAULT_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
]);
verifierDeadline.fire();
await expect(verifierPending).resolves.toMatchObject({
ok: false,
error: {
code: "UNAVAILABLE",
retryable: true,
recovery: "RETRY",
},
});
expect(verifierDeadline.clearTimeout).toHaveBeenCalledOnce();
expect(observations).toEqual([
expect.objectContaining({
operation: "IMAGE_RESOLVE",
outcome: "FAILED",
failureCode: "UNAVAILABLE",
countBucket: "ZERO",
}),
]);
const digestDeadline =
manualCapabilityVerificationScheduler();
const stalledDigest = vi.fn(
() => new Promise<ArrayBuffer>(() => undefined),
);
const digestVerifier = vi.fn().mockResolvedValue(true);
const digestRuntime = createImageCdnRuntime({
policies,
now: () => 1_000_000,
subtle: {
digest: stalledDigest as SubtleCrypto["digest"],
},
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: digestVerifier,
},
capabilityVerificationTimeoutMs:
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
capabilityVerificationScheduler: digestDeadline.scheduler,
});
const digestPending =
digestRuntime.assets.acceptBackendIssued(issued);
await vi.waitFor(() => {
expect(stalledDigest).toHaveBeenCalledOnce();
});
expect(digestDeadline.delays).toEqual([
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
]);
digestDeadline.fire();
await expect(digestPending).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
expect(digestVerifier).not.toHaveBeenCalled();
const abortDeadline =
manualCapabilityVerificationScheduler();
const abortVerifier = vi.fn(
() => new Promise<boolean>(() => undefined),
);
const abortRuntime = createImageCdnRuntime({
policies,
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: abortVerifier,
},
capabilityVerificationScheduler: abortDeadline.scheduler,
});
const controller = new AbortController();
const abortPending = abortRuntime.assets.acceptBackendIssued(
issued,
{ signal: controller.signal },
);
await vi.waitFor(() => {
expect(abortVerifier).toHaveBeenCalledOnce();
});
controller.abort();
await expect(abortPending).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
expect(abortDeadline.clearTimeout).toHaveBeenCalledOnce();
});
it("bounds concurrent capability verification and releases the slot after abort", async () => {
const preset = imageCdnPresetReference(
"verification-concurrency",
"bound-image-verification-concurrency",
);
const policies = createPolicies([createPreset(preset)], {
hardLimits: {
...hardLimits,
maxConcurrentCapabilityVerifications: 1,
},
});
let verificationAttempt = 0;
const verify = vi.fn(() => {
verificationAttempt += 1;
return verificationAttempt === 1
? new Promise<boolean>(() => undefined)
: Promise.resolve(true);
});
const runtime = createImageCdnRuntime({
policies,
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify,
},
});
const issued = await privateAsset();
const controller = new AbortController();
const first = runtime.assets.acceptBackendIssued(issued, {
signal: controller.signal,
});
await vi.waitFor(() => {
expect(verify).toHaveBeenCalledOnce();
});
await expect(
runtime.assets.acceptBackendIssued(issued),
).resolves.toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(verify).toHaveBeenCalledOnce();
controller.abort();
await expect(first).resolves.toMatchObject({
ok: false,
error: { code: "ABORTED" },
});
await expect(
runtime.assets.acceptBackendIssued(issued),
).resolves.toMatchObject({ ok: true });
expect(verify).toHaveBeenCalledTimes(2);
});
it("fails fast on invalid capability verification deadline composition", () => {
const preset = imageCdnPresetReference(
"deadline-config",
"validate-private-capability-deadline",
);
const policies = createPolicies([createPreset(preset)]);
expect(
DEFAULT_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS,
).toBe(5_000);
expect(MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS).toBe(
60_000,
);
for (const capabilityVerificationTimeoutMs of [
0,
1.5,
MAX_IMAGE_CAPABILITY_VERIFICATION_TIMEOUT_MS + 1,
]) {
expect(() =>
createImageCdnRuntime({
policies,
capabilityVerificationTimeoutMs,
}),
).toThrow(/verification timeout/u);
}
expect(() =>
createImageCdnRuntime({
policies,
capabilityVerificationScheduler: {
setTimeout: null,
clearTimeout: vi.fn(),
} as unknown as ImageCapabilityVerificationScheduler,
}),
).toThrow(/verification scheduler/u);
});
it("closes terminally and idempotently, revoking old references and rejecting every later operation", async () => {
const preset = imageCdnPresetReference(
"lifecycle",
"close-image-runtime",
);
const runtime = createImageCdnRuntime({
policies: createPolicies([createPreset(preset)]),
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: vi.fn().mockResolvedValue(true),
},
});
const accepted =
runtime.assets.acceptPublicImmutable(publicAsset());
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
await expect(
runtime.presentation.resolve({
asset: accepted.value,
preset,
signal: new AbortController().signal,
}),
).resolves.toMatchObject({ ok: true });
const acceptedPrivate =
await runtime.assets.acceptBackendIssued(
await privateAsset(),
);
expect(acceptedPrivate.ok).toBe(true);
if (!acceptedPrivate.ok) return;
runtime.close();
expect(() => runtime.close()).not.toThrow();
await expect(
runtime.presentation.resolve({
asset: accepted.value,
preset,
signal: new AbortController().signal,
}),
).resolves.toMatchObject({
ok: false,
error: {
code: "UNAVAILABLE",
retryable: false,
recovery: "NONE",
},
});
await expect(
runtime.presentation.resolve({
asset: acceptedPrivate.value,
preset,
signal: new AbortController().signal,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
expect(
runtime.assets.acceptPublicImmutable(publicAsset()),
).toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
await expect(
runtime.assets.acceptBackendIssued(await privateAsset()),
).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
});
it("close aborts stalled private verification and image probing", async () => {
const verifierPreset = imageCdnPresetReference(
"close-verifier",
"abort-image-verification",
);
const verifierDeadline =
manualCapabilityVerificationScheduler();
const stalledVerifier = vi.fn(
() => new Promise<boolean>(() => undefined),
);
const verifierRuntime = createImageCdnRuntime({
policies: createPolicies([
createPreset(verifierPreset),
]),
now: () => 1_000_000,
subtle: globalThis.crypto.subtle,
capabilityVerifier: {
acceptsKey: acceptsTestImageKey,
verify: stalledVerifier,
},
capabilityVerificationScheduler:
verifierDeadline.scheduler,
});
const verificationPending =
verifierRuntime.assets.acceptBackendIssued(
await privateAsset(),
);
await vi.waitFor(() => {
expect(stalledVerifier).toHaveBeenCalledOnce();
});
verifierRuntime.close();
await expect(verificationPending).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE", retryable: false },
});
expect(verifierDeadline.clearTimeout).toHaveBeenCalledOnce();
const probePreset = imageCdnPresetReference(
"close-probe",
"abort-image-probe",
);
const stalledProbe = vi.fn(
(_request: ImageProbeRequest) =>
new Promise<never>(() => undefined),
);
const probeRuntime = createImageCdnRuntime({
policies: createPolicies([
createPreset(probePreset, {
probeMode: "PRIMARY_REQUIRED",
}),
]),
probe: { probe: stalledProbe },
});
const accepted =
probeRuntime.assets.acceptPublicImmutable(publicAsset());
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
const caller = new AbortController();
const probePending = probeRuntime.presentation.resolve({
asset: accepted.value,
preset: probePreset,
signal: caller.signal,
});
await vi.waitFor(() => {
expect(stalledProbe).toHaveBeenCalledOnce();
});
const runtimeSignal =
stalledProbe.mock.calls[0]?.[0].signal;
expect(runtimeSignal).not.toBe(caller.signal);
probeRuntime.close();
await expect(probePending).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE", retryable: false },
});
expect(runtimeSignal?.aborted).toBe(true);
});
it("requires and validates the bounded probe receipt when the preset says so", async () => {
const preset = imageCdnPresetReference(
"probed",
"render-verified-image",
);
const policy = createPreset(preset, {
bindingId: "probed-v1",
probeMode: "PRIMARY_REQUIRED",
});
const withoutProbe = createImageCdnRuntime({
policies: createPolicies([policy]),
});
const noProbeAsset =
withoutProbe.assets.acceptPublicImmutable(publicAsset());
expect(noProbeAsset.ok).toBe(true);
if (!noProbeAsset.ok) return;
await expect(
withoutProbe.presentation.resolve({
asset: noProbeAsset.value,
preset,
signal: new AbortController().signal,
}),
).resolves.toMatchObject({
ok: false,
error: { code: "UNSUPPORTED" },
});
const probe = vi.fn(async (request: ImageProbeRequest) => ({
ok: true as const,
value: {
absoluteUrl: request.absoluteUrl,
mediaType: request.expectedMediaType,
encodedBytes: 1_024,
decodedWidth: request.expectedWidth,
decodedHeight: request.expectedHeight,
},
}));
const runtime = createImageCdnRuntime({
policies: createPolicies([policy]),
probe: { probe },
});
const accepted =
runtime.assets.acceptPublicImmutable(publicAsset());
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
const resolved = await runtime.presentation.resolve({
asset: accepted.value,
preset,
signal: new AbortController().signal,
});
expect(resolved.ok).toBe(true);
expect(probe).toHaveBeenCalledWith(
expect.objectContaining({
expectedMediaType: "image/jpeg",
expectedWidth: 640,
expectedHeight: 360,
maxEncodedBytes: 524_288,
maxDecodedBytes: 4_194_304,
delivery: "PUBLIC_IMMUTABLE",
}),
);
});
it("records only safe terminal buckets for accept, probe and resolve", async () => {
const preset = imageCdnPresetReference(
"observed",
"render-observed-image",
);
const observations: Record<string, unknown>[] = [];
const observer = {
record(observation: Record<string, unknown>) {
observations.push(observation);
},
};
const runtime = createImageCdnRuntime({
policies: createPolicies([
createPreset(preset, {
bindingId: "observed-v1",
probeMode: "PRIMARY_REQUIRED",
}),
]),
observer,
probe: {
async probe(request) {
return {
ok: true,
value: {
absoluteUrl: request.absoluteUrl,
mediaType: request.expectedMediaType,
encodedBytes: 2_048,
decodedWidth: request.expectedWidth,
decodedHeight: request.expectedHeight,
},
};
},
},
});
observer.record = () => {
throw new TypeError("mutated observer must not be observed");
};
const accepted =
runtime.assets.acceptPublicImmutable(publicAsset());
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
const resolved = await runtime.presentation.resolve({
asset: accepted.value,
preset,
signal: new AbortController().signal,
});
expect(resolved.ok).toBe(true);
expect(observations).toHaveLength(3);
for (const observation of observations) {
expect(observation).toMatchObject({
operation: "IMAGE_RESOLVE",
outcome: "SUCCEEDED",
});
expect(Object.keys(observation)).not.toEqual(
expect.arrayContaining([
"assetId",
"preset",
"capability",
"signature",
"url",
]),
);
expect(JSON.stringify(observation)).not.toContain(
"images.example.test",
);
}
expect(observations.map((value) => value.countBucket)).toEqual([
"ONE",
"ONE",
"TWO_TO_TEN",
]);
expect(observations.map((value) => value.byteBucket)).toEqual([
undefined,
"LT1MIB",
"LT1MIB",
]);
});
});
describe("browser image probe", () => {
const imageUrl =
"https://images.example.test/v1/assets/a/rev?format=png";
const request = (
overrides: Partial<ImageProbeRequest> = {},
): ImageProbeRequest => ({
absoluteUrl: imageUrl,
expectedMediaType: "image/png",
expectedWidth: 640,
expectedHeight: 360,
maxEncodedBytes: 1_024,
maxDecodedPixels: 230_400,
maxDecodedBytes: 921_600,
delivery: "PUBLIC_IMMUTABLE",
minimumPublicMaxAgeSeconds: 31_536_000,
referrerPolicy: "no-referrer",
signal: new AbortController().signal,
...overrides,
});
it("parses all supported static headers before decode and closes each bitmap", async () => {
const samples = [
{
mediaType: "image/png" as const,
bytes: pngBytes(640, 360),
},
{
mediaType: "image/jpeg" as const,
bytes: jpegBytes(640, 360),
},
{
mediaType: "image/webp" as const,
bytes: webpBytes(640, 360),
},
{
mediaType: "image/avif" as const,
bytes: avifBytes(640, 360),
},
];
const close = vi.fn();
for (const sample of samples) {
const exactUrl = imageUrl.replace(
"format=png",
`format=${sample.mediaType.slice("image/".length)}`,
);
const fetcher = vi.fn(async () =>
responseAt(exactUrl, sample.bytes, {
status: 200,
headers: publicImageHeaders(
sample.mediaType,
sample.bytes.byteLength,
),
}),
);
const probe = createBrowserImageProbe({
fetcher: fetcher as typeof fetch,
createBitmap: vi.fn(async () => ({
width: 640,
height: 360,
close,
})),
});
await expect(
probe.probe(
request({
absoluteUrl: exactUrl,
expectedMediaType: sample.mediaType,
}),
),
).resolves.toMatchObject({
ok: true,
value: {
absoluteUrl: exactUrl,
mediaType: sample.mediaType,
encodedBytes: sample.bytes.byteLength,
decodedWidth: 640,
decodedHeight: 360,
},
});
expect(fetcher).toHaveBeenCalledWith(
exactUrl,
expect.objectContaining({
credentials: "omit",
redirect: "error",
mode: "cors",
cache: "no-store",
referrerPolicy: "no-referrer",
}),
);
}
expect(close).toHaveBeenCalledTimes(samples.length);
});
it("fails closed on duplicate/conflicting cache directives and oversized bodies", async () => {
const png = pngBytes(640, 360);
const createBitmap = vi.fn(async () => ({
width: 640,
height: 360,
close: vi.fn(),
}));
for (const cacheControl of [
// BT-IMG-02. Unmatched quotes must not be unwrapped into a bare number.
'public, max-age="31536000, immutable',
'public, max-age=31536000", immutable',
'public, max-age="31536000\\", immutable',
"public, public, max-age=31536000, immutable",
"public, max-age=31536000, s-maxage=60, immutable",
"public, max-age=31536000, immutable, must-revalidate",
"public=1, max-age=31536000, immutable",
"public, max-age=31536000, immutable=true",
]) {
const probe = createBrowserImageProbe({
fetcher: (async () =>
responseAt(imageUrl, png, {
status: 200,
headers: {
"cache-control": cacheControl,
"content-type": "image/png",
},
})) as typeof fetch,
createBitmap,
});
await expect(probe.probe(request())).resolves.toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
}
const probe = createBrowserImageProbe({
fetcher: (async () =>
responseAt(imageUrl, new Uint8Array(2_048), {
status: 200,
headers: {
"cache-control":
"public, max-age=31536000, immutable",
"content-type": "image/png",
},
})) as typeof fetch,
createBitmap,
});
await expect(probe.probe(request())).resolves.toMatchObject({
ok: false,
error: { code: "LIMIT_EXCEEDED" },
});
expect(createBitmap).not.toHaveBeenCalled();
});
it("rejects non-identity content encoding and mismatched declared lengths", async () => {
const png = pngBytes(640, 360);
const createBitmap = vi.fn();
const cases = [
{
headers: {
"content-encoding": "gzip",
"content-length": String(png.byteLength),
},
code: "POLICY_REJECTED",
},
{
headers: {
"content-length": String(png.byteLength + 1),
},
code: "INTEGRITY_FAILED",
},
];
for (const invalid of cases) {
const headers = publicImageHeaders("image/png");
for (const [name, value] of Object.entries(invalid.headers)) {
headers.set(name, value);
}
const probe = createBrowserImageProbe({
fetcher: (async () =>
responseAt(imageUrl, png, {
status: 200,
headers,
})) as typeof fetch,
createBitmap,
});
await expect(probe.probe(request())).resolves.toMatchObject({
ok: false,
error: { code: invalid.code },
});
}
expect(createBitmap).not.toHaveBeenCalled();
});
it("rejects malicious dimensions and animated PNG/WebP before native decode", async () => {
const createBitmap = vi.fn(async () => ({
width: 640,
height: 360,
close: vi.fn(),
}));
const cases = [
{
bytes: pngBytes(20_000, 20_000),
mediaType: "image/png" as const,
code: "LIMIT_EXCEEDED",
},
{
bytes: pngBytes(320, 180),
mediaType: "image/png" as const,
code: "INTEGRITY_FAILED",
},
{
bytes: jpegBytes(320, 180),
mediaType: "image/jpeg" as const,
code: "INTEGRITY_FAILED",
},
{
bytes: webpBytes(10_000, 10_000),
mediaType: "image/webp" as const,
code: "LIMIT_EXCEEDED",
},
{
bytes: avifBytes(20_000, 20_000),
mediaType: "image/avif" as const,
code: "LIMIT_EXCEEDED",
},
{
bytes: pngBytes(640, 360, true),
mediaType: "image/png" as const,
code: "INTEGRITY_FAILED",
},
{
bytes: webpBytes(640, 360, true),
mediaType: "image/webp" as const,
code: "INTEGRITY_FAILED",
},
{
bytes: avifBytes(640, 360, "avis"),
mediaType: "image/avif" as const,
code: "INTEGRITY_FAILED",
},
];
for (const malicious of cases) {
const url = imageUrl.replace(
"format=png",
`format=${malicious.mediaType.slice("image/".length)}`,
);
const probe = createBrowserImageProbe({
fetcher: (async () =>
responseAt(url, malicious.bytes, {
status: 200,
headers: publicImageHeaders(
malicious.mediaType,
malicious.bytes.byteLength,
),
})) as typeof fetch,
createBitmap,
});
await expect(
probe.probe(
request({
absoluteUrl: url,
expectedMediaType: malicious.mediaType,
}),
),
).resolves.toMatchObject({
ok: false,
error: { code: malicious.code },
});
}
expect(createBitmap).not.toHaveBeenCalled();
});
it("enforces private no-store, omitted credentials and the exact final URL", async () => {
const png = pngBytes(640, 360);
const fetcher = vi.fn(async () =>
responseAt(imageUrl, png, {
status: 200,
headers: {
"cache-control": "private, no-store",
"content-type": "image/png",
},
}),
);
const probe = createBrowserImageProbe({
fetcher: fetcher as typeof fetch,
createBitmap: async () => ({
width: 640,
height: 360,
close: vi.fn(),
}),
});
await expect(
probe.probe(
request({
delivery: "PRIVATE_SIGNED",
minimumPublicMaxAgeSeconds: 0,
}),
),
).resolves.toMatchObject({ ok: true });
expect(fetcher).toHaveBeenCalledWith(
imageUrl,
expect.objectContaining({
cache: "no-store",
credentials: "omit",
redirect: "error",
}),
);
for (const response of [
responseAt(imageUrl, png, {
status: 200,
headers: {
"cache-control": "private, no-store=value",
"content-type": "image/png",
},
}),
responseAt(imageUrl, png, {
status: 200,
headers: {
"cache-control": "public, no-store",
"content-type": "image/png",
},
}),
responseAt(
"https://images.example.test/v1/assets/other",
png,
{
status: 200,
headers: {
"cache-control": "private, no-store",
"content-type": "image/png",
},
},
),
]) {
const rejectingProbe = createBrowserImageProbe({
fetcher: (async () => response) as typeof fetch,
createBitmap: async () => ({
width: 640,
height: 360,
close: vi.fn(),
}),
});
await expect(
rejectingProbe.probe(
request({
delivery: "PRIVATE_SIGNED",
minimumPublicMaxAgeSeconds: 0,
}),
),
).resolves.toMatchObject({
ok: false,
error: { code: "POLICY_REJECTED" },
});
}
await expect(
probe.probe(
request({
expectedMediaType: "image/svg+xml",
} as unknown as Partial<ImageProbeRequest>),
),
).resolves.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);
const releaseLock = vi.fn();
const read = vi.fn(
() =>
new Promise<ReadableStreamReadResult<Uint8Array>>(
() => undefined,
),
);
const response = {
body: {
getReader: () => ({ cancel, read, releaseLock }),
},
headers: publicImageHeaders("image/png"),
ok: true,
redirected: false,
status: 200,
type: "cors",
url: imageUrl,
} as unknown as Response;
const fetcher = vi.fn(
async (_input: RequestInfo | URL, _init?: RequestInit) =>
response,
);
const probe = createBrowserImageProbe({
fetcher: fetcher as typeof fetch,
createBitmap: vi.fn(),
timeoutMs: 1_000,
scheduler: manual.scheduler,
});
const probeRequest = request();
const pending = probe.probe(probeRequest);
await vi.waitFor(() => {
expect(read).toHaveBeenCalledOnce();
});
const composedSignal = fetcher.mock.calls[0]?.[1]?.signal as
| AbortSignal
| null
| undefined;
expect(composedSignal).not.toBe(probeRequest.signal);
manual.fire();
await expect(pending).resolves.toMatchObject({
ok: false,
error: {
code: "UNAVAILABLE",
retryable: true,
recovery: "RETRY",
},
});
expect(cancel).toHaveBeenCalledOnce();
expect(releaseLock).toHaveBeenCalledOnce();
expect(composedSignal?.aborted).toBe(true);
});
it("times out stalled decode and closes a bitmap that resolves late", async () => {
const manual = manualImageProbeScheduler();
const close = vi.fn();
let finishDecode:
((bitmap: {
width: number;
height: number;
close(): void;
}) => void) | undefined;
const createBitmap = vi.fn(
() =>
new Promise<{
width: number;
height: number;
close(): void;
}>((resolve) => {
finishDecode = resolve;
}),
);
const png = pngBytes(640, 360);
const probe = createBrowserImageProbe({
fetcher: (async () =>
responseAt(imageUrl, png, {
status: 200,
headers: publicImageHeaders("image/png", png.byteLength),
})) as typeof fetch,
createBitmap,
timeoutMs: 1_000,
scheduler: manual.scheduler,
});
const pending = probe.probe(request());
await vi.waitFor(() => {
expect(createBitmap).toHaveBeenCalledOnce();
});
manual.fire();
await expect(pending).resolves.toMatchObject({
ok: false,
error: { code: "UNAVAILABLE" },
});
finishDecode?.({ width: 640, height: 360, close });
await vi.waitFor(() => {
expect(close).toHaveBeenCalledOnce();
});
});
});
describe("P-256 image capability verifier", () => {
it("rejects an ECDSA public key on any curve other than P-256", async () => {
const generated = await globalThis.crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-384" },
false,
["sign", "verify"],
);
if (!("publicKey" in generated)) {
throw new TypeError("Expected an ECDSA key pair.");
}
expect(() =>
createP256ImageCapabilityVerifier({
subtle: globalThis.crypto.subtle,
publicKeys: [
{
keyId: "image-signing-wrong-curve",
key: generated.publicKey,
},
],
}),
).toThrow(/public key binding/u);
});
it("verifies the exact canonical payload and rejects tampering", async () => {
const generated = await globalThis.crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" },
false,
["sign", "verify"],
);
if (!("privateKey" in generated)) {
throw new TypeError("Expected an ECDSA key pair.");
}
const payload = new TextEncoder().encode(
'["image-cdn-capability-v1","bound"]',
);
const payloadBuffer = new Uint8Array(payload.byteLength);
payloadBuffer.set(payload);
const signature = await globalThis.crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-256" },
generated.privateKey,
payloadBuffer.buffer,
);
const verifier = createP256ImageCapabilityVerifier({
subtle: globalThis.crypto.subtle,
publicKeys: [
{
keyId: "image-signing-2026-01",
key: generated.publicKey,
},
],
});
expect(verifier.acceptsKey("image-signing-2026-01")).toBe(
true,
);
expect(verifier.acceptsKey("image-signing-unknown")).toBe(
false,
);
const signatureBase64Url = base64Url(
new Uint8Array(signature),
);
await expect(
verifier.verify({
algorithm: "ECDSA_P256_SHA256",
keyId: "image-signing-2026-01",
canonicalPayload: payload,
signatureBase64Url,
}),
).resolves.toBe(true);
const tampered = Uint8Array.from(payload);
tampered[0] ^= 1;
await expect(
verifier.verify({
algorithm: "ECDSA_P256_SHA256",
keyId: "image-signing-2026-01",
canonicalPayload: tampered,
signatureBase64Url,
}),
).resolves.toBe(false);
});
});
function responseAt(
url: string,
body: Uint8Array,
init: ResponseInit,
): Response {
const responseBytes = new Uint8Array(body.byteLength);
responseBytes.set(body);
const response = new Response(responseBytes.buffer, init);
Object.defineProperty(response, "url", {
configurable: false,
enumerable: true,
value: url,
});
return response;
}
function publicImageHeaders(
mediaType: string,
contentLength?: number,
): Headers {
const headers = new Headers({
"cache-control":
"public, max-age=31536000, s-maxage=31536000, immutable",
"content-type": mediaType,
vary: "Accept-Encoding",
});
if (contentLength !== undefined) {
headers.set("content-length", String(contentLength));
}
return headers;
}
function pngBytes(
width: number,
height: number,
animated = false,
): Uint8Array {
const header = new Uint8Array(13);
const headerView = new DataView(header.buffer);
headerView.setUint32(0, width);
headerView.setUint32(4, height);
header[8] = 8;
header[9] = 6;
return concatenateBytes([
Uint8Array.from([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
]),
pngChunk("IHDR", header),
...(animated
? [pngChunk("acTL", new Uint8Array(8))]
: []),
pngChunk("IDAT", new Uint8Array()),
pngChunk("IEND", new Uint8Array()),
]);
}
function pngChunk(type: string, payload: Uint8Array): Uint8Array {
const chunk = new Uint8Array(12 + payload.byteLength);
const view = new DataView(chunk.buffer);
view.setUint32(0, payload.byteLength);
writeAscii(chunk, 4, type);
chunk.set(payload, 8);
return chunk;
}
function jpegBytes(width: number, height: number): Uint8Array {
return Uint8Array.from([
0xff,
0xd8,
0xff,
0xc0,
0x00,
0x11,
0x08,
(height >>> 8) & 0xff,
height & 0xff,
(width >>> 8) & 0xff,
width & 0xff,
0x03,
0x01,
0x11,
0x00,
0x02,
0x11,
0x00,
0x03,
0x11,
0x00,
0xff,
0xda,
]);
}
function webpBytes(
width: number,
height: number,
animated = false,
): Uint8Array {
const chunkType = animated ? "VP8X" : "VP8 ";
const payload = new Uint8Array(10);
if (animated) {
payload[0] = 0x02;
writeUint24LittleEndian(payload, 4, width - 1);
writeUint24LittleEndian(payload, 7, height - 1);
} else {
payload.set([0x9d, 0x01, 0x2a], 3);
const view = new DataView(payload.buffer);
view.setUint16(6, width, true);
view.setUint16(8, height, true);
}
const chunk = concatenateBytes([
asciiBytes(chunkType),
littleEndianUint32(payload.byteLength),
payload,
]);
return concatenateBytes([
asciiBytes("RIFF"),
littleEndianUint32(4 + chunk.byteLength),
asciiBytes("WEBP"),
chunk,
]);
}
function avifBytes(
width: number,
height: number,
brand = "avif",
): Uint8Array {
const fileType = isoBox(
"ftyp",
concatenateBytes([
asciiBytes(brand),
new Uint8Array(4),
asciiBytes(brand),
]),
);
const spatialExtent = new Uint8Array(12);
const extentView = new DataView(spatialExtent.buffer);
extentView.setUint32(4, width);
extentView.setUint32(8, height);
const primaryItem = new Uint8Array(6);
new DataView(primaryItem.buffer).setUint16(4, 1);
const itemInfoEntry = new Uint8Array(13);
itemInfoEntry[0] = 2;
const itemInfoView = new DataView(itemInfoEntry.buffer);
itemInfoView.setUint16(4, 1);
writeAscii(itemInfoEntry, 8, "av01");
const itemInfo = new Uint8Array(6);
new DataView(itemInfo.buffer).setUint16(4, 1);
const propertyAssociation = new Uint8Array(12);
const associationView = new DataView(
propertyAssociation.buffer,
);
associationView.setUint32(4, 1);
associationView.setUint16(8, 1);
propertyAssociation[10] = 1;
propertyAssociation[11] = 0x81;
const properties = isoBox(
"iprp",
concatenateBytes([
isoBox("ipco", isoBox("ispe", spatialExtent)),
isoBox("ipma", propertyAssociation),
]),
);
const metadata = isoBox(
"meta",
concatenateBytes([
new Uint8Array(4),
isoBox("pitm", primaryItem),
isoBox(
"iinf",
concatenateBytes([
itemInfo,
isoBox("infe", itemInfoEntry),
]),
),
properties,
]),
);
return concatenateBytes([
fileType,
metadata,
isoBox("mdat", Uint8Array.of(0)),
]);
}
function isoBox(type: string, payload: Uint8Array): Uint8Array {
const box = new Uint8Array(8 + payload.byteLength);
const view = new DataView(box.buffer);
view.setUint32(0, box.byteLength);
writeAscii(box, 4, type);
box.set(payload, 8);
return box;
}
function littleEndianUint32(value: number): Uint8Array {
const bytes = new Uint8Array(4);
new DataView(bytes.buffer).setUint32(0, value, true);
return bytes;
}
function writeUint24LittleEndian(
bytes: Uint8Array,
offset: number,
value: number,
): void {
bytes[offset] = value & 0xff;
bytes[offset + 1] = (value >>> 8) & 0xff;
bytes[offset + 2] = (value >>> 16) & 0xff;
}
function asciiBytes(value: string): Uint8Array {
return Uint8Array.from(
[...value].map((character) => character.charCodeAt(0)),
);
}
function writeAscii(
target: Uint8Array,
offset: number,
value: string,
): void {
target.set(asciiBytes(value), offset);
}
function concatenateBytes(
chunks: readonly Uint8Array[],
): Uint8Array {
const combined = new Uint8Array(
chunks.reduce((total, chunk) => total + chunk.byteLength, 0),
);
let offset = 0;
for (const chunk of chunks) {
combined.set(chunk, offset);
offset += chunk.byteLength;
}
return combined;
}
function manualImageProbeScheduler(): Readonly<{
scheduler: ImageProbeScheduler;
fire(): void;
}> {
let callback: (() => void) | undefined;
return {
scheduler: {
setTimeout(nextCallback) {
callback = nextCallback;
return 1;
},
clearTimeout: vi.fn(),
},
fire() {
if (!callback) {
throw new TypeError("No image probe timeout is scheduled.");
}
callback();
},
};
}
function manualCapabilityVerificationScheduler(): Readonly<{
scheduler: ImageCapabilityVerificationScheduler;
delays: readonly number[];
clearTimeout: ReturnType<typeof vi.fn>;
fire(): void;
}> {
let callback: (() => void) | undefined;
const delays: number[] = [];
const clearTimeout = vi.fn();
return {
scheduler: {
setTimeout(nextCallback, milliseconds) {
callback = nextCallback;
delays.push(milliseconds);
return 1;
},
clearTimeout,
},
delays,
clearTimeout,
fire() {
if (!callback) {
throw new TypeError(
"No capability verification timeout is scheduled.",
);
}
callback();
},
};
}
function base64Url(bytes: Uint8Array): string {
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary)
.replace(/\+/gu, "-")
.replace(/\//gu, "_")
.replace(/=+$/gu, "");
}