import assert from "node:assert/strict"; import { createServer } from "node:http"; import { chmod, mkdtemp, rename, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { FOUNDRY_BINDING_CATALOG_ACTION, FOUNDRY_BINDING_CATALOG_PATH, FOUNDRY_BINDING_GRANT_SCHEMA_VERSION, FOUNDRY_BINDING_UPSERT_SCHEMA_VERSION, FOUNDRY_BINDING_UPSERT_ACTION, FOUNDRY_BINDING_UPSERT_PATH, createFoundryBindingGrantToken, foundryBindingGrantFileName, handleFoundryBindingApiRequest, } from "./foundry-binding-api.mjs"; const NOW = Date.parse("2026-07-15T12:00:00.000Z"); const TARGET = Object.freeze({ applicationId: "11111111-1111-4111-8111-111111111111", pageId: "map-page-1", bindingId: "fleet-live-points", dataProductId: "fleet.positions.current.v1", slotId: "points", semanticTypes: ["map.moving_object"], fieldProjection: ["course", "name", "speed"], }); test("Foundry binding workload API is scoped, revocable and replay-safe at its operation boundary", async (t) => { const grantsDir = await mkdtemp(join(tmpdir(), "nodedc-foundry-binding-grants-")); const token = createFoundryBindingGrantToken(); const calls = { preflight: [], upsert: [] }; let readerGrantReady = true; const options = { grantsDir, production: false, now: () => NOW, async preflightReaderGrant(input) { calls.preflight.push(structuredClone(input)); return readerGrantReady; }, async upsertBinding(input, actor) { calls.upsert.push({ input: structuredClone(input), actor: structuredClone(actor) }); return { application: { privateManifestMaterial: "must-not-leak" }, page: { unrelatedBindings: ["must-not-leak"] }, binding: { ...input.binding, delivery: "snapshot+patch" }, idempotency: { replayed: calls.upsert.length > 1 }, }; }, }; await writeGrant(grantsDir, token, grantFor(token)); const server = createServer((request, response) => { void handleFoundryBindingApiRequest(request, response, options); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const address = server.address(); const baseUrl = `http://127.0.0.1:${address.port}`; t.after(async () => { await new Promise((resolve) => server.close(resolve)); await rm(grantsDir, { recursive: true, force: true }); }); await t.test("catalog exposes only grant-allowlisted products", async () => { const response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token }); assert.equal(response.status, 200); assert.equal(response.headers.get("cache-control"), "no-store, max-age=0"); assert.equal(response.headers.get("pragma"), "no-cache"); assert.equal(response.headers.get("vary"), "authorization"); assert.deepEqual(response.body, { ok: true, dataProducts: [{ id: TARGET.dataProductId, semanticTypes: TARGET.semanticTypes }], }); }); await t.test("POST forwards the stable idempotency key and grant actor without leaking full manifests", async () => { const payload = upsertPayload(); const first = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: payload, headers: { "X-NODEDC-Actor": "attacker-supplied-actor" }, }); const second = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: payload }); assert.equal(first.status, 200); assert.equal(second.status, 200); assert.equal(first.body.idempotency.replayed, false); assert.equal(second.body.idempotency.replayed, true); assert.equal(first.body.application, undefined); assert.equal(first.body.page, undefined); assert.deepEqual(calls.upsert.map((call) => call.input.idempotencyKey), [payload.idempotencyKey, payload.idempotencyKey]); assert.deepEqual(calls.upsert.map((call) => call.actor), [ { actorId: "engine-agent-42", ownerKey: "workspace-42" }, { actorId: "engine-agent-42", ownerKey: "workspace-42" }, ]); assert.deepEqual(calls.preflight[0], { applicationId: TARGET.applicationId, pageId: TARGET.pageId, bindingId: TARGET.bindingId, dataProductId: TARGET.dataProductId, actor: { actorId: "engine-agent-42", ownerKey: "workspace-42" }, grantId: "foundry-binding-grant-42", }); }); await t.test("action scope and reader-grant preflight fail closed", async () => { const catalogDeniedToken = createFoundryBindingGrantToken(); await writeGrant(grantsDir, catalogDeniedToken, grantFor(catalogDeniedToken, { actions: [FOUNDRY_BINDING_UPSERT_ACTION], })); let response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: catalogDeniedToken }); assert.equal(response.status, 403); assert.equal(response.body.error, "foundry_binding_action_forbidden"); const baselineUpserts = calls.upsert.length; readerGrantReady = false; response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: upsertPayload(), }); readerGrantReady = true; assert.equal(response.status, 409); assert.equal(response.body.error, "data_product_reader_grant_not_ready"); assert.equal(calls.upsert.length, baselineUpserts); }); await t.test("exact target scope rejects any changed dimension before callbacks", async () => { const baselinePreflight = calls.preflight.length; const baselineUpsert = calls.upsert.length; const mutations = [ (body) => { body.applicationId = "22222222-2222-4222-8222-222222222222"; }, (body) => { body.pageId = "map-page-2"; }, (body) => { body.binding.id = "fleet-live-points-2"; }, (body) => { body.binding.dataProductId = "fleet.positions.current.v2"; }, (body) => { body.binding.slotId = "traces"; }, (body) => { body.binding.semanticTypes = ["map.route"]; }, (body) => { body.binding.fieldProjection = ["name"]; }, ]; for (const mutate of mutations) { const body = upsertPayload(); mutate(body); const response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body }); assert.equal(response.status, 403); assert.equal(response.body.error, "foundry_binding_target_forbidden"); } assert.equal(calls.preflight.length, baselinePreflight); assert.equal(calls.upsert.length, baselineUpsert); }); await t.test("wire schema enforces the canonical UUID and identifier grammar", async () => { const mutations = [ (body) => { body.applicationId = "00000000-0000-0000-0000-000000000000"; }, (body) => { body.binding.id = "Fleet-Live-Points"; }, (body) => { body.binding.dataProductId = "Fleet.Positions.Current.V1"; }, (body) => { body.binding.slotId = "-points"; }, (body) => { body.binding.semanticTypes = ["Map.Moving_Object"]; }, (body) => { body.binding.fieldProjection = ["Display_Name"]; }, (body) => { body.idempotencyKey = "X"; }, (body) => { body.binding.dataProductId = ` ${TARGET.dataProductId}`; }, ]; for (const mutate of mutations) { const body = upsertPayload(); mutate(body); const response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body }); assert.equal(response.status, 400); assert.equal(response.body.error, "foundry_binding_request_invalid"); } }); await t.test("secret material, transport scope, browser origin and shared tokens are rejected", async () => { const wrongSchema = upsertPayload(); wrongSchema.schemaVersion = "nodedc.foundry.binding-upsert/v2"; let response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: wrongSchema }); assert.equal(response.status, 400); assert.equal(response.body.error, "foundry_binding_schema_version_unsupported"); const withSecret = upsertPayload(); withSecret.binding.token = "ndc_edprb_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: withSecret }); assert.equal(response.status, 400); assert.equal(response.body.error, "foundry_binding_secret_material_forbidden"); const withProvider = upsertPayload(); withProvider.binding.providerId = "gelios"; response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: withProvider }); assert.equal(response.status, 400); assert.equal(response.body.error, "foundry_binding_transport_or_scope_forbidden"); response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token, headers: { Origin: "https://foundry.nodedc.ru" }, }); assert.equal(response.status, 403); assert.equal(response.body.error, "foundry_binding_browser_origin_forbidden"); response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { rawAuthorization: "Bearer shared-platform-token" }); assert.equal(response.status, 401); assert.equal(response.body.error, "foundry_binding_unauthorized"); response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { headers: { Cookie: "nodedc_foundry_session=browser-session" }, }); assert.equal(response.status, 401); assert.equal(response.body.error, "foundry_binding_unauthorized"); }); await t.test("writable and symlink grant records are rejected", async () => { const writableToken = createFoundryBindingGrantToken(); await writeGrant(grantsDir, writableToken, grantFor(writableToken)); const writablePath = join(grantsDir, foundryBindingGrantFileName(writableToken)); await chmod(writablePath, 0o600); let response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: writableToken }); assert.equal(response.status, 503); assert.equal(response.body.error, "foundry_binding_grant_file_invalid"); const symlinkToken = createFoundryBindingGrantToken(); const sourcePath = join(grantsDir, `source-${randomSuffix()}`); await writeFile(sourcePath, `${JSON.stringify(grantFor(symlinkToken))}\n`, { mode: 0o400 }); await chmod(sourcePath, 0o400); await symlink(sourcePath, join(grantsDir, foundryBindingGrantFileName(symlinkToken))); response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: symlinkToken }); assert.equal(response.status, 503); assert.equal(response.body.error, "foundry_binding_grant_file_invalid"); }); await t.test("grant expiry is enforced", async () => { const expiredToken = createFoundryBindingGrantToken(); await writeGrant(grantsDir, expiredToken, grantFor(expiredToken, { issuedAt: "2026-07-14T10:00:00.000Z", expiresAt: "2026-07-15T11:59:59.000Z", })); const response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: expiredToken }); assert.equal(response.status, 401); assert.equal(response.body.error, "foundry_binding_grant_expired"); }); await t.test("revocation and rotation take effect on the next request", async () => { let response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token }); assert.equal(response.status, 200); await writeGrant(grantsDir, token, grantFor(token, { active: false })); response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token }); assert.equal(response.status, 401); assert.equal(response.body.error, "foundry_binding_grant_inactive"); const rotatedToken = createFoundryBindingGrantToken(); await writeGrant(grantsDir, rotatedToken, grantFor(rotatedToken)); response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: rotatedToken }); assert.equal(response.status, 200); }); }); function grantFor(token, overrides = {}) { return { schemaVersion: FOUNDRY_BINDING_GRANT_SCHEMA_VERSION, grantId: "foundry-binding-grant-42", tokenHash: foundryBindingGrantFileName(token), active: true, issuedAt: "2026-07-15T11:00:00.000Z", expiresAt: "2026-07-16T12:00:00.000Z", actorId: "engine-agent-42", ownerKey: "workspace-42", actions: [FOUNDRY_BINDING_CATALOG_ACTION, FOUNDRY_BINDING_UPSERT_ACTION], targets: [structuredClone(TARGET)], ...overrides, }; } function upsertPayload() { return { schemaVersion: FOUNDRY_BINDING_UPSERT_SCHEMA_VERSION, applicationId: TARGET.applicationId, pageId: TARGET.pageId, idempotencyKey: "foundry-binding-3b7e28b52f7212a843fa5aa4809b03ea", binding: { id: TARGET.bindingId, dataProductId: TARGET.dataProductId, slotId: TARGET.slotId, semanticTypes: [...TARGET.semanticTypes], fieldProjection: [...TARGET.fieldProjection], }, }; } async function writeGrant(grantsDir, token, grant) { const target = join(grantsDir, foundryBindingGrantFileName(token)); const temp = `${target}.${randomSuffix()}.tmp`; await writeFile(temp, `${JSON.stringify(grant)}\n`, { mode: 0o400 }); await chmod(temp, 0o400); await rename(temp, target); } function randomSuffix() { return Math.random().toString(16).slice(2); } async function request(baseUrl, path, { token, rawAuthorization, method = "GET", body, headers = {} } = {}) { const response = await fetch(`${baseUrl}${path}`, { method, headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(rawAuthorization ? { Authorization: rawAuthorization } : {}), ...(body ? { "Content-Type": "application/json" } : {}), ...headers, }, body: body ? JSON.stringify(body) : undefined, }); return { status: response.status, headers: response.headers, body: await response.json() }; }