The product was materialized from the template at `4dc033c` and has stayed on it through 43 template commits, so it was missing all three rounds of adapter remediation — including files it never had, such as the shared `abortable-operation` primitive and the `exact-snapshot` decoder that later fixes are written against. Taking only the newest round was not possible for that reason: the delta is coherent only as a whole. The product had not touched `src/adapters` at all since materialization, so the 140-file delta applied with a three-way merge and no conflicts. `package.json` was the single overlap and merged cleanly: the product owns `name`, the template contributed `check:adapter-inventory`, `check:remediation-ledger` and the image-resolve-signal type fixture. All 24 product-owned files — README, index.html, CI workflow, i18n catalog, home page, generated schemas, evidence scripts, component and visual snapshots — are byte-identical to `main`. `template.lock.json` now pins the synced revision and tree. Verified in this repository, not inherited from the template: six type projects, lint, nine gates (adapter inventory, remediation ledger, registries, diagnostics, realtime boundaries, architecture, browser file/storage boundaries, optional recipes, documentation), the production build, and 2,054 of 2,073 tests. The 19 failures are all in `tests/unit/ci-artifact-contract.test.ts` and are the same pre-existing sandbox RLIMIT, EMFILE, umask and `/tmp` permission behaviour the template records; four suites that failed once under parallel load pass in isolation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2483 lines
77 KiB
TypeScript
2483 lines
77 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { PRESIGNED_TRANSFER_PROTOCOL } from "../../src/application/ports/browser-transfer/presigned-transfer.ts";
|
|
|
|
import type {
|
|
PresignedDownloadCapability,
|
|
PresignedDownloadByteSource,
|
|
} from "../../src/application/ports/browser-transfer/presigned-transfer.ts";
|
|
import { RESUMABLE_UPLOAD_PROTOCOL } from "../../src/application/ports/browser-transfer/resumable-upload.ts";
|
|
import type { BrowserDataObservation } from "../../src/application/ports/browser-file-storage/shared.ts";
|
|
import {
|
|
createPresignedCapabilityVault,
|
|
createSingleUsePresignedReplayGuard,
|
|
} from "../../src/adapters/browser-transfer/presigned/presigned-capability-vault.ts";
|
|
import { createPresignedCapabilityHttpProvider } from "../../src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts";
|
|
import {
|
|
createPresignedTransferExecutor,
|
|
type PresignedTransferExecutorOptions,
|
|
} from "../../src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts";
|
|
import { sha256Hex } from "../../src/adapters/browser-transfer/presigned/incremental-sha256.ts";
|
|
import {
|
|
BrowserFilePolicyRegistry,
|
|
browserFilePolicyReference,
|
|
} from "../../src/adapters/browser-files/browser-file-policy-registry.ts";
|
|
import {
|
|
createDownloadDeliveryAdapter,
|
|
type SaveFileHandle,
|
|
} from "../../src/adapters/browser-files/download-delivery-adapter.ts";
|
|
|
|
const NOW = 1_000_000;
|
|
const CONTROL_ENDPOINT = "https://api.example/capabilities";
|
|
const DATA_ORIGIN = "https://objects.example";
|
|
const DOWNLOAD_PATH = "/files/resource-1";
|
|
const DOWNLOAD_HREF =
|
|
`${DATA_ORIGIN}${DOWNLOAD_PATH}?sig=do-not-log-this`;
|
|
const POLICY_HEADER = "x-policy-version";
|
|
const DIGEST_HEADER = "x-content-sha256";
|
|
const CHECKSUM_HEADER = "x-checksum-sha256";
|
|
const UPLOAD_SESSION_ID = "upload-session-1";
|
|
const REQUEST_BINDING_SHA256 = "c".repeat(64);
|
|
|
|
function downloadCapabilityPayload(
|
|
bytes: Uint8Array,
|
|
overrides: Readonly<Record<string, unknown>> = {},
|
|
) {
|
|
const digest = sha256Hex(bytes);
|
|
return {
|
|
// BT-PRE-02. Every capability envelope carries the top-level protocol.
|
|
protocol: PRESIGNED_TRANSFER_PROTOCOL,
|
|
capabilityReceipt: "capability-download-1",
|
|
method: "GET",
|
|
binding: {
|
|
kind: "DOWNLOAD",
|
|
resourceId: "resource-1",
|
|
},
|
|
href: DOWNLOAD_HREF,
|
|
origin: DATA_ORIGIN,
|
|
path: DOWNLOAD_PATH,
|
|
allowedQueryParameters: ["sig"],
|
|
requestHeaders: [
|
|
{ name: "accept", value: "application/octet-stream" },
|
|
],
|
|
requiredResponseHeaders: [
|
|
{ name: POLICY_HEADER, value: "v1" },
|
|
],
|
|
digestRequestHeader: null,
|
|
digestResponseHeader: DIGEST_HEADER,
|
|
receiptResponseHeader: null,
|
|
expectedStatus: 200,
|
|
expectedResponseByteLength: null,
|
|
mediaType: "application/octet-stream",
|
|
byteLength: bytes.byteLength,
|
|
maxBytes: 64,
|
|
expectedSha256: digest,
|
|
expiresAtEpochMs: NOW + 30_000,
|
|
singleUse: true,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
type CapabilityPayload = ReturnType<typeof downloadCapabilityPayload>;
|
|
|
|
function uploadCapabilityPayload(input: Readonly<{
|
|
bytes: Uint8Array;
|
|
checksum: string;
|
|
}>, overrides: Readonly<Record<string, unknown>> = {}) {
|
|
return {
|
|
protocol: PRESIGNED_TRANSFER_PROTOCOL,
|
|
capabilityReceipt: "capability-upload-1",
|
|
method: "PUT",
|
|
binding: {
|
|
kind: "UPLOAD_PART",
|
|
protocol: RESUMABLE_UPLOAD_PROTOCOL,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
idempotencyKey: "part-attempt-1",
|
|
},
|
|
href: `${DATA_ORIGIN}/uploads/session-1/part-1?sig=secret`,
|
|
origin: DATA_ORIGIN,
|
|
path: "/uploads/session-1/part-1",
|
|
allowedQueryParameters: ["sig"],
|
|
requestHeaders: [
|
|
{ name: "content-type", value: "application/octet-stream" },
|
|
{ name: CHECKSUM_HEADER, value: input.checksum },
|
|
],
|
|
requiredResponseHeaders: [
|
|
{ name: POLICY_HEADER, value: "v1" },
|
|
],
|
|
digestRequestHeader: CHECKSUM_HEADER,
|
|
digestResponseHeader: null,
|
|
receiptResponseHeader: "etag",
|
|
expectedStatus: 200,
|
|
expectedResponseByteLength: 0,
|
|
mediaType: "application/octet-stream",
|
|
byteLength: input.bytes.byteLength,
|
|
maxBytes: 64,
|
|
expectedSha256: input.checksum,
|
|
expiresAtEpochMs: NOW + 30_000,
|
|
singleUse: true,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function jsonResponse(
|
|
value: unknown,
|
|
url = CONTROL_ENDPOINT,
|
|
): Response {
|
|
return responseWithUrl(
|
|
new Response(JSON.stringify(value), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
url,
|
|
);
|
|
}
|
|
|
|
function downloadResponse(
|
|
body: BodyInit | null,
|
|
payload: CapabilityPayload,
|
|
headers: Record<string, string> = {},
|
|
): Response {
|
|
return responseWithUrl(
|
|
new Response(body, {
|
|
status: payload.expectedStatus as number,
|
|
headers: {
|
|
"Content-Type": String(payload.mediaType),
|
|
"Content-Length": String(payload.byteLength),
|
|
[DIGEST_HEADER]: String(payload.expectedSha256),
|
|
[POLICY_HEADER]: "v1",
|
|
...headers,
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}
|
|
|
|
function responseWithUrl(response: Response, href: string): Response {
|
|
Object.defineProperty(response, "url", {
|
|
configurable: true,
|
|
value: href,
|
|
});
|
|
return response;
|
|
}
|
|
|
|
function createHarness(input: Readonly<{
|
|
fetcher: typeof fetch;
|
|
maxActiveCapabilities?: number;
|
|
now?: () => number;
|
|
digestBytes?: PresignedTransferExecutorOptions["digestBytes"];
|
|
scheduler?: PresignedTransferExecutorOptions["scheduler"];
|
|
observer?: Readonly<{
|
|
record(observation: BrowserDataObservation): void;
|
|
}>;
|
|
}>) {
|
|
const now = input.now ?? (() => NOW);
|
|
const vault = createPresignedCapabilityVault({
|
|
maxActiveCapabilities: input.maxActiveCapabilities ?? 16,
|
|
now,
|
|
});
|
|
const replayGuard = createSingleUsePresignedReplayGuard();
|
|
const provider = createPresignedCapabilityHttpProvider({
|
|
endpoint: CONTROL_ENDPOINT,
|
|
vault,
|
|
allowedDataOrigins: [DATA_ORIGIN],
|
|
allowedDataPathPrefixes: ["/files/", "/uploads/"],
|
|
allowedQueryParameters: ["sig"],
|
|
allowedRequestHeaders: [
|
|
"accept",
|
|
"content-type",
|
|
CHECKSUM_HEADER,
|
|
],
|
|
allowedResponseHeaders: [
|
|
POLICY_HEADER,
|
|
DIGEST_HEADER,
|
|
"etag",
|
|
],
|
|
hardMaxTransferBytes: 64,
|
|
hardMaxUploadResponseBytes: 16,
|
|
maxCapabilityTtlMs: 60_000,
|
|
minimumRemainingLifetimeMs: 1_000,
|
|
timeoutMs: 5_000,
|
|
fetcher: input.fetcher,
|
|
now,
|
|
scheduler: input.scheduler,
|
|
observer: input.observer,
|
|
});
|
|
const executor = createPresignedTransferExecutor({
|
|
vault,
|
|
replayGuard,
|
|
hardMaxTransferBytes: 64,
|
|
hardMaxChunkBytes: 2,
|
|
hardMaxUploadResponseBytes: 16,
|
|
minimumRemainingLifetimeMs: 1_000,
|
|
timeoutMs: 5_000,
|
|
fetcher: input.fetcher,
|
|
now,
|
|
scheduler: input.scheduler,
|
|
digestBytes: input.digestBytes,
|
|
observer: input.observer,
|
|
});
|
|
return { provider, executor, vault };
|
|
}
|
|
|
|
async function collect(
|
|
source: PresignedDownloadByteSource,
|
|
signal = new AbortController().signal,
|
|
) {
|
|
const results = [];
|
|
for await (const result of source.stream(signal)) {
|
|
results.push(result);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
describe("presigned transfer", () => {
|
|
it("matches the standard SHA-256 vector", () => {
|
|
expect(sha256Hex(new TextEncoder().encode("abc"))).toBe(
|
|
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
|
);
|
|
});
|
|
|
|
it("keeps URL and headers adapter-private and streams bounded verified chunks", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3, 4, 5]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const dataCalls: RequestInit[] = [];
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
if (String(input) === CONTROL_ENDPOINT) return jsonResponse(payload);
|
|
dataCalls.push(init ?? {});
|
|
return downloadResponse(bytes.slice().buffer, payload);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
|
|
const issued = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(Object.isFrozen(issued.value)).toBe(true);
|
|
expect("href" in issued.value).toBe(false);
|
|
expect("requestHeaders" in issued.value).toBe(false);
|
|
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
const results = await collect(opened.value);
|
|
expect(results.every((result) => result.ok)).toBe(true);
|
|
expect(
|
|
results.flatMap((result) =>
|
|
result.ok ? [...result.value] : [],
|
|
),
|
|
).toEqual([...bytes]);
|
|
expect(
|
|
results.map((result) =>
|
|
result.ok ? result.value.byteLength : 0,
|
|
),
|
|
).toEqual([2, 2, 1]);
|
|
expect(dataCalls[0]).toMatchObject({
|
|
method: "GET",
|
|
credentials: "omit",
|
|
redirect: "error",
|
|
referrerPolicy: "no-referrer",
|
|
cache: "no-store",
|
|
});
|
|
|
|
expect(
|
|
await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "POLICY_REJECTED",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("retires consumed identities immediately and reuses the active slot before TTL", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
const responsePayload = downloadCapabilityPayload(bytes);
|
|
let issueSequence = 0;
|
|
const fetcher = vi.fn(
|
|
async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
issueSequence += 1;
|
|
return jsonResponse(
|
|
downloadCapabilityPayload(bytes, {
|
|
capabilityReceipt: `capability-download-${issueSequence}`,
|
|
}),
|
|
);
|
|
}
|
|
return downloadResponse(
|
|
bytes.slice().buffer,
|
|
responsePayload,
|
|
);
|
|
},
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor, vault } = createHarness({
|
|
fetcher,
|
|
maxActiveCapabilities: 1,
|
|
});
|
|
const signal = new AbortController().signal;
|
|
|
|
const first = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal,
|
|
});
|
|
expect(first.ok).toBe(true);
|
|
if (!first.ok) return;
|
|
expect(
|
|
await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "LIMIT_EXCEEDED" },
|
|
});
|
|
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: first.value,
|
|
signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
expect(vault.resolve(first.value)).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
expect((await collect(opened.value)).every((result) => result.ok)).toBe(
|
|
true,
|
|
);
|
|
|
|
const afterConsume = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal,
|
|
});
|
|
expect(afterConsume.ok).toBe(true);
|
|
if (!afterConsume.ok) return;
|
|
vault.revoke(afterConsume.value);
|
|
expect(vault.resolve(afterConsume.value)).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
|
|
const afterRevoke = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal,
|
|
});
|
|
expect(afterRevoke.ok).toBe(true);
|
|
if (!afterRevoke.ok) return;
|
|
vault.dispose();
|
|
expect(vault.resolve(afterRevoke.value)).toMatchObject({
|
|
ok: false,
|
|
error: { code: "UNAVAILABLE" },
|
|
});
|
|
});
|
|
|
|
it("snapshots the issuance request before asynchronous mutation", async () => {
|
|
const bytes = new Uint8Array([1]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
let release: (() => void) | undefined;
|
|
const gate = new Promise<void>((resolve) => {
|
|
release = resolve;
|
|
});
|
|
let posted: unknown;
|
|
const fetcher = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
posted = JSON.parse(String(init?.body)) as unknown;
|
|
await gate;
|
|
return jsonResponse(payload);
|
|
}) as unknown as typeof fetch;
|
|
const { provider } = createHarness({ fetcher });
|
|
const request = {
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
};
|
|
|
|
const pending = provider.issueDownload(request);
|
|
request.resourceId = "mutated-resource";
|
|
release?.();
|
|
expect(await pending).toMatchObject({ ok: true });
|
|
expect(posted).toMatchObject({
|
|
binding: { resourceId: "resource-1" },
|
|
});
|
|
});
|
|
|
|
it("rejects a capability BFF response from a different final URL", async () => {
|
|
const bytes = new Uint8Array([1]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const fetcher = vi.fn(async () =>
|
|
jsonResponse(
|
|
payload,
|
|
"https://api.example/retargeted-capabilities",
|
|
),
|
|
) as unknown as typeof fetch;
|
|
const { provider } = createHarness({ fetcher });
|
|
|
|
expect(
|
|
await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
});
|
|
|
|
it("snapshots multipart session bindings before capability issuance awaits", async () => {
|
|
const bytes = new Uint8Array([1, 2]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload({ bytes, checksum });
|
|
let release: (() => void) | undefined;
|
|
const gate = new Promise<void>((resolve) => {
|
|
release = resolve;
|
|
});
|
|
let posted: unknown;
|
|
const fetcher = vi.fn(
|
|
async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
posted = JSON.parse(String(init?.body)) as unknown;
|
|
await gate;
|
|
return jsonResponse(payload);
|
|
},
|
|
) as unknown as typeof fetch;
|
|
const { provider } = createHarness({ fetcher });
|
|
const request = {
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
};
|
|
|
|
const pending = provider.issueUploadPart(request);
|
|
request.sessionId = "mutated-session";
|
|
request.requestBindingSha256 = "d".repeat(64);
|
|
release?.();
|
|
|
|
expect(await pending).toMatchObject({
|
|
ok: true,
|
|
value: {
|
|
binding: {
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
},
|
|
},
|
|
});
|
|
expect(posted).toMatchObject({
|
|
binding: {
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
},
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
"protocol",
|
|
"sessionId",
|
|
"requestBindingSha256",
|
|
] as const)(
|
|
"rejects a capability response with a mismatched %s",
|
|
async (field) => {
|
|
const bytes = new Uint8Array([1, 2]);
|
|
const checksum = sha256Hex(bytes);
|
|
const valid = uploadCapabilityPayload({ bytes, checksum });
|
|
const payload = uploadCapabilityPayload(
|
|
{ bytes, checksum },
|
|
{
|
|
binding: {
|
|
...valid.binding,
|
|
[field]:
|
|
field === "protocol"
|
|
? "PRESIGNED_MULTIPART_V2"
|
|
: field === "sessionId"
|
|
? "different-session"
|
|
: "d".repeat(64),
|
|
},
|
|
},
|
|
);
|
|
const fetcher = vi.fn(async () =>
|
|
jsonResponse(payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider } = createHarness({ fetcher });
|
|
expect(
|
|
await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
},
|
|
);
|
|
|
|
it("rejects capability query, origin, path and fabricated-handle mismatches", async () => {
|
|
const bytes = new Uint8Array([1]);
|
|
const malformed = downloadCapabilityPayload(bytes, {
|
|
href: `${DOWNLOAD_HREF}&extra=1`,
|
|
});
|
|
const fetcher = vi.fn(async () => jsonResponse(malformed)) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
expect(
|
|
await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
|
|
const forged = Object.freeze({
|
|
capabilityReceipt: "forged",
|
|
method: "GET",
|
|
binding: Object.freeze({
|
|
kind: "DOWNLOAD",
|
|
resourceId: "resource-1",
|
|
}),
|
|
mediaType: "application/octet-stream",
|
|
byteLength: 1,
|
|
maxBytes: 1,
|
|
expectedSha256: "a".repeat(64),
|
|
expiresAtEpochMs: NOW + 1_000,
|
|
}) as unknown as PresignedDownloadCapability;
|
|
expect(
|
|
await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: forged,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
});
|
|
|
|
it("rejects redirected, retargeted and response-header-mismatched downloads", async () => {
|
|
const bytes = new Uint8Array([1, 2]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
let mode: "redirect" | "retarget" | "header" = "redirect";
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) return jsonResponse(payload);
|
|
const response =
|
|
mode === "header"
|
|
? downloadResponse(bytes.slice().buffer, payload, {
|
|
[DIGEST_HEADER]: "f".repeat(64),
|
|
})
|
|
: downloadResponse(bytes.slice().buffer, payload);
|
|
if (mode === "redirect") {
|
|
Object.defineProperty(response, "redirected", { value: true });
|
|
} else if (mode === "retarget") {
|
|
Object.defineProperty(response, "url", {
|
|
value: `${DATA_ORIGIN}/files/different?sig=do-not-log-this`,
|
|
});
|
|
}
|
|
return response;
|
|
}) as unknown as typeof fetch;
|
|
|
|
let harness = createHarness({ fetcher });
|
|
let issued = await harness.provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
await expect(
|
|
firstStreamResult(
|
|
await harness.executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
|
|
mode = "retarget";
|
|
harness = createHarness({ fetcher });
|
|
issued = await harness.provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
await expect(
|
|
firstStreamResult(
|
|
await harness.executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
|
|
mode = "header";
|
|
harness = createHarness({ fetcher });
|
|
issued = await harness.provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
await expect(
|
|
firstStreamResult(
|
|
await harness.executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTEGRITY_FAILED" },
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
{ label: "missing", protocol: undefined },
|
|
{ label: "V0", protocol: "PRESIGNED_TRANSFER_V0" },
|
|
{ label: "V2", protocol: "PRESIGNED_TRANSFER_V2" },
|
|
])(
|
|
"requires PRESIGNED_TRANSFER_V1 in request and response ($label)",
|
|
async ({ protocol }) => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const body =
|
|
protocol === undefined
|
|
? (({ protocol: _dropped, ...rest }) => rest)(
|
|
payload as Record<string, unknown>,
|
|
)
|
|
: { ...payload, protocol };
|
|
const requests: unknown[] = [];
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
requests.push(JSON.parse(String(init?.body)));
|
|
return jsonResponse(body as never);
|
|
}
|
|
return downloadResponse(bytes.slice().buffer, payload);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, vault } = createHarness({ fetcher });
|
|
|
|
const issued = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
|
|
// BT-PRE-02. The request always declares V1, and a response that does not
|
|
// is closed before the vault ever registers it.
|
|
expect(requests[0]).toMatchObject({
|
|
protocol: PRESIGNED_TRANSFER_PROTOCOL,
|
|
});
|
|
expect(issued).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
// The rejected envelope never reached vault registration.
|
|
expect(vault.resolve).toBeTypeOf("function");
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
{ label: "encoded slash", path: "/files/a%2Fb" },
|
|
{ label: "encoded backslash", path: "/files/a%5Cb" },
|
|
{ label: "double-encoded dot segment", path: "/files/%252e%252e" },
|
|
{ label: "lowercase percent-hex", path: "/files/a%c3%a9" },
|
|
{ label: "encoded NUL", path: "/files/a%00b" },
|
|
])("rejects a provider path that can decode again ($label)", async ({ path }) => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
const payload = downloadCapabilityPayload(bytes, {
|
|
path,
|
|
href: `${DATA_ORIGIN}${path}`,
|
|
});
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: downloadResponse(bytes.slice().buffer, payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider } = createHarness({ fetcher });
|
|
|
|
await expect(
|
|
provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
});
|
|
|
|
it("accepts a valid opaque UTF-8 path segment", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
// BT-PRE-05. Canonical uppercase percent-hex for a real UTF-8 segment.
|
|
const path = `/files/${encodeURIComponent("caf\u00e9")}`;
|
|
const payload = downloadCapabilityPayload(bytes, {
|
|
path,
|
|
href: `${DATA_ORIGIN}${path}?sig=do-not-log-this`,
|
|
});
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: downloadResponse(bytes.slice().buffer, payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider } = createHarness({ fetcher });
|
|
|
|
await expect(
|
|
provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).resolves.toMatchObject({ ok: true });
|
|
});
|
|
|
|
it("bounds a fetch that ignores its abort signal and cancels the late body", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const cancel = vi.fn(async () => {});
|
|
let releaseControl: ((response: Response) => void) | undefined;
|
|
const timers: Array<() => void> = [];
|
|
const fetcher = vi.fn(
|
|
async () =>
|
|
await new Promise<Response>((resolve) => {
|
|
releaseControl = resolve;
|
|
}),
|
|
) as unknown as typeof fetch;
|
|
const { provider } = createHarness({
|
|
fetcher,
|
|
scheduler: {
|
|
setTimeout: (callback: () => void) => {
|
|
timers.push(callback);
|
|
return timers.length;
|
|
},
|
|
clearTimeout: () => {},
|
|
},
|
|
});
|
|
|
|
const issuing = provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
await Promise.resolve();
|
|
// BT-PRE-03. The timeout fires while the fetch is still pending and never
|
|
// settles on its own.
|
|
timers.forEach((fire) => fire());
|
|
|
|
await expect(issuing).resolves.toMatchObject({ ok: false });
|
|
|
|
releaseControl?.({ body: { cancel } } as unknown as Response);
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
expect(cancel).toHaveBeenCalledOnce();
|
|
void payload;
|
|
});
|
|
|
|
/**
|
|
* TR-RR-05. A scheduler that cannot install the deadline leaves the operation
|
|
* unbounded. Releasing the caller listener and continuing anyway meant a
|
|
* later abort was invisible, so an install failure is itself terminal: the
|
|
* request fails closed with a typed Result and its resources are released.
|
|
*/
|
|
it("fails closed when the scheduler cannot install the deadline", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const caller = new AbortController();
|
|
const remove = vi.spyOn(caller.signal, "removeEventListener");
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: downloadResponse(bytes.slice().buffer, payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider } = createHarness({
|
|
fetcher,
|
|
scheduler: {
|
|
setTimeout: () => {
|
|
throw new TypeError("scheduler exploded");
|
|
},
|
|
clearTimeout: () => {},
|
|
},
|
|
});
|
|
|
|
// The public result stays a typed Result, not a rejection.
|
|
await expect(
|
|
provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: caller.signal,
|
|
}),
|
|
).resolves.toMatchObject({ ok: false });
|
|
expect(remove).toHaveBeenCalled();
|
|
});
|
|
|
|
it.each([
|
|
{ label: "href/origin mismatch", patch: { origin: "https://evil.example" } },
|
|
{ label: "href/path mismatch", patch: { path: "/files/other" } },
|
|
{ label: "credentials in href", patch: { href: `https://u:p@objects.example${DOWNLOAD_PATH}` } },
|
|
{ label: "maxBytes below byteLength", patch: { maxBytes: 0 } },
|
|
{ label: "malformed digest", patch: { expectedSha256: "not-a-digest" } },
|
|
{ label: "non-positive expiry", patch: { expiresAtEpochMs: 0 } },
|
|
// TR-RR-03. The registration is a versioned exact union: plaintext, an
|
|
// ambient credential header, an unknown protocol version and any extra
|
|
// own field are all refused at the issuer seam.
|
|
{ label: "unknown protocol version", patch: { protocol: "PRESIGNED_TRANSFER_V0" } },
|
|
{ label: "missing protocol version", patch: { protocol: undefined } },
|
|
{
|
|
label: "plaintext target",
|
|
patch: {
|
|
href: `http://objects.example${DOWNLOAD_PATH}`,
|
|
origin: "http://objects.example",
|
|
},
|
|
},
|
|
{
|
|
label: "ambient credential header",
|
|
patch: {
|
|
requestHeaders: [{ name: "authorization", value: "Bearer leak" }],
|
|
},
|
|
},
|
|
{
|
|
label: "cookie response header",
|
|
patch: {
|
|
requiredResponseHeaders: [{ name: "set-cookie", value: "a=b" }],
|
|
},
|
|
},
|
|
{ label: "status outside 2xx", patch: { expectedStatus: 302 } },
|
|
{ label: "extra own field", patch: { injected: true } },
|
|
])(
|
|
"rejects a malformed registration at the vault issuer seam ($label)",
|
|
({ patch }) => {
|
|
// BT-PRE-04. The vault owns these invariants itself, so a second issuer
|
|
// cannot register a weaker capability of the same type.
|
|
const vault = createPresignedCapabilityVault({
|
|
now: () => NOW,
|
|
maxActiveCapabilities: 4,
|
|
});
|
|
const base = {
|
|
protocol: PRESIGNED_TRANSFER_PROTOCOL,
|
|
capabilityReceipt: "capability-direct-1",
|
|
method: "GET" as const,
|
|
binding: { kind: "DOWNLOAD" as const, resourceId: "resource-1" },
|
|
href: `${DATA_ORIGIN}${DOWNLOAD_PATH}`,
|
|
origin: DATA_ORIGIN,
|
|
path: DOWNLOAD_PATH,
|
|
allowedQueryParameters: [],
|
|
requestHeaders: [],
|
|
requiredResponseHeaders: [],
|
|
digestRequestHeader: null,
|
|
digestResponseHeader: null,
|
|
receiptResponseHeader: null,
|
|
expectedStatus: 200,
|
|
expectedResponseByteLength: 3,
|
|
mediaType: "application/octet-stream",
|
|
byteLength: 3,
|
|
maxBytes: 3,
|
|
expectedSha256: "a".repeat(64),
|
|
expiresAtEpochMs: NOW + 60_000,
|
|
};
|
|
|
|
expect(
|
|
vault.register({ ...base, ...patch } as never),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
// The same registration without the defect is accepted.
|
|
expect(vault.register(base as never)).toMatchObject({ ok: true });
|
|
vault.dispose();
|
|
},
|
|
);
|
|
|
|
/**
|
|
* TR-01. The vault validated the issuer's own object and then read it again
|
|
* to copy it. Between those reads a stateful issuer could show an allowed
|
|
* header set to the forbidden-header check and hand `Authorization` to the
|
|
* stored binding, so the executor sent a credential no rule had approved.
|
|
*/
|
|
describe("TR-01 the stored capability is the one that was validated", () => {
|
|
const baseRegistration = () => ({
|
|
protocol: PRESIGNED_TRANSFER_PROTOCOL,
|
|
capabilityReceipt: "capability-snapshot-1",
|
|
method: "GET" as const,
|
|
binding: { kind: "DOWNLOAD" as const, resourceId: "resource-1" },
|
|
href: `${DATA_ORIGIN}${DOWNLOAD_PATH}`,
|
|
origin: DATA_ORIGIN,
|
|
path: DOWNLOAD_PATH,
|
|
allowedQueryParameters: [],
|
|
requestHeaders: [{ name: "x-safe", value: "1" }],
|
|
requiredResponseHeaders: [],
|
|
digestRequestHeader: null,
|
|
digestResponseHeader: null,
|
|
receiptResponseHeader: null,
|
|
expectedStatus: 200,
|
|
expectedResponseByteLength: 3,
|
|
mediaType: "application/octet-stream",
|
|
byteLength: 3,
|
|
maxBytes: 3,
|
|
expectedSha256: "a".repeat(64),
|
|
expiresAtEpochMs: NOW + 60_000,
|
|
});
|
|
|
|
const freshVault = () =>
|
|
createPresignedCapabilityVault({
|
|
now: () => NOW,
|
|
maxActiveCapabilities: 4,
|
|
});
|
|
|
|
it("refuses a header row that answers differently on a second read", () => {
|
|
const vault = freshVault();
|
|
let nameReads = 0;
|
|
const header = new Proxy(
|
|
{ name: "x-safe", value: "1" },
|
|
{
|
|
getOwnPropertyDescriptor(target, key) {
|
|
if (key === "name") {
|
|
nameReads += 1;
|
|
return {
|
|
configurable: true,
|
|
enumerable: true,
|
|
value: nameReads > 1 ? "authorization" : "x-safe",
|
|
};
|
|
}
|
|
return Reflect.getOwnPropertyDescriptor(target, key);
|
|
},
|
|
},
|
|
);
|
|
|
|
const registered = vault.register({
|
|
...baseRegistration(),
|
|
requestHeaders: [header],
|
|
} as never);
|
|
|
|
if (registered.ok) {
|
|
// A single read means the value that was checked is the value stored.
|
|
const resolved = vault.resolve(registered.value);
|
|
expect(resolved.ok).toBe(true);
|
|
if (resolved.ok) {
|
|
expect(resolved.value.requestHeaders.map((row) => row.name)).toEqual([
|
|
"x-safe",
|
|
]);
|
|
}
|
|
}
|
|
vault.dispose();
|
|
});
|
|
|
|
const hostileRegistrations: readonly (readonly [string, () => unknown])[] = [
|
|
[
|
|
"an accessor field",
|
|
() =>
|
|
Object.defineProperty(baseRegistration(), "href", {
|
|
enumerable: true,
|
|
get: () => `${DATA_ORIGIN}${DOWNLOAD_PATH}`,
|
|
}),
|
|
],
|
|
[
|
|
"an inherited field",
|
|
() => Object.assign(Object.create({ injected: true }), baseRegistration()),
|
|
],
|
|
[
|
|
"a symbol field",
|
|
() => ({ ...baseRegistration(), [Symbol.for("injected")]: true }),
|
|
],
|
|
[
|
|
"a non-enumerable own field",
|
|
() =>
|
|
Object.defineProperty(baseRegistration(), "injected", {
|
|
enumerable: false,
|
|
value: true,
|
|
}),
|
|
],
|
|
[
|
|
"a throwing ownKeys trap",
|
|
() =>
|
|
new Proxy(baseRegistration(), {
|
|
ownKeys() {
|
|
throw new TypeError("hostile ownKeys trap");
|
|
},
|
|
}),
|
|
],
|
|
[
|
|
"a null header array",
|
|
() => ({ ...baseRegistration(), requestHeaders: null }),
|
|
],
|
|
[
|
|
"a non-iterable header array",
|
|
() => ({ ...baseRegistration(), requestHeaders: { length: 1 } }),
|
|
],
|
|
[
|
|
"a header row with an extra field",
|
|
() => ({
|
|
...baseRegistration(),
|
|
requestHeaders: [{ name: "x-safe", value: "1", injected: true }],
|
|
}),
|
|
],
|
|
[
|
|
"an accessor header name",
|
|
() => ({
|
|
...baseRegistration(),
|
|
requestHeaders: [
|
|
Object.defineProperty({ value: "1" }, "name", {
|
|
enumerable: true,
|
|
get: () => "x-safe",
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
[
|
|
"a binding with an extra field",
|
|
() => ({
|
|
...baseRegistration(),
|
|
binding: { kind: "DOWNLOAD", resourceId: "r", injected: true },
|
|
}),
|
|
],
|
|
[
|
|
"a null binding",
|
|
() => ({ ...baseRegistration(), binding: null }),
|
|
],
|
|
];
|
|
|
|
for (const [label, build] of hostileRegistrations) {
|
|
it(`rejects ${label} as POLICY_REJECTED`, () => {
|
|
const vault = freshVault();
|
|
expect(vault.register(build() as never)).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
vault.dispose();
|
|
});
|
|
}
|
|
|
|
it("does not observe a mutation of the issuer's object after registration", () => {
|
|
const vault = freshVault();
|
|
const registration = baseRegistration();
|
|
const registered = vault.register(registration as never);
|
|
expect(registered.ok).toBe(true);
|
|
if (!registered.ok) return;
|
|
|
|
registration.requestHeaders[0]!.name = "authorization";
|
|
registration.expiresAtEpochMs = NOW + 999_999;
|
|
|
|
const resolved = vault.resolve(registered.value);
|
|
expect(resolved.ok).toBe(true);
|
|
if (!resolved.ok) return;
|
|
expect(resolved.value.requestHeaders.map((row) => row.name)).toEqual([
|
|
"x-safe",
|
|
]);
|
|
expect(resolved.value.expiresAtEpochMs).toBe(NOW + 60_000);
|
|
vault.dispose();
|
|
});
|
|
});
|
|
|
|
it("does not fetch a presigned download until stream consumption", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
const responsePayload = downloadCapabilityPayload(bytes);
|
|
let downloadFetches = 0;
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
return jsonResponse(responsePayload);
|
|
}
|
|
downloadFetches += 1;
|
|
return downloadResponse(bytes.slice().buffer, responsePayload);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const signal = new AbortController().signal;
|
|
const issued = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
// BT-PRE-01. open() performs no network I/O.
|
|
expect(downloadFetches).toBe(0);
|
|
|
|
for await (const chunk of opened.value.stream(signal)) {
|
|
expect(chunk.ok).toBe(true);
|
|
}
|
|
expect(downloadFetches).toBe(1);
|
|
opened.value.close();
|
|
});
|
|
|
|
it("closes an unused download source without network I/O", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
const responsePayload = downloadCapabilityPayload(bytes);
|
|
let downloadFetches = 0;
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
return jsonResponse(responsePayload);
|
|
}
|
|
downloadFetches += 1;
|
|
return downloadResponse(bytes.slice().buffer, responsePayload);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const signal = new AbortController().signal;
|
|
const issued = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
|
|
opened.value.close();
|
|
// close() is idempotent and never starts the transfer.
|
|
opened.value.close();
|
|
expect(downloadFetches).toBe(0);
|
|
|
|
// A stream after close is one terminal conflict, still without fetching.
|
|
const results = [];
|
|
for await (const chunk of opened.value.stream(signal)) {
|
|
results.push(chunk);
|
|
}
|
|
expect(results).toMatchObject([
|
|
{ ok: false, error: { code: "CONFLICT" } },
|
|
]);
|
|
expect(downloadFetches).toBe(0);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "truncation",
|
|
body: new Uint8Array([1, 2]),
|
|
expectedCode: "INTEGRITY_FAILED",
|
|
},
|
|
{
|
|
name: "overrun",
|
|
body: new Uint8Array([1, 2, 3, 4]),
|
|
expectedCode: "INTEGRITY_FAILED",
|
|
},
|
|
])("closes $name as a terminal stream failure", async ({ body, expectedCode }) => {
|
|
const declared = new Uint8Array([1, 2, 3]);
|
|
const payload = downloadCapabilityPayload(declared);
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: downloadResponse(body.slice().buffer, payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
const results = await collect(opened.value);
|
|
expect(results.at(-1)).toMatchObject({
|
|
ok: false,
|
|
error: { code: expectedCode },
|
|
});
|
|
const firstFailure = results.findIndex((result) => !result.ok);
|
|
expect(results.slice(firstFailure + 1)).toEqual([]);
|
|
});
|
|
|
|
it("closes native body errors without throwing across the port", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const failingBody = new ReadableStream<Uint8Array>({
|
|
pull(controller) {
|
|
controller.error(new DOMException("secret native detail", "NetworkError"));
|
|
},
|
|
});
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: downloadResponse(failingBody, payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
await expect(collect(opened.value)).resolves.toMatchObject([
|
|
{
|
|
ok: false,
|
|
error: {
|
|
code: "NOT_READABLE",
|
|
recovery: "REISSUE_CAPABILITY",
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("closes active abort and timeout without leaking native rejection", async () => {
|
|
const bytes = new Uint8Array([1]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const neverBody = () =>
|
|
new ReadableStream<Uint8Array>({ pull() {} });
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: downloadResponse(neverBody(), payload),
|
|
) as unknown as typeof fetch;
|
|
const controller = new AbortController();
|
|
let harness = createHarness({ fetcher });
|
|
let issued = await harness.provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: controller.signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
let opened = await harness.executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: controller.signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
const aborted = collect(opened.value, controller.signal);
|
|
controller.abort("user");
|
|
expect(await aborted).toMatchObject([
|
|
{ ok: false, error: { code: "ABORTED" } },
|
|
]);
|
|
|
|
let timeoutCallback: (() => void) | undefined;
|
|
const scheduler = {
|
|
setTimeout(callback: () => void) {
|
|
timeoutCallback = callback;
|
|
return 1;
|
|
},
|
|
clearTimeout() {},
|
|
};
|
|
harness = createHarness({ fetcher, scheduler });
|
|
issued = await harness.provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
opened = await harness.executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
const timedOut = collect(opened.value);
|
|
timeoutCallback?.();
|
|
expect(await timedOut).toMatchObject([
|
|
{
|
|
ok: false,
|
|
error: {
|
|
code: "UNAVAILABLE",
|
|
recovery: "REISSUE_CAPABILITY",
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("rejects an expired capability before data-plane fetch", async () => {
|
|
const bytes = new Uint8Array([1]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
let current = NOW;
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: downloadResponse(bytes.slice().buffer, payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({
|
|
fetcher,
|
|
now: () => current,
|
|
});
|
|
const issued = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
current = Number(payload.expiresAtEpochMs) + 1;
|
|
expect(
|
|
await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "EXPIRED_RESOURCE",
|
|
recovery: "REISSUE_CAPABILITY",
|
|
},
|
|
});
|
|
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("rejects capabilities below the configured minimum remaining lifetime", async () => {
|
|
const bytes = new Uint8Array([1]);
|
|
const nearExpiryPayload = downloadCapabilityPayload(bytes, {
|
|
expiresAtEpochMs: NOW + 999,
|
|
});
|
|
let fetcher = vi.fn(async () =>
|
|
jsonResponse(nearExpiryPayload),
|
|
) as unknown as typeof fetch;
|
|
let harness = createHarness({ fetcher });
|
|
expect(
|
|
await harness.provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "EXPIRED_RESOURCE",
|
|
recovery: "REISSUE_CAPABILITY",
|
|
},
|
|
});
|
|
|
|
const acceptedPayload = downloadCapabilityPayload(bytes, {
|
|
expiresAtEpochMs: NOW + 2_000,
|
|
});
|
|
let current = NOW;
|
|
fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(acceptedPayload)
|
|
: downloadResponse(bytes.slice().buffer, acceptedPayload),
|
|
) as unknown as typeof fetch;
|
|
harness = createHarness({
|
|
fetcher,
|
|
now: () => current,
|
|
});
|
|
const issued = await harness.provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
current = NOW + 1_001;
|
|
expect(
|
|
await harness.executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
code: "EXPIRED_RESOURCE",
|
|
recovery: "REISSUE_CAPABILITY",
|
|
},
|
|
});
|
|
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("closes malformed AbortSignal inputs at every public boundary", async () => {
|
|
const bytes = new Uint8Array([1, 2]);
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const uploadChecksum = sha256Hex(bytes);
|
|
const uploadPayload = uploadCapabilityPayload({
|
|
bytes,
|
|
checksum: uploadChecksum,
|
|
});
|
|
const fetcher = vi.fn(
|
|
async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
const request = JSON.parse(String(init?.body)) as {
|
|
method: string;
|
|
};
|
|
return jsonResponse(
|
|
request.method === "GET" ? payload : uploadPayload,
|
|
);
|
|
}
|
|
if (String(input) === DOWNLOAD_HREF) {
|
|
return downloadResponse(bytes.slice().buffer, payload);
|
|
}
|
|
return responseWithUrl(
|
|
new Response(null, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "0",
|
|
ETag: "\"part-etag-1\"",
|
|
},
|
|
}),
|
|
String(uploadPayload.href),
|
|
);
|
|
},
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const malformed = {} as AbortSignal;
|
|
|
|
expect(
|
|
await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: malformed,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INVALID_INPUT" },
|
|
});
|
|
expect(
|
|
await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: uploadChecksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: malformed,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INVALID_INPUT" },
|
|
});
|
|
|
|
const issuedDownload = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issuedDownload.ok).toBe(true);
|
|
if (!issuedDownload.ok) return;
|
|
expect(
|
|
await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issuedDownload.value,
|
|
signal: malformed,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INVALID_INPUT" },
|
|
});
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issuedDownload.value,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
expect(await collect(opened.value, malformed)).toMatchObject([
|
|
{ ok: false, error: { code: "INVALID_INPUT" } },
|
|
]);
|
|
expect(await collect(opened.value)).toMatchObject([
|
|
{ ok: false, error: { code: "CONFLICT" } },
|
|
]);
|
|
|
|
const issuedUpload = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: uploadChecksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issuedUpload.ok).toBe(true);
|
|
if (!issuedUpload.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issuedUpload.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: uploadChecksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: malformed,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INVALID_INPUT" },
|
|
});
|
|
});
|
|
|
|
it("snapshots, verifies and uploads a PUT part with a separate response receipt", async () => {
|
|
const original = new Uint8Array([9, 8, 7]);
|
|
const checksum = sha256Hex(original);
|
|
const payload = uploadCapabilityPayload({
|
|
bytes: original,
|
|
checksum,
|
|
});
|
|
let releaseDigest: (() => void) | undefined;
|
|
const digestGate = new Promise<void>((resolve) => {
|
|
releaseDigest = resolve;
|
|
});
|
|
const sentBodies: number[][] = [];
|
|
const dataCalls: RequestInit[] = [];
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
if (String(input) === CONTROL_ENDPOINT) return jsonResponse(payload);
|
|
dataCalls.push(init ?? {});
|
|
sentBodies.push([
|
|
...new Uint8Array(init?.body as ArrayBuffer),
|
|
]);
|
|
return responseWithUrl(
|
|
new Response(null, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "0",
|
|
ETag: "\"part-etag-1\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({
|
|
fetcher,
|
|
digestBytes: async (bytes) => {
|
|
await digestGate;
|
|
return sha256Hex(bytes);
|
|
},
|
|
});
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: original.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
const request = {
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: original.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes: original,
|
|
signal: new AbortController().signal,
|
|
};
|
|
const pending = executor.uploadParts.put(request);
|
|
original.fill(0);
|
|
request.sessionId = "mutated-session";
|
|
request.requestBindingSha256 = "d".repeat(64);
|
|
request.checksumSha256 = "f".repeat(64);
|
|
releaseDigest?.();
|
|
|
|
expect(await pending).toEqual({
|
|
ok: true,
|
|
value: {
|
|
bytesWritten: 3,
|
|
checksumSha256: checksum,
|
|
receiptToken: "part-etag-1",
|
|
},
|
|
});
|
|
expect(sentBodies).toEqual([[9, 8, 7]]);
|
|
expect(dataCalls[0]).toMatchObject({
|
|
method: "PUT",
|
|
credentials: "omit",
|
|
redirect: "error",
|
|
referrerPolicy: "no-referrer",
|
|
});
|
|
expect(
|
|
(dataCalls[0]?.headers as Headers).get(CHECKSUM_HEADER),
|
|
).toBe(checksum);
|
|
});
|
|
|
|
it.each(["sessionId", "requestBindingSha256"] as const)(
|
|
"rejects an actual PUT whose %s differs from the capability",
|
|
async (field) => {
|
|
const bytes = new Uint8Array([3, 2, 1]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload({ bytes, checksum });
|
|
const fetcher = vi.fn(async () =>
|
|
jsonResponse(payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId:
|
|
field === "sessionId"
|
|
? "different-session"
|
|
: UPLOAD_SESSION_ID,
|
|
requestBindingSha256:
|
|
field === "requestBindingSha256"
|
|
? "d".repeat(64)
|
|
: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
},
|
|
);
|
|
|
|
it("rejects URL-shaped upload receipts", async () => {
|
|
const bytes = new Uint8Array([4, 5, 6]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload({ bytes, checksum });
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
return jsonResponse(payload);
|
|
}
|
|
return responseWithUrl(
|
|
new Response(null, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "0",
|
|
ETag: "\"https://objects.example/authorizing-token\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "INTEGRITY_FAILED" },
|
|
});
|
|
});
|
|
|
|
it("drains a bounded successful PUT acknowledgement without cancelling it", async () => {
|
|
const bytes = new Uint8Array([4, 5, 6]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload(
|
|
{ bytes, checksum },
|
|
{ expectedResponseByteLength: 2 },
|
|
);
|
|
let cancelled = false;
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
return jsonResponse(payload);
|
|
}
|
|
const body = new ReadableStream<Uint8Array>({
|
|
start(controller) {
|
|
controller.enqueue(new Uint8Array([8, 9]));
|
|
controller.close();
|
|
},
|
|
cancel() {
|
|
cancelled = true;
|
|
},
|
|
});
|
|
return responseWithUrl(
|
|
new Response(body, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "2",
|
|
ETag: "\"part-etag-1\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: true,
|
|
value: { receiptToken: "part-etag-1" },
|
|
});
|
|
expect(cancelled).toBe(false);
|
|
});
|
|
|
|
it("accepts an empty 204 PUT acknowledgement", async () => {
|
|
const bytes = new Uint8Array([4, 5, 6]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload(
|
|
{ bytes, checksum },
|
|
{
|
|
expectedStatus: 204,
|
|
expectedResponseByteLength: 0,
|
|
},
|
|
);
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: responseWithUrl(
|
|
new Response(null, {
|
|
status: 204,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
ETag: "\"part-etag-204\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
),
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: true,
|
|
value: { receiptToken: "part-etag-204" },
|
|
});
|
|
});
|
|
|
|
it("cancels a PUT acknowledgement whose declared length violates its binding", async () => {
|
|
const bytes = new Uint8Array([4, 5, 6]);
|
|
const checksum = sha256Hex(bytes);
|
|
const payload = uploadCapabilityPayload({ bytes, checksum });
|
|
let cancelled = false;
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
return jsonResponse(payload);
|
|
}
|
|
const body = new ReadableStream<Uint8Array>({
|
|
pull() {},
|
|
cancel() {
|
|
cancelled = true;
|
|
},
|
|
});
|
|
return responseWithUrl(
|
|
new Response(body, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "1",
|
|
ETag: "\"part-etag-1\"",
|
|
},
|
|
}),
|
|
String(payload.href),
|
|
);
|
|
}) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issued.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { code: "POLICY_REJECTED" },
|
|
});
|
|
expect(cancelled).toBe(true);
|
|
});
|
|
|
|
it("observes only safe operation, outcome, failure and byte buckets", async () => {
|
|
const bytes = new Uint8Array([7, 8, 9]);
|
|
const downloadPayload = downloadCapabilityPayload(bytes);
|
|
const checksum = sha256Hex(bytes);
|
|
const uploadPayload = uploadCapabilityPayload({ bytes, checksum });
|
|
const observations: BrowserDataObservation[] = [];
|
|
const fetcher = vi.fn(
|
|
async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
if (String(input) === CONTROL_ENDPOINT) {
|
|
const request = JSON.parse(String(init?.body)) as {
|
|
method: string;
|
|
};
|
|
return jsonResponse(
|
|
request.method === "GET"
|
|
? downloadPayload
|
|
: uploadPayload,
|
|
);
|
|
}
|
|
if (String(input) === DOWNLOAD_HREF) {
|
|
return downloadResponse(
|
|
bytes.slice().buffer,
|
|
downloadPayload,
|
|
);
|
|
}
|
|
return responseWithUrl(
|
|
new Response(null, {
|
|
status: 200,
|
|
headers: {
|
|
[POLICY_HEADER]: "v1",
|
|
"Content-Length": "0",
|
|
ETag: "\"part-etag-secret\"",
|
|
},
|
|
}),
|
|
String(uploadPayload.href),
|
|
);
|
|
},
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({
|
|
fetcher,
|
|
observer: {
|
|
record(observation) {
|
|
observations.push(observation);
|
|
},
|
|
},
|
|
});
|
|
const issuedDownload = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issuedDownload.ok).toBe(true);
|
|
if (!issuedDownload.ok) return;
|
|
const opened = await executor.downloadSources.open({
|
|
resourceId: "resource-1",
|
|
capability: issuedDownload.value,
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(opened.ok).toBe(true);
|
|
if (!opened.ok) return;
|
|
expect((await collect(opened.value)).every((result) => result.ok)).toBe(
|
|
true,
|
|
);
|
|
|
|
const issuedUpload = await provider.issueUploadPart({
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
mediaType: "application/octet-stream",
|
|
idempotencyKey: "part-attempt-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issuedUpload.ok).toBe(true);
|
|
if (!issuedUpload.ok) return;
|
|
expect(
|
|
await executor.uploadParts.put({
|
|
capability: issuedUpload.value,
|
|
sessionId: UPLOAD_SESSION_ID,
|
|
requestBindingSha256: REQUEST_BINDING_SHA256,
|
|
uploadBindingSha256: "b".repeat(64),
|
|
partNumber: 1,
|
|
offset: 0,
|
|
byteLength: bytes.byteLength,
|
|
checksumSha256: checksum,
|
|
idempotencyKey: "part-attempt-1",
|
|
bytes,
|
|
signal: new AbortController().signal,
|
|
}),
|
|
).toMatchObject({ ok: true });
|
|
|
|
expect(observations).toEqual([
|
|
{
|
|
operation: "PRESIGNED_TRANSFER",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
{
|
|
operation: "PRESIGNED_TRANSFER",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
{
|
|
operation: "DOWNLOAD",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
{
|
|
operation: "PRESIGNED_TRANSFER",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
{
|
|
operation: "UPLOAD_PART",
|
|
outcome: "SUCCEEDED",
|
|
byteBucket: "LT1MIB",
|
|
},
|
|
]);
|
|
const serialized = JSON.stringify(observations);
|
|
for (const secret of [
|
|
DOWNLOAD_HREF,
|
|
"do-not-log-this",
|
|
checksum,
|
|
"capability-download-1",
|
|
"capability-upload-1",
|
|
"part-etag-secret",
|
|
"resource-1",
|
|
]) {
|
|
expect(serialized).not.toContain(secret);
|
|
}
|
|
});
|
|
|
|
it("connects an issued capability to DownloadDeliveryPort without caller digest input", async () => {
|
|
const bytes = new TextEncoder().encode("verified");
|
|
const payload = downloadCapabilityPayload(bytes);
|
|
const fetcher = vi.fn(async (input: RequestInfo | URL) =>
|
|
String(input) === CONTROL_ENDPOINT
|
|
? jsonResponse(payload)
|
|
: downloadResponse(bytes.slice().buffer, payload),
|
|
) as unknown as typeof fetch;
|
|
const { provider, executor } = createHarness({ fetcher });
|
|
const issued = await provider.issueDownload({
|
|
resourceId: "resource-1",
|
|
signal: new AbortController().signal,
|
|
});
|
|
expect(issued.ok).toBe(true);
|
|
if (!issued.ok) return;
|
|
|
|
const policy = browserFilePolicyReference(
|
|
"download",
|
|
"presigned-stream",
|
|
);
|
|
const policies = new BrowserFilePolicyRegistry({
|
|
profiles: [
|
|
{
|
|
reference: policy,
|
|
download: {
|
|
strategy: "PROMPT_AND_STREAM",
|
|
mediaType: "application/octet-stream",
|
|
safeExtension: ".bin",
|
|
maxTransferBytes: 64,
|
|
maxBufferedBytes: 8,
|
|
integrity: "REQUIRED",
|
|
},
|
|
},
|
|
],
|
|
hardLimits: {
|
|
maxInspectionBytes: 64,
|
|
maxRetainedFileBytes: 64,
|
|
maxPreviewBytes: 64,
|
|
maxObjectUrlBytes: 64,
|
|
maxTransferBytes: 64,
|
|
},
|
|
});
|
|
const written: number[] = [];
|
|
const handle: SaveFileHandle = {
|
|
async createWritable() {
|
|
return new WritableStream<Uint8Array>({
|
|
write(chunk) {
|
|
written.push(...chunk);
|
|
},
|
|
});
|
|
},
|
|
};
|
|
const downloads = createDownloadDeliveryAdapter({
|
|
host: { handoff() {} },
|
|
policies,
|
|
hardMaxObjectUrlBytes: 64,
|
|
hardMaxTransferBytes: 64,
|
|
browserManagedCapabilities: {
|
|
resolve() {
|
|
throw new TypeError("not used");
|
|
},
|
|
},
|
|
openAuthorizedSource:
|
|
executor.downloadSources.open.bind(executor.downloadSources),
|
|
showSaveFilePicker: async () => handle,
|
|
userActivation: { isActive: true },
|
|
now: () => NOW,
|
|
});
|
|
const result = await downloads.deliver({
|
|
policy,
|
|
source: {
|
|
kind: "AUTHORIZED_STREAM_RESOURCE",
|
|
resourceId: "resource-1",
|
|
capability: issued.value,
|
|
},
|
|
suggestedFileName: "artifact.bin",
|
|
signal: new AbortController().signal,
|
|
onProgress() {},
|
|
});
|
|
expect(result).toMatchObject({
|
|
ok: true,
|
|
value: {
|
|
kind: "SAVED",
|
|
integrity: "VERIFIED",
|
|
bytesWritten: bytes.byteLength,
|
|
},
|
|
});
|
|
expect(written).toEqual([...bytes]);
|
|
});
|
|
|
|
/**
|
|
* TR-RR-04. A presigned byte source owns a fetch reader and a capability
|
|
* lease, and its port requires `close()`. The delivery consumer never called
|
|
* it, so every outcome — success, validation failure, writer failure and
|
|
* abort — leaked both.
|
|
*/
|
|
it.each([
|
|
{ label: "success", mode: "SUCCESS" as const },
|
|
{ label: "writer failure", mode: "WRITER_FAILURE" as const },
|
|
{ label: "abort", mode: "ABORT" as const },
|
|
])("closes the presigned source exactly once on $label", async ({ mode }) => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
let closes = 0;
|
|
const controller = new AbortController();
|
|
const source = {
|
|
byteLength: bytes.byteLength,
|
|
integrity: "VERIFIED_ON_SUCCESSFUL_EXHAUSTION" as const,
|
|
capability: undefined as never,
|
|
close() {
|
|
closes += 1;
|
|
},
|
|
async *stream() {
|
|
if (mode === "ABORT") controller.abort();
|
|
yield { ok: true as const, value: bytes };
|
|
},
|
|
};
|
|
const capability = Object.freeze({
|
|
capabilityReceipt: "capability-close-1",
|
|
method: "GET" as const,
|
|
binding: { kind: "DOWNLOAD" as const, resourceId: "resource-1" },
|
|
mediaType: "application/octet-stream",
|
|
byteLength: bytes.byteLength,
|
|
maxBytes: bytes.byteLength,
|
|
expectedSha256: "a".repeat(64),
|
|
expiresAtEpochMs: NOW + 60_000,
|
|
});
|
|
source.capability = capability as never;
|
|
|
|
const closePolicy = browserFilePolicyReference(
|
|
"download",
|
|
"presigned-close",
|
|
);
|
|
const policies = new BrowserFilePolicyRegistry({
|
|
profiles: [
|
|
{
|
|
reference: closePolicy,
|
|
download: {
|
|
strategy: "PROMPT_AND_STREAM",
|
|
mediaType: "application/octet-stream",
|
|
safeExtension: ".bin",
|
|
maxTransferBytes: 64,
|
|
maxBufferedBytes: 8,
|
|
integrity: "REQUIRED",
|
|
},
|
|
},
|
|
],
|
|
hardLimits: {
|
|
maxInspectionBytes: 64,
|
|
maxRetainedFileBytes: 64,
|
|
maxPreviewBytes: 64,
|
|
maxObjectUrlBytes: 64,
|
|
maxTransferBytes: 64,
|
|
},
|
|
});
|
|
const handle: SaveFileHandle = {
|
|
async createWritable() {
|
|
return new WritableStream<Uint8Array>({
|
|
write() {
|
|
if (mode === "WRITER_FAILURE") {
|
|
throw new TypeError("writer exploded");
|
|
}
|
|
},
|
|
});
|
|
},
|
|
};
|
|
const downloads = createDownloadDeliveryAdapter({
|
|
host: { handoff() {} },
|
|
policies,
|
|
hardMaxObjectUrlBytes: 64,
|
|
hardMaxTransferBytes: 64,
|
|
browserManagedCapabilities: {
|
|
resolve() {
|
|
throw new TypeError("not used");
|
|
},
|
|
},
|
|
openAuthorizedSource: async () =>
|
|
({ ok: true, value: source }) as never,
|
|
showSaveFilePicker: async () => handle,
|
|
userActivation: { isActive: true },
|
|
now: () => NOW,
|
|
});
|
|
|
|
const deliveryResult = await downloads.deliver({
|
|
policy: closePolicy,
|
|
source: {
|
|
kind: "AUTHORIZED_STREAM_RESOURCE",
|
|
resourceId: "resource-1",
|
|
capability: capability as never,
|
|
},
|
|
suggestedFileName: "artifact.bin",
|
|
signal: controller.signal,
|
|
onProgress() {},
|
|
});
|
|
|
|
void deliveryResult;
|
|
expect(closes).toBe(1);
|
|
});
|
|
|
|
/**
|
|
* TR-02. A lease that resolved after the abort already ended the delivery
|
|
* never reached the holder, so nothing closed it: the fetch reader and the
|
|
* capability lease outlived the terminal result.
|
|
*/
|
|
it("closes a source lease that arrives after the delivery was aborted", async () => {
|
|
const bytes = new Uint8Array([1, 2, 3]);
|
|
let closes = 0;
|
|
const controller = new AbortController();
|
|
let releaseOpen:
|
|
| ((value: { ok: true; value: unknown }) => void)
|
|
| undefined;
|
|
const source = {
|
|
byteLength: bytes.byteLength,
|
|
integrity: "VERIFIED_ON_SUCCESSFUL_EXHAUSTION" as const,
|
|
capability: undefined as never,
|
|
close() {
|
|
closes += 1;
|
|
},
|
|
async *stream() {
|
|
yield { ok: true as const, value: bytes };
|
|
},
|
|
};
|
|
const capability = Object.freeze({
|
|
capabilityReceipt: "capability-late-1",
|
|
method: "GET" as const,
|
|
binding: { kind: "DOWNLOAD" as const, resourceId: "resource-1" },
|
|
mediaType: "application/octet-stream",
|
|
byteLength: bytes.byteLength,
|
|
maxBytes: bytes.byteLength,
|
|
expectedSha256: "a".repeat(64),
|
|
expiresAtEpochMs: NOW + 60_000,
|
|
});
|
|
source.capability = capability as never;
|
|
|
|
const latePolicy = browserFilePolicyReference("download", "presigned-late");
|
|
const policies = new BrowserFilePolicyRegistry({
|
|
profiles: [
|
|
{
|
|
reference: latePolicy,
|
|
download: {
|
|
strategy: "PROMPT_AND_STREAM",
|
|
mediaType: "application/octet-stream",
|
|
safeExtension: ".bin",
|
|
maxTransferBytes: 64,
|
|
maxBufferedBytes: 8,
|
|
integrity: "REQUIRED",
|
|
},
|
|
},
|
|
],
|
|
hardLimits: {
|
|
maxInspectionBytes: 64,
|
|
maxRetainedFileBytes: 64,
|
|
maxPreviewBytes: 64,
|
|
maxObjectUrlBytes: 64,
|
|
maxTransferBytes: 64,
|
|
},
|
|
});
|
|
const downloads = createDownloadDeliveryAdapter({
|
|
host: { handoff() {} },
|
|
policies,
|
|
hardMaxObjectUrlBytes: 64,
|
|
hardMaxTransferBytes: 64,
|
|
browserManagedCapabilities: {
|
|
resolve() {
|
|
throw new TypeError("not used");
|
|
},
|
|
},
|
|
// Ignores the signal entirely and resolves only when the test says so.
|
|
openAuthorizedSource: () =>
|
|
new Promise((resolve) => {
|
|
releaseOpen = resolve as never;
|
|
}) as never,
|
|
showSaveFilePicker: async () => ({
|
|
async createWritable() {
|
|
return new WritableStream<Uint8Array>({ write() {} });
|
|
},
|
|
}),
|
|
userActivation: { isActive: true },
|
|
now: () => NOW,
|
|
});
|
|
|
|
const delivering = downloads.deliver({
|
|
policy: latePolicy,
|
|
source: {
|
|
kind: "AUTHORIZED_STREAM_RESOURCE",
|
|
resourceId: "resource-1",
|
|
capability: capability as never,
|
|
},
|
|
suggestedFileName: "artifact.bin",
|
|
signal: controller.signal,
|
|
onProgress() {},
|
|
});
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
controller.abort();
|
|
const delivered = await delivering;
|
|
expect(delivered.ok).toBe(false);
|
|
|
|
// The lease arrives only now, long after the terminal result.
|
|
releaseOpen?.({ ok: true, value: source });
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
expect(closes).toBe(1);
|
|
});
|
|
|
|
it("does not leave a late rejection unhandled after an abort", async () => {
|
|
const unhandled: unknown[] = [];
|
|
const onUnhandled = (reason: unknown) => unhandled.push(reason);
|
|
process.on("unhandledRejection", onUnhandled);
|
|
try {
|
|
const controller = new AbortController();
|
|
let rejectOpen: ((reason: unknown) => void) | undefined;
|
|
const rejectPolicy = browserFilePolicyReference(
|
|
"download",
|
|
"presigned-late-reject",
|
|
);
|
|
const policies = new BrowserFilePolicyRegistry({
|
|
profiles: [
|
|
{
|
|
reference: rejectPolicy,
|
|
download: {
|
|
strategy: "PROMPT_AND_STREAM",
|
|
mediaType: "application/octet-stream",
|
|
safeExtension: ".bin",
|
|
maxTransferBytes: 64,
|
|
maxBufferedBytes: 8,
|
|
integrity: "REQUIRED",
|
|
},
|
|
},
|
|
],
|
|
hardLimits: {
|
|
maxInspectionBytes: 64,
|
|
maxRetainedFileBytes: 64,
|
|
maxPreviewBytes: 64,
|
|
maxObjectUrlBytes: 64,
|
|
maxTransferBytes: 64,
|
|
},
|
|
});
|
|
const downloads = createDownloadDeliveryAdapter({
|
|
host: { handoff() {} },
|
|
policies,
|
|
hardMaxObjectUrlBytes: 64,
|
|
hardMaxTransferBytes: 64,
|
|
browserManagedCapabilities: {
|
|
resolve() {
|
|
throw new TypeError("not used");
|
|
},
|
|
},
|
|
openAuthorizedSource: () =>
|
|
new Promise((_resolve, reject) => {
|
|
rejectOpen = reject;
|
|
}) as never,
|
|
showSaveFilePicker: async () => ({
|
|
async createWritable() {
|
|
return new WritableStream<Uint8Array>({ write() {} });
|
|
},
|
|
}),
|
|
userActivation: { isActive: true },
|
|
now: () => NOW,
|
|
});
|
|
|
|
const delivering = downloads.deliver({
|
|
policy: rejectPolicy,
|
|
source: {
|
|
kind: "AUTHORIZED_STREAM_RESOURCE",
|
|
resourceId: "resource-1",
|
|
capability: Object.freeze({
|
|
capabilityReceipt: "capability-late-2",
|
|
method: "GET" as const,
|
|
binding: { kind: "DOWNLOAD" as const, resourceId: "resource-1" },
|
|
mediaType: "application/octet-stream",
|
|
byteLength: 3,
|
|
maxBytes: 3,
|
|
expectedSha256: "a".repeat(64),
|
|
expiresAtEpochMs: NOW + 60_000,
|
|
}) as never,
|
|
},
|
|
suggestedFileName: "artifact.bin",
|
|
signal: controller.signal,
|
|
onProgress() {},
|
|
});
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
controller.abort();
|
|
await delivering;
|
|
|
|
rejectOpen?.(new Error("late open failure"));
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
expect(unhandled).toEqual([]);
|
|
} finally {
|
|
process.off("unhandledRejection", onUnhandled);
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* BT-PRE-01. The download lease is lazy, so a response-shape rejection is
|
|
* observed on first consumption rather than at `open()`.
|
|
*/
|
|
async function firstStreamResult(
|
|
opened: Awaited<
|
|
ReturnType<
|
|
ReturnType<typeof createHarness>["executor"]["downloadSources"]["open"]
|
|
>
|
|
>,
|
|
): Promise<unknown> {
|
|
if (!opened.ok) return opened;
|
|
try {
|
|
for await (const chunk of opened.value.stream(
|
|
new AbortController().signal,
|
|
)) {
|
|
if (!chunk.ok) return chunk;
|
|
}
|
|
return { ok: true };
|
|
} finally {
|
|
opened.value.close();
|
|
}
|
|
}
|