@kya-os/checkpoint-wasm-runtime 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/adapters.js +0 -2
  3. package/dist/adapters.mjs +0 -2
  4. package/dist/edge.js +0 -2
  5. package/dist/edge.mjs +0 -2
  6. package/dist/engine-edge.js +0 -2
  7. package/dist/engine-edge.mjs +0 -2
  8. package/dist/engine.js +0 -2
  9. package/dist/engine.mjs +0 -2
  10. package/dist/index.js +0 -2
  11. package/dist/index.mjs +0 -2
  12. package/dist/node.js +0 -2
  13. package/dist/node.mjs +0 -2
  14. package/dist/orchestrator-edge.d.mts +5 -199
  15. package/dist/orchestrator-edge.d.ts +5 -199
  16. package/dist/orchestrator-edge.js +0 -2
  17. package/dist/orchestrator-edge.mjs +0 -2
  18. package/dist/orchestrator-node.d.mts +49 -0
  19. package/dist/orchestrator-node.d.ts +49 -0
  20. package/dist/orchestrator-node.js +547 -0
  21. package/dist/orchestrator-node.mjs +538 -0
  22. package/dist/orchestrator.d.mts +4 -47
  23. package/dist/orchestrator.d.ts +4 -47
  24. package/dist/orchestrator.js +0 -2
  25. package/dist/orchestrator.mjs +0 -2
  26. package/dist/render-decision-C1a-iuiW.d.mts +200 -0
  27. package/dist/render-decision-Dsjwt96g.d.ts +200 -0
  28. package/package.json +11 -1
  29. package/dist/adapters.js.map +0 -1
  30. package/dist/adapters.mjs.map +0 -1
  31. package/dist/edge.js.map +0 -1
  32. package/dist/edge.mjs.map +0 -1
  33. package/dist/engine-edge.js.map +0 -1
  34. package/dist/engine-edge.mjs.map +0 -1
  35. package/dist/engine.js.map +0 -1
  36. package/dist/engine.mjs.map +0 -1
  37. package/dist/index.js.map +0 -1
  38. package/dist/index.mjs.map +0 -1
  39. package/dist/node.js.map +0 -1
  40. package/dist/node.mjs.map +0 -1
  41. package/dist/orchestrator-edge.js.map +0 -1
  42. package/dist/orchestrator-edge.mjs.map +0 -1
  43. package/dist/orchestrator.js.map +0 -1
  44. package/dist/orchestrator.mjs.map +0 -1
@@ -0,0 +1,49 @@
1
+ import { V as VerifyResult } from './types-D0j85fF0.js';
2
+ import { V as VerifyRequestOpts, I as IncomingHttpLike } from './render-decision-Dsjwt96g.js';
3
+ export { B as BuildAgentRequestOpts, R as RenderedResponse, b as buildAgentRequest, e as extractAgentDid, a as extractCredentialStatusUrl, c as extractIssuer, h as hasMalformedJwsBody, r as renderDecisionAsResponse } from './render-decision-Dsjwt96g.js';
4
+ import '@kya-os/checkpoint-shared';
5
+ import './adapters.js';
6
+
7
+ /**
8
+ * `verifyRequest` async orchestrator — Phase C.2.
9
+ *
10
+ * The single entry point Phase D (Next.js) and Phase E (Express)
11
+ * compose. Async pre-fetch on the host side; one sync `engineVerify`
12
+ * call across the WASM boundary. Sync-engine / async-host invariant
13
+ * (H-1 § 4.5) preserved.
14
+ *
15
+ * Orchestration:
16
+ * 1. Translate HTTP → AgentRequest (no engine I/O).
17
+ * 2. Extract identifiers (issuer DID, agent DID, status-list URL).
18
+ * 3. Conditional Promise.all over the *applicable* adapters —
19
+ * anonymous PlainHttp gets no network calls; signed MCP-I gets
20
+ * DID + status-list + reputation in parallel.
21
+ * 4. Translate adapter errors to verdicts where the architect-
22
+ * ratified posture says so:
23
+ * - DidResolver throw → Block(ParseError) verdict
24
+ * - StatusListCache throw → re-throw (host renders 503)
25
+ * - Reputation throw → impossible (Phase B § 4.5)
26
+ * 5. Compute the tenant Decision via PolicyEvaluator over the
27
+ * resolved reputation.
28
+ * 6. Build ContextSpec, call `engineVerify`, return VerifyResult.
29
+ *
30
+ * Cedar-1 forward-compat: step (5) is the only place the
31
+ * PolicyEvaluator interface gets exercised. When Cedar-1 swaps
32
+ * implementations, this orchestrator does not change.
33
+ */
34
+
35
+ /**
36
+ * Factory — constructs a `verifyRequest` closure that remembers the
37
+ * one-shot Argus-not-configured warning state. Use this when the
38
+ * host wrapper wants the startup log; call `verifyRequest` directly
39
+ * (the loose function below) if you don't.
40
+ */
41
+ declare function makeVerifyRequest(opts: VerifyRequestOpts): (req: IncomingHttpLike) => Promise<VerifyResult>;
42
+ /**
43
+ * Single-shot async entry. Use [`makeVerifyRequest`] in long-lived
44
+ * hosts (so the Argus warning is one-shot per process); use this
45
+ * loose form in tests + one-off invocations.
46
+ */
47
+ declare function verifyRequest(req: IncomingHttpLike, opts: VerifyRequestOpts): Promise<VerifyResult>;
48
+
49
+ export { IncomingHttpLike, VerifyRequestOpts, makeVerifyRequest, verifyRequest };
@@ -0,0 +1,547 @@
1
+ 'use strict';
2
+
3
+ // src/engine/adapters/outbound-url-policy.ts
4
+ var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "metadata", "metadata.google.internal"]);
5
+ var UnsafeOutboundUrl = class extends Error {
6
+ kind = "UnsafeOutboundUrl";
7
+ };
8
+ function assertSafeHttpsUrl(rawUrl, label = "outbound URL") {
9
+ let parsed;
10
+ try {
11
+ parsed = new URL(rawUrl);
12
+ } catch {
13
+ throw new UnsafeOutboundUrl(`${label} must be a valid URL: ${rawUrl}`);
14
+ }
15
+ if (parsed.protocol !== "https:") {
16
+ throw new UnsafeOutboundUrl(`${label} must use https: ${rawUrl}`);
17
+ }
18
+ if (parsed.username || parsed.password) {
19
+ throw new UnsafeOutboundUrl(`${label} must not contain credentials: ${rawUrl}`);
20
+ }
21
+ const hostname = normalizeHostname(parsed.hostname);
22
+ if (!hostname || isBlockedHostname(hostname)) {
23
+ throw new UnsafeOutboundUrl(`${label} targets a local or private host: ${rawUrl}`);
24
+ }
25
+ return rawUrl;
26
+ }
27
+ function normalizeHostname(hostname) {
28
+ let normalized = hostname.trim().toLowerCase();
29
+ if (normalized.startsWith("[") && normalized.endsWith("]")) {
30
+ normalized = normalized.slice(1, -1);
31
+ }
32
+ while (normalized.endsWith(".")) {
33
+ normalized = normalized.slice(0, -1);
34
+ }
35
+ return normalized;
36
+ }
37
+ function isBlockedHostname(hostname) {
38
+ if (BLOCKED_HOSTNAMES.has(hostname) || hostname.endsWith(".localhost")) {
39
+ return true;
40
+ }
41
+ const ipv4 = parseIpv4(hostname);
42
+ if (ipv4) {
43
+ return isBlockedIpv4(ipv4);
44
+ }
45
+ return isBlockedIpv6(hostname);
46
+ }
47
+ function parseIpv4(hostname) {
48
+ const parts = hostname.split(".");
49
+ if (parts.length !== 4) return null;
50
+ const octets = parts.map((part) => {
51
+ if (!/^\d{1,3}$/.test(part)) return Number.NaN;
52
+ const value = Number(part);
53
+ return value >= 0 && value <= 255 ? value : Number.NaN;
54
+ });
55
+ if (octets.some(Number.isNaN)) return null;
56
+ return octets;
57
+ }
58
+ function isBlockedIpv4([a, b]) {
59
+ return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19) || a >= 224;
60
+ }
61
+ function isBlockedIpv6(hostname) {
62
+ if (!hostname.includes(":")) return false;
63
+ const ipv4Mapped = hostname.match(/(?:^|:)ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
64
+ if (ipv4Mapped) {
65
+ const ipv4 = parseIpv4(ipv4Mapped[1]);
66
+ return ipv4 ? isBlockedIpv4(ipv4) : true;
67
+ }
68
+ if (hostname === "::" || hostname === "::1" || hostname === "0:0:0:0:0:0:0:1") {
69
+ return true;
70
+ }
71
+ const firstSegment = Number.parseInt(hostname.split(":")[0] || "0", 16);
72
+ if (Number.isNaN(firstSegment)) return true;
73
+ return (firstSegment & 65024) === 64512 || // unique local fc00::/7
74
+ (firstSegment & 65472) === 65152 || // link-local fe80::/10
75
+ (firstSegment & 65280) === 65280;
76
+ }
77
+
78
+ // src/engine/index.ts
79
+ function engineVerify(input, ctx) {
80
+ const result = (void 0)(input, ctx);
81
+ return result;
82
+ }
83
+
84
+ // src/engine/adapters/util.ts
85
+ function base64UrlDecode(input) {
86
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
87
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
88
+ return new Uint8Array(Buffer.from(padded + padding, "base64"));
89
+ }
90
+
91
+ // src/engine/orchestrator/build-agent-request.ts
92
+ function buildAgentRequest(req, opts = {}) {
93
+ const mcpI = tryBuildMcpIFromBody(req);
94
+ if (mcpI) {
95
+ return { protocol: "McpI", request: mcpI };
96
+ }
97
+ if (opts.legacyEnvelopeFallback) {
98
+ const legacyMcpI = tryBuildMcpIFromLegacyHeader(req);
99
+ if (legacyMcpI) {
100
+ return { protocol: "McpI", request: legacyMcpI };
101
+ }
102
+ }
103
+ if (getHeader(req, "signature-input")) {
104
+ return { protocol: "HttpSigned", request: buildHttpSigned(req) };
105
+ }
106
+ return { protocol: "PlainHttp", request: buildPlainHttp(req) };
107
+ }
108
+ function hasMalformedJwsBody(req) {
109
+ const parsed = parseBodyAsObject(req.body);
110
+ if (!parsed || typeof parsed !== "object") return false;
111
+ const meta = parsed._meta;
112
+ if (!meta || typeof meta !== "object") return false;
113
+ const proof = meta.proof;
114
+ if (!proof || typeof proof !== "object") return false;
115
+ const jws = proof.jws;
116
+ if (typeof jws !== "string" || jws.length === 0) return false;
117
+ const raw = Array.from(Buffer.from(jws, "utf8"));
118
+ return parseJwsPayloadStruct(raw) === null;
119
+ }
120
+ function extractIssuer(request) {
121
+ if (request.protocol === "McpI") return request.request.payload.iss || null;
122
+ return null;
123
+ }
124
+ function extractAgentDid(request) {
125
+ if (request.protocol === "McpI") return request.request.payload.sub || null;
126
+ return null;
127
+ }
128
+ function extractCredentialStatusUrl(request) {
129
+ if (request.protocol !== "McpI") return null;
130
+ const raw = decodeJwsPayloadJson(request.request.raw);
131
+ if (!raw) return null;
132
+ const vc = raw.vc;
133
+ if (!vc || typeof vc !== "object") return null;
134
+ const credentialStatus = vc.credentialStatus;
135
+ if (!credentialStatus || typeof credentialStatus !== "object") return null;
136
+ const id = credentialStatus.id;
137
+ return typeof id === "string" ? id : null;
138
+ }
139
+ function tryBuildMcpIFromBody(req) {
140
+ const parsed = parseBodyAsObject(req.body);
141
+ if (!parsed) return null;
142
+ const meta = parsed._meta;
143
+ if (!meta || typeof meta !== "object") return null;
144
+ const proof = meta.proof;
145
+ if (!proof || typeof proof !== "object") return null;
146
+ const jws = proof.jws;
147
+ if (typeof jws !== "string" || jws.length === 0) return null;
148
+ const raw = Array.from(Buffer.from(jws, "utf8"));
149
+ const payload = parseJwsPayloadStruct(raw);
150
+ if (!payload) return null;
151
+ return { raw, payload };
152
+ }
153
+ function parseBodyAsObject(body) {
154
+ if (!body) return null;
155
+ if (Buffer.isBuffer(body)) {
156
+ try {
157
+ return JSON.parse(body.toString("utf8"));
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+ if (typeof body === "string") {
163
+ try {
164
+ return JSON.parse(body);
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+ if (typeof body === "object") return body;
170
+ return null;
171
+ }
172
+ function tryBuildMcpIFromLegacyHeader(req) {
173
+ const header = getHeader(req, "kya-delegation");
174
+ if (!header) return null;
175
+ let parsed;
176
+ try {
177
+ parsed = JSON.parse(header);
178
+ } catch {
179
+ return null;
180
+ }
181
+ if (!parsed || typeof parsed !== "object") return null;
182
+ const obj = parsed;
183
+ const protectedSeg = obj.protected;
184
+ const payloadSeg = obj.payload;
185
+ const signatureSeg = obj.signature;
186
+ if (typeof protectedSeg !== "string" || typeof payloadSeg !== "string" || typeof signatureSeg !== "string") {
187
+ return null;
188
+ }
189
+ const compact = `${protectedSeg}.${payloadSeg}.${signatureSeg}`;
190
+ const raw = Array.from(Buffer.from(compact, "utf8"));
191
+ const payload = parseJwsPayloadStruct(raw);
192
+ if (!payload) return null;
193
+ return { raw, payload };
194
+ }
195
+ function parseJwsPayloadStruct(rawBytes) {
196
+ const json = decodeJwsPayloadJson(rawBytes);
197
+ if (!json || typeof json !== "object") return null;
198
+ return projectMcpIPayload(json);
199
+ }
200
+ function decodeJwsPayloadJson(rawBytes) {
201
+ const text = Buffer.from(rawBytes).toString("utf8");
202
+ const segments = text.split(".");
203
+ if (segments.length !== 3) return null;
204
+ let decoded;
205
+ try {
206
+ decoded = base64UrlDecode(segments[1]);
207
+ } catch {
208
+ return null;
209
+ }
210
+ try {
211
+ return JSON.parse(Buffer.from(decoded).toString("utf8"));
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+ function projectMcpIPayload(raw) {
217
+ const aud = raw.aud;
218
+ const iss = raw.iss;
219
+ const sub = raw.sub;
220
+ const nonce = raw.nonce;
221
+ const sessionId = raw.sessionId;
222
+ const ts = raw.ts;
223
+ const requestHash = raw.requestHash;
224
+ const responseHash = raw.responseHash;
225
+ if (typeof aud !== "string" || typeof iss !== "string" || typeof sub !== "string" || typeof nonce !== "string" || typeof sessionId !== "string" || typeof ts !== "number" || typeof requestHash !== "string" || typeof responseHash !== "string") {
226
+ return null;
227
+ }
228
+ return { aud, iss, sub, nonce, sessionId, ts, requestHash, responseHash };
229
+ }
230
+ function buildHttpSigned(req) {
231
+ return {
232
+ raw: bodyAsBytes(req.body),
233
+ method: req.method,
234
+ path: req.url,
235
+ headers: flattenHeaders(req.headers)
236
+ };
237
+ }
238
+ function buildPlainHttp(req) {
239
+ return {
240
+ raw: bodyAsBytes(req.body),
241
+ method: req.method,
242
+ path: req.url,
243
+ headers: flattenHeaders(req.headers),
244
+ userAgent: getHeader(req, "user-agent") ?? null,
245
+ remoteIp: req.remoteAddress ?? null
246
+ };
247
+ }
248
+ function getHeader(req, name) {
249
+ const lowered = name.toLowerCase();
250
+ for (const [key, value] of Object.entries(req.headers)) {
251
+ if (key.toLowerCase() !== lowered) continue;
252
+ if (Array.isArray(value)) return value[0] ?? null;
253
+ if (typeof value === "string") return value;
254
+ }
255
+ return null;
256
+ }
257
+ function flattenHeaders(headers) {
258
+ const out = [];
259
+ for (const [key, value] of Object.entries(headers)) {
260
+ if (value === void 0) continue;
261
+ if (Array.isArray(value)) {
262
+ for (const v of value) out.push([key, v]);
263
+ } else {
264
+ out.push([key, value]);
265
+ }
266
+ }
267
+ return out;
268
+ }
269
+ function bodyAsBytes(body) {
270
+ if (!body) return [];
271
+ if (Buffer.isBuffer(body)) return Array.from(body);
272
+ if (typeof body === "string") return Array.from(Buffer.from(body, "utf8"));
273
+ if (typeof body === "object") return Array.from(Buffer.from(JSON.stringify(body), "utf8"));
274
+ return [];
275
+ }
276
+
277
+ // src/engine/orchestrator/verify-request.ts
278
+ var DEFAULT_REPUTATION_BASELINE = 1;
279
+ function makeVerifyRequest(opts) {
280
+ let argusWarningLogged = false;
281
+ const log = opts.logger ?? defaultLogger;
282
+ return async function verifyRequest2(req) {
283
+ if (!argusWarningLogged && !opts.argusUrl) {
284
+ log(
285
+ '[Checkpoint] WARNING: Argus URL not configured; reputation will degrade to baseline 1.0 ("trust by default"). Set ARGUS_API_URL to enable.'
286
+ );
287
+ argusWarningLogged = true;
288
+ }
289
+ return verifyRequest_internal(req, opts);
290
+ };
291
+ }
292
+ async function verifyRequest(req, opts) {
293
+ return verifyRequest_internal(req, opts);
294
+ }
295
+ async function verifyRequest_internal(req, opts) {
296
+ if (hasMalformedJwsBody(req)) {
297
+ return blockWithParseError("malformed JWS body", opts.enforcementMode);
298
+ }
299
+ const agentRequest = buildAgentRequest(req, {
300
+ legacyEnvelopeFallback: opts.legacyEnvelopeFallback
301
+ });
302
+ const issuer = extractIssuer(agentRequest);
303
+ const agentDid = extractAgentDid(agentRequest);
304
+ const credentialStatusUrl = extractCredentialStatusUrl(agentRequest);
305
+ const baseline = opts.reputationBaseline ?? DEFAULT_REPUTATION_BASELINE;
306
+ const [didResult, statusResult, repResult] = await Promise.allSettled([
307
+ issuer ? opts.didResolver.resolve(issuer) : Promise.resolve(null),
308
+ credentialStatusUrl ? fetchCredentialStatus(credentialStatusUrl, opts.statusListCache) : Promise.resolve(null),
309
+ agentDid ? opts.reputationOracle.score(agentDid) : Promise.resolve(baseline)
310
+ ]);
311
+ if (didResult.status === "rejected") {
312
+ if (isDidResolverError(didResult.reason)) {
313
+ return blockWithParseError(
314
+ didResult.reason instanceof Error ? didResult.reason.message : String(didResult.reason),
315
+ opts.enforcementMode
316
+ );
317
+ }
318
+ throw didResult.reason;
319
+ }
320
+ if (statusResult.status === "rejected") {
321
+ if (statusResult.reason instanceof UnsafeOutboundUrl) {
322
+ return blockWithParseError(statusResult.reason.message, opts.enforcementMode);
323
+ }
324
+ throw statusResult.reason;
325
+ }
326
+ if (repResult.status === "rejected") {
327
+ throw repResult.reason;
328
+ }
329
+ const didDoc = didResult.value;
330
+ const revokedIndices = statusResult.value;
331
+ const repScore = repResult.value;
332
+ const tenantDecision = await opts.policyEvaluator.evaluate({
333
+ tenantHost: opts.tenantHost,
334
+ reputation: repScore
335
+ });
336
+ const ctx = {
337
+ didDocs: didDoc && issuer ? { [issuer]: didDoc } : {},
338
+ revoked: revokedIndices !== null && credentialStatusUrl ? { [credentialStatusUrl]: revokedIndices } : {},
339
+ reputation: agentDid ? { [agentDid]: repScore } : {},
340
+ tenantDecision,
341
+ nowUnix: opts.clock.nowUnix(),
342
+ enforcementMode: opts.enforcementMode
343
+ };
344
+ return engineVerify(agentRequest, ctx);
345
+ }
346
+ async function fetchCredentialStatus(credentialStatusUrl, statusListCache) {
347
+ assertSafeHttpsUrl(credentialStatusUrl, "credential status URL");
348
+ return statusListCache.fetch(credentialStatusUrl);
349
+ }
350
+ function isDidResolverError(err) {
351
+ if (!(err instanceof Error)) return false;
352
+ const kind = err.kind;
353
+ return kind === "DidNotFound" || kind === "DidResolverTimeout" || kind === "DidResolverError" || kind === "MalformedDid" || kind === "UnsupportedKeyType" || kind === "UnsupportedDidMethod";
354
+ }
355
+ function blockWithParseError(detail, enforcementMode) {
356
+ return {
357
+ decision: {
358
+ kind: "Block",
359
+ reason: {
360
+ kind: "ParseError",
361
+ detail
362
+ }
363
+ },
364
+ enforcementMode,
365
+ engineInfo: {
366
+ name: "checkpoint-engine-wasm",
367
+ version: "0.0.0-host-synth",
368
+ rulesetHash: "sha256:host-synthesized",
369
+ rulesetVersion: "0.0.0-host-synth",
370
+ extras: { synthesized: true }
371
+ }
372
+ };
373
+ }
374
+ function defaultLogger(msg) {
375
+ console.warn(msg);
376
+ }
377
+
378
+ // src/engine/orchestrator/render-decision.ts
379
+ function renderDecisionAsResponse(result) {
380
+ const baseHeaders = buildBaseHeaders(result);
381
+ if (result.enforcementMode === "observe") {
382
+ return {
383
+ status: null,
384
+ headers: {
385
+ ...baseHeaders,
386
+ "X-Checkpoint-Mode": "observe",
387
+ "X-Checkpoint-Would-Have-Been": result.decision.kind,
388
+ ...wouldHaveBeenReasonHeader(result)
389
+ }
390
+ };
391
+ }
392
+ switch (result.decision.kind) {
393
+ case "Permit":
394
+ return {
395
+ status: null,
396
+ headers: { ...baseHeaders, "X-Checkpoint-Decision": "permit" }
397
+ };
398
+ case "Block": {
399
+ const reason = result.decision.reason;
400
+ return {
401
+ status: httpStatusForBlockReason(reason),
402
+ headers: {
403
+ ...baseHeaders,
404
+ ...blockHeaders(reason),
405
+ "X-Checkpoint-Decision": "block",
406
+ "X-Checkpoint-Reason": reason.kind
407
+ },
408
+ body: blockResponseBody(reason)
409
+ };
410
+ }
411
+ case "Challenge": {
412
+ const params = result.decision.params;
413
+ return {
414
+ status: 401,
415
+ headers: {
416
+ ...baseHeaders,
417
+ "X-Checkpoint-Decision": "challenge",
418
+ "X-Checkpoint-Challenge": params.nonce
419
+ },
420
+ body: {
421
+ challenge: params
422
+ }
423
+ };
424
+ }
425
+ case "Redirect": {
426
+ const target = result.decision.target;
427
+ return {
428
+ status: 302,
429
+ headers: {
430
+ ...baseHeaders,
431
+ "X-Checkpoint-Decision": "redirect",
432
+ Location: target.url,
433
+ "X-Checkpoint-Redirect-Reason": target.reason
434
+ }
435
+ };
436
+ }
437
+ case "Instruct": {
438
+ const payload = result.decision.payload;
439
+ return {
440
+ status: 422,
441
+ headers: {
442
+ ...baseHeaders,
443
+ "X-Checkpoint-Decision": "instruct",
444
+ "Content-Type": "application/problem+json"
445
+ },
446
+ body: {
447
+ type: payload.problem,
448
+ title: payload.title,
449
+ suggestedActions: payload.suggestedActions
450
+ }
451
+ };
452
+ }
453
+ }
454
+ }
455
+ function buildBaseHeaders(result) {
456
+ const headers = {
457
+ "X-Checkpoint-Engine": result.engineInfo.name,
458
+ "X-Checkpoint-Engine-Version": result.engineInfo.version
459
+ };
460
+ if (result.engineInfo.rulesetHash) {
461
+ headers["X-Checkpoint-Ruleset-Hash"] = result.engineInfo.rulesetHash;
462
+ }
463
+ return headers;
464
+ }
465
+ function httpStatusForBlockReason(reason) {
466
+ switch (reason.kind) {
467
+ case "Unauthenticated":
468
+ case "Expired":
469
+ return 401;
470
+ case "ParseError":
471
+ return 400;
472
+ case "InvalidSignature":
473
+ case "Revoked":
474
+ case "OutOfScope":
475
+ case "LowReputation":
476
+ case "PolicyDenied":
477
+ return 403;
478
+ }
479
+ }
480
+ function blockHeaders(reason) {
481
+ if (reason.kind === "Unauthenticated") {
482
+ return { "WWW-Authenticate": 'KyaProof realm="checkpoint"' };
483
+ }
484
+ return {};
485
+ }
486
+ function blockResponseBody(reason) {
487
+ switch (reason.kind) {
488
+ case "Revoked":
489
+ case "InvalidSignature":
490
+ case "Unauthenticated":
491
+ case "Expired":
492
+ return { error: humanError(reason.kind), reason: reason.kind };
493
+ case "OutOfScope":
494
+ return {
495
+ error: "requested scope is not granted",
496
+ reason: "OutOfScope",
497
+ requested: reason.requested,
498
+ granted: reason.granted
499
+ };
500
+ case "LowReputation":
501
+ return {
502
+ error: "agent reputation below tenant threshold",
503
+ reason: "LowReputation",
504
+ score: reason.score,
505
+ threshold: reason.threshold
506
+ };
507
+ case "PolicyDenied":
508
+ return {
509
+ error: "tenant policy denied the request",
510
+ reason: "PolicyDenied",
511
+ detail: reason.detail
512
+ };
513
+ case "ParseError":
514
+ return {
515
+ error: "request envelope could not be parsed",
516
+ reason: "ParseError",
517
+ detail: reason.detail
518
+ };
519
+ }
520
+ }
521
+ function humanError(kind) {
522
+ switch (kind) {
523
+ case "Revoked":
524
+ return "credential has been revoked";
525
+ case "InvalidSignature":
526
+ return "request signature failed verification";
527
+ case "Unauthenticated":
528
+ return "authentication required";
529
+ case "Expired":
530
+ return "credential is expired";
531
+ }
532
+ }
533
+ function wouldHaveBeenReasonHeader(result) {
534
+ if (result.decision.kind === "Block") {
535
+ return { "X-Checkpoint-Would-Have-Been-Reason": result.decision.reason.kind };
536
+ }
537
+ return {};
538
+ }
539
+
540
+ exports.buildAgentRequest = buildAgentRequest;
541
+ exports.extractAgentDid = extractAgentDid;
542
+ exports.extractCredentialStatusUrl = extractCredentialStatusUrl;
543
+ exports.extractIssuer = extractIssuer;
544
+ exports.hasMalformedJwsBody = hasMalformedJwsBody;
545
+ exports.makeVerifyRequest = makeVerifyRequest;
546
+ exports.renderDecisionAsResponse = renderDecisionAsResponse;
547
+ exports.verifyRequest = verifyRequest;