@suveren/gateway 0.2.12 → 0.3.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 (170) hide show
  1. package/README.md +21 -7
  2. package/dist/control-plane/index.mjs +146 -2
  3. package/dist/mcp-server/http.mjs +80 -38
  4. package/dist/ui/assets/index-RkZBhIxU.js +105 -0
  5. package/dist/ui/index.html +1 -1
  6. package/node_modules/@hap/core/dist/index.d.mts +147 -3
  7. package/node_modules/@hap/core/dist/index.d.ts +147 -3
  8. package/node_modules/@hap/core/dist/index.js +74 -10
  9. package/node_modules/@hap/core/dist/index.mjs +69 -10
  10. package/node_modules/@hap/core/package.json +1 -1
  11. package/node_modules/@hap/core/src/gatekeeper.ts +4 -4
  12. package/node_modules/@hap/core/src/identity.ts +77 -0
  13. package/node_modules/@hap/core/src/index.ts +2 -0
  14. package/node_modules/@hap/core/src/intent-disclosure.ts +69 -0
  15. package/node_modules/@hap/core/src/types.ts +62 -2
  16. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/README.md +16 -1
  17. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/index.js +4 -20
  18. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/read.js +17 -17
  19. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/json.js +60 -32
  20. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/raw.js +3 -3
  21. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/text.js +3 -3
  22. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/urlencoded.js +16 -20
  23. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/utils.js +18 -16
  24. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/LICENSE +22 -0
  25. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/README.md +69 -0
  26. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +26 -0
  27. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js +170 -0
  28. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -0
  29. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/package.json +52 -0
  30. package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/package.json +23 -10
  31. package/node_modules/@types/node/README.md +1 -1
  32. package/node_modules/@types/node/assert/strict.d.ts +10 -1
  33. package/node_modules/@types/node/assert.d.ts +144 -17
  34. package/node_modules/@types/node/async_hooks.d.ts +9 -9
  35. package/node_modules/@types/node/buffer.d.ts +0 -5
  36. package/node_modules/@types/node/child_process.d.ts +17 -21
  37. package/node_modules/@types/node/cluster.d.ts +13 -13
  38. package/node_modules/@types/node/console.d.ts +1 -1
  39. package/node_modules/@types/node/crypto.d.ts +454 -571
  40. package/node_modules/@types/node/diagnostics_channel.d.ts +37 -29
  41. package/node_modules/@types/node/dns.d.ts +14 -14
  42. package/node_modules/@types/node/events.d.ts +3 -3
  43. package/node_modules/@types/node/fs/promises.d.ts +4 -4
  44. package/node_modules/@types/node/fs.d.ts +3 -12
  45. package/node_modules/@types/node/globals.d.ts +4 -4
  46. package/node_modules/@types/node/http.d.ts +5 -5
  47. package/node_modules/@types/node/http2.d.ts +5 -1
  48. package/node_modules/@types/node/index.d.ts +0 -3
  49. package/node_modules/@types/node/inspector.d.ts +1 -1
  50. package/node_modules/@types/node/module.d.ts +16 -45
  51. package/node_modules/@types/node/net.d.ts +1 -1
  52. package/node_modules/@types/node/os.d.ts +2 -2
  53. package/node_modules/@types/node/package.json +4 -4
  54. package/node_modules/@types/node/perf_hooks.d.ts +1 -9
  55. package/node_modules/@types/node/process.d.ts +11 -11
  56. package/node_modules/@types/node/readline.d.ts +12 -11
  57. package/node_modules/@types/node/repl.d.ts +10 -10
  58. package/node_modules/@types/node/sqlite.d.ts +1 -1
  59. package/node_modules/@types/node/stream/promises.d.ts +2 -2
  60. package/node_modules/@types/node/stream/web.d.ts +1 -1
  61. package/node_modules/@types/node/stream.d.ts +5 -5
  62. package/node_modules/@types/node/test/reporters.d.ts +1 -1
  63. package/node_modules/@types/node/test.d.ts +8 -8
  64. package/node_modules/@types/node/tls.d.ts +1 -1
  65. package/node_modules/@types/node/ts5.6/index.d.ts +1 -4
  66. package/node_modules/@types/node/ts5.7/index.d.ts +0 -3
  67. package/node_modules/@types/node/url.d.ts +3 -3
  68. package/node_modules/@types/node/util/types.d.ts +1 -1
  69. package/node_modules/@types/node/util.d.ts +3 -20
  70. package/node_modules/@types/node/v8.d.ts +1 -1
  71. package/node_modules/@types/node/vm.d.ts +9 -9
  72. package/node_modules/@types/node/web-globals/domexception.d.ts +15 -0
  73. package/node_modules/@types/node/worker_threads.d.ts +2 -5
  74. package/node_modules/@types/node/zlib.d.ts +3 -3
  75. package/node_modules/fast-uri/index.js +1 -1
  76. package/node_modules/fast-uri/package.json +1 -1
  77. package/node_modules/fast-uri/test/security.test.js +28 -0
  78. package/node_modules/hono/dist/adapter/aws-lambda/handler.js +14 -20
  79. package/node_modules/hono/dist/adapter/lambda-edge/handler.js +8 -2
  80. package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +14 -20
  81. package/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +8 -2
  82. package/node_modules/hono/dist/cjs/helper/css/common.js +3 -1
  83. package/node_modules/hono/dist/cjs/helper/css/index.js +9 -1
  84. package/node_modules/hono/dist/cjs/jsx/base.js +8 -14
  85. package/node_modules/hono/dist/cjs/jsx/components.js +41 -21
  86. package/node_modules/hono/dist/cjs/jsx/context.js +131 -5
  87. package/node_modules/hono/dist/cjs/jsx/streaming.js +9 -7
  88. package/node_modules/hono/dist/cjs/middleware/bearer-auth/index.js +1 -1
  89. package/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -5
  90. package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -32
  91. package/node_modules/hono/dist/cjs/middleware/serve-static/index.js +1 -1
  92. package/node_modules/hono/dist/cjs/middleware/timing/timing.js +3 -1
  93. package/node_modules/hono/dist/cjs/utils/ipaddr.js +6 -1
  94. package/node_modules/hono/dist/helper/css/common.js +3 -1
  95. package/node_modules/hono/dist/helper/css/index.js +9 -1
  96. package/node_modules/hono/dist/jsx/base.js +15 -15
  97. package/node_modules/hono/dist/jsx/components.js +42 -22
  98. package/node_modules/hono/dist/jsx/context.js +129 -5
  99. package/node_modules/hono/dist/jsx/streaming.js +10 -8
  100. package/node_modules/hono/dist/middleware/bearer-auth/index.js +1 -1
  101. package/node_modules/hono/dist/middleware/cors/index.js +2 -5
  102. package/node_modules/hono/dist/middleware/language/language.js +10 -32
  103. package/node_modules/hono/dist/middleware/serve-static/index.js +1 -1
  104. package/node_modules/hono/dist/middleware/timing/timing.js +3 -1
  105. package/node_modules/hono/dist/tsconfig.build.tsbuildinfo +1 -1
  106. package/node_modules/hono/dist/types/adapter/aws-lambda/handler.d.ts +1 -1
  107. package/node_modules/hono/dist/types/jsx/base.d.ts +1 -3
  108. package/node_modules/hono/dist/types/jsx/context.d.ts +39 -0
  109. package/node_modules/hono/dist/utils/ipaddr.js +6 -1
  110. package/node_modules/hono/package.json +19 -13
  111. package/node_modules/http-proxy-middleware/README.md +3 -10
  112. package/node_modules/http-proxy-middleware/dist/errors.d.ts +4 -0
  113. package/node_modules/http-proxy-middleware/dist/errors.js +16 -1
  114. package/node_modules/http-proxy-middleware/dist/factory.d.ts +2 -2
  115. package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body-utils/stringify-form-data.d.ts +7 -0
  116. package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body-utils/stringify-form-data.js +45 -0
  117. package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body.d.ts +1 -1
  118. package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body.js +23 -23
  119. package/node_modules/http-proxy-middleware/dist/handlers/response-interceptor.d.ts +1 -1
  120. package/node_modules/http-proxy-middleware/dist/handlers/response-interceptor.js +1 -1
  121. package/node_modules/http-proxy-middleware/dist/http-proxy-middleware.d.ts +1 -1
  122. package/node_modules/http-proxy-middleware/dist/http-proxy-middleware.js +2 -2
  123. package/node_modules/http-proxy-middleware/dist/legacy/create-proxy-middleware.d.ts +1 -1
  124. package/node_modules/http-proxy-middleware/dist/legacy/create-proxy-middleware.js +1 -1
  125. package/node_modules/http-proxy-middleware/dist/legacy/options-adapter.js +1 -1
  126. package/node_modules/http-proxy-middleware/dist/legacy/types.d.ts +1 -1
  127. package/node_modules/http-proxy-middleware/dist/path-filter.d.ts +1 -1
  128. package/node_modules/http-proxy-middleware/dist/path-filter.js +1 -1
  129. package/node_modules/http-proxy-middleware/dist/path-rewriter.js +1 -1
  130. package/node_modules/http-proxy-middleware/dist/plugins/default/error-response-plugin.js +2 -1
  131. package/node_modules/http-proxy-middleware/dist/plugins/default/logger-plugin.js +3 -3
  132. package/node_modules/http-proxy-middleware/dist/router.js +26 -8
  133. package/node_modules/http-proxy-middleware/dist/status-code.js +15 -12
  134. package/node_modules/http-proxy-middleware/dist/types.d.ts +3 -3
  135. package/node_modules/http-proxy-middleware/dist/types.js +0 -4
  136. package/node_modules/http-proxy-middleware/dist/utils/sanitize.d.ts +1 -0
  137. package/node_modules/http-proxy-middleware/dist/utils/sanitize.js +6 -0
  138. package/node_modules/http-proxy-middleware/package.json +30 -28
  139. package/node_modules/qs/CHANGELOG.md +11 -0
  140. package/node_modules/qs/dist/qs.js +17 -17
  141. package/node_modules/qs/lib/parse.js +19 -8
  142. package/node_modules/qs/lib/utils.js +47 -8
  143. package/node_modules/qs/package.json +6 -5
  144. package/node_modules/qs/test/parse.js +230 -0
  145. package/node_modules/qs/test/utils.js +194 -0
  146. package/node_modules/side-channel/CHANGELOG.md +10 -0
  147. package/node_modules/side-channel/README.md +1 -1
  148. package/node_modules/side-channel/index.js +5 -2
  149. package/node_modules/side-channel/package.json +10 -10
  150. package/node_modules/side-channel/test/index.js +16 -0
  151. package/node_modules/undici-types/agent.d.ts +0 -2
  152. package/node_modules/undici-types/client.d.ts +25 -19
  153. package/node_modules/undici-types/dispatcher.d.ts +7 -33
  154. package/node_modules/undici-types/dispatcher1-wrapper.d.ts +7 -0
  155. package/node_modules/undici-types/fetch.d.ts +24 -4
  156. package/node_modules/undici-types/h2c-client.d.ts +6 -6
  157. package/node_modules/undici-types/handlers.d.ts +1 -2
  158. package/node_modules/undici-types/header.d.ts +5 -0
  159. package/node_modules/undici-types/index.d.ts +3 -1
  160. package/node_modules/undici-types/interceptors.d.ts +1 -1
  161. package/node_modules/undici-types/package.json +1 -1
  162. package/node_modules/undici-types/pool.d.ts +0 -2
  163. package/node_modules/undici-types/proxy-agent.d.ts +2 -2
  164. package/node_modules/undici-types/round-robin-pool.d.ts +0 -2
  165. package/node_modules/undici-types/snapshot-agent.d.ts +4 -0
  166. package/node_modules/undici-types/socks5-proxy-agent.d.ts +2 -2
  167. package/node_modules/undici-types/webidl.d.ts +0 -1
  168. package/package.json +3 -3
  169. package/dist/ui/assets/index-CS16uhqb.js +0 -102
  170. package/node_modules/@types/node/compatibility/iterators.d.ts +0 -21
@@ -50,8 +50,62 @@ function computeContentHash(binding, content) {
50
50
  return `sha256:${sha256Hex(contentCanonicalBytes(binding.kind, content))}`;
51
51
  }
52
52
 
53
- // src/frame.ts
53
+ // src/intent-disclosure.ts
54
54
  import { createHash as createHash2 } from "crypto";
55
+ function sha256Hex2(bytes) {
56
+ return createHash2("sha256").update(bytes, "utf8").digest("hex");
57
+ }
58
+ function computeIntentHash(intentText) {
59
+ return `sha256:${sha256Hex2(canonicalizeText(intentText))}`;
60
+ }
61
+ function intentDisclosureCanonicalBytes(intentCiphertext, approversFrozen) {
62
+ const sorted = [...approversFrozen].sort();
63
+ return `${intentCiphertext}
64
+ ${canonicalize(sorted)}`;
65
+ }
66
+ function computeIntentDisclosureHash(intentCiphertext, approversFrozen) {
67
+ return `sha256:${sha256Hex2(intentDisclosureCanonicalBytes(intentCiphertext, approversFrozen))}`;
68
+ }
69
+
70
+ // src/identity.ts
71
+ function validateSubject(subject) {
72
+ const errors = [];
73
+ const { did, assurance, method, trust_root, verifier, disclose, owner_signature } = subject;
74
+ if (!did) errors.push("subject.did is required");
75
+ if (assurance === "low") {
76
+ if (method !== "self_declared") errors.push(`low assurance requires method "self_declared", got "${method}"`);
77
+ if (trust_root !== "self") errors.push(`low assurance requires trust_root "self", got "${trust_root}"`);
78
+ if (disclose) errors.push("low assurance MUST NOT disclose a name");
79
+ } else if (assurance === "high") {
80
+ if (method === "as_vouched") {
81
+ if (trust_root !== "as") errors.push('as_vouched requires trust_root "as"');
82
+ if (!verifier) errors.push("as_vouched requires a verifier");
83
+ } else if (method === "eudi") {
84
+ if (trust_root !== "external") errors.push('eudi requires trust_root "external"');
85
+ if (!verifier) errors.push("eudi requires a verifier");
86
+ if (!owner_signature) errors.push("eudi requires an owner_signature");
87
+ } else {
88
+ errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
89
+ }
90
+ if (disclose && typeof disclose.name !== "string") errors.push("disclose.name must be a string");
91
+ } else {
92
+ errors.push(`assurance must be "low" or "high", got "${assurance}"`);
93
+ }
94
+ return { valid: errors.length === 0, errors };
95
+ }
96
+ function deriveIdentityLine(subject, { operatorName }) {
97
+ const name = subject?.assurance === "high" ? subject.disclose?.name : void 0;
98
+ if (!name) {
99
+ return `Sent by an AI agent via ${operatorName}`;
100
+ }
101
+ if (subject.method === "eudi") {
102
+ return `Sent by an AI agent of ${name} \u2014 identity verified (EUDI)`;
103
+ }
104
+ return `Sent by an AI agent of ${name} \u2014 verified by ${operatorName}`;
105
+ }
106
+
107
+ // src/frame.ts
108
+ import { createHash as createHash3 } from "crypto";
55
109
  function validateFrameParams(params, profile) {
56
110
  const errors = [];
57
111
  if (!profile.frameSchema) {
@@ -88,7 +142,7 @@ function canonicalFrame(params, profile) {
88
142
  return lines.join("\n");
89
143
  }
90
144
  function frameHash(canonicalFrameString) {
91
- const hash = createHash2("sha256").update(canonicalFrameString, "utf8").digest("hex");
145
+ const hash = createHash3("sha256").update(canonicalFrameString, "utf8").digest("hex");
92
146
  return `sha256:${hash}`;
93
147
  }
94
148
  function computeFrameHash(params, profile) {
@@ -172,17 +226,17 @@ function canonicalContext(params, profile) {
172
226
  }
173
227
  function computeBoundsHash(params, profile) {
174
228
  const canonical = canonicalBounds(params, profile);
175
- const hash = createHash2("sha256").update(canonical, "utf8").digest("hex");
229
+ const hash = createHash3("sha256").update(canonical, "utf8").digest("hex");
176
230
  return `sha256:${hash}`;
177
231
  }
178
232
  function computeContextHash(params, profile) {
179
233
  const canonical = canonicalContext(params, profile);
180
- const hash = createHash2("sha256").update(canonical, "utf8").digest("hex");
234
+ const hash = createHash3("sha256").update(canonical, "utf8").digest("hex");
181
235
  return `sha256:${hash}`;
182
236
  }
183
237
 
184
238
  // src/attestation.ts
185
- import { createHash as createHash3 } from "crypto";
239
+ import { createHash as createHash4 } from "crypto";
186
240
  import * as ed from "@noble/ed25519";
187
241
  function decodeAttestationBlob(blob) {
188
242
  try {
@@ -200,7 +254,7 @@ function encodeAttestationBlob(attestation) {
200
254
  return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
201
255
  }
202
256
  function attestationId(blob) {
203
- const hash = createHash3("sha256").update(blob, "utf8").digest("hex");
257
+ const hash = createHash4("sha256").update(blob, "utf8").digest("hex");
204
258
  return `sha256:${hash}`;
205
259
  }
206
260
  async function verifyAttestationSignature(attestation, publicKeyHex) {
@@ -330,11 +384,11 @@ async function verifyV3(request, profile, publicKeyHex, now, executionLog, error
330
384
  try {
331
385
  checkAttestationExpiry(attestation.payload, now);
332
386
  } catch {
333
- const domainNames = attestation.payload.resolved_domains.map((d) => d.domain).join(", ");
387
+ const domainNames = (attestation.payload.resolved_domains ?? []).map((d) => d.domain).join(", ");
334
388
  errors.push({ code: "TTL_EXPIRED", message: `Attestation for domain "${domainNames}" has expired` });
335
389
  continue;
336
390
  }
337
- for (const rd of attestation.payload.resolved_domains) {
391
+ for (const rd of attestation.payload.resolved_domains ?? []) {
338
392
  coveredDomains.add(rd.domain);
339
393
  }
340
394
  }
@@ -409,11 +463,11 @@ async function verifyV4(request, profile, publicKeyHex, now, executionLog) {
409
463
  try {
410
464
  checkAttestationExpiry(attestation.payload, now);
411
465
  } catch {
412
- const domainNames = attestation.payload.resolved_domains.map((d) => d.domain).join(", ");
466
+ const domainNames = (attestation.payload.resolved_domains ?? []).map((d) => d.domain).join(", ");
413
467
  errors.push({ code: "TTL_EXPIRED", message: `Attestation for domain "${domainNames}" has expired` });
414
468
  continue;
415
469
  }
416
- for (const rd of attestation.payload.resolved_domains) {
470
+ for (const rd of attestation.payload.resolved_domains ?? []) {
417
471
  coveredDomains.add(rd.domain);
418
472
  }
419
473
  }
@@ -716,18 +770,23 @@ export {
716
770
  computeContentHash,
717
771
  computeContextHash,
718
772
  computeFrameHash,
773
+ computeIntentDisclosureHash,
774
+ computeIntentHash,
719
775
  contentCanonicalBytes,
720
776
  decodeAttestationBlob,
777
+ deriveIdentityLine,
721
778
  encodeAttestationBlob,
722
779
  frameHash,
723
780
  getAllProfiles,
724
781
  getProfile,
782
+ intentDisclosureCanonicalBytes,
725
783
  isV4Attestation,
726
784
  listProfiles,
727
785
  registerProfile,
728
786
  validateBoundsParams,
729
787
  validateContextParams,
730
788
  validateFrameParams,
789
+ validateSubject,
731
790
  verify,
732
791
  verifyAttestation,
733
792
  verifyAttestationSignature,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanagencyp/hap-core",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Core types, cryptographic primitives, and verification logic for the Human Agency Protocol",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -131,12 +131,12 @@ async function verifyV3(
131
131
  try {
132
132
  checkAttestationExpiry(attestation.payload, now);
133
133
  } catch {
134
- const domainNames = attestation.payload.resolved_domains.map(d => d.domain).join(', ');
134
+ const domainNames = (attestation.payload.resolved_domains ?? []).map(d => d.domain).join(', ');
135
135
  errors.push({ code: 'TTL_EXPIRED', message: `Attestation for domain "${domainNames}" has expired` });
136
136
  continue;
137
137
  }
138
138
 
139
- for (const rd of attestation.payload.resolved_domains) {
139
+ for (const rd of attestation.payload.resolved_domains ?? []) {
140
140
  coveredDomains.add(rd.domain);
141
141
  }
142
142
  }
@@ -244,12 +244,12 @@ async function verifyV4(
244
244
  try {
245
245
  checkAttestationExpiry(attestation.payload, now);
246
246
  } catch {
247
- const domainNames = attestation.payload.resolved_domains.map(d => d.domain).join(', ');
247
+ const domainNames = (attestation.payload.resolved_domains ?? []).map(d => d.domain).join(', ');
248
248
  errors.push({ code: 'TTL_EXPIRED', message: `Attestation for domain "${domainNames}" has expired` });
249
249
  continue;
250
250
  }
251
251
 
252
- for (const rd of attestation.payload.resolved_domains) {
252
+ for (const rd of attestation.payload.resolved_domains ?? []) {
253
253
  coveredDomains.add(rd.domain);
254
254
  }
255
255
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Identity Assurance (HAP v0.6) — validation + footer derivation for the signed
3
+ * {@link Subject} overlay. See review.md → "Identity Assurance".
4
+ *
5
+ * Two pure helpers, the single source of truth for both the gateway footer and
6
+ * the AS verify page:
7
+ * - {@link validateSubject} — enforces the level/method/trust-root invariants.
8
+ * - {@link deriveIdentityLine} — the human-readable footer line for a subject.
9
+ */
10
+
11
+ import type { Subject } from './types';
12
+
13
+ export interface SubjectValidation {
14
+ valid: boolean;
15
+ errors: string[];
16
+ }
17
+
18
+ /**
19
+ * Enforce the Identity-Assurance invariants on a single subject:
20
+ * - `self_declared` ⇒ `low` / `self`, no disclosed name.
21
+ * - `as_vouched` ⇒ `high` / `as`, `verifier` required.
22
+ * - `eudi` ⇒ `high` / `external`, `verifier` + `owner_signature` required.
23
+ * - `disclose.name` only at `assurance:"high"`.
24
+ */
25
+ export function validateSubject(subject: Subject): SubjectValidation {
26
+ const errors: string[] = [];
27
+ const { did, assurance, method, trust_root, verifier, disclose, owner_signature } = subject;
28
+
29
+ if (!did) errors.push('subject.did is required');
30
+
31
+ if (assurance === 'low') {
32
+ if (method !== 'self_declared') errors.push(`low assurance requires method "self_declared", got "${method}"`);
33
+ if (trust_root !== 'self') errors.push(`low assurance requires trust_root "self", got "${trust_root}"`);
34
+ if (disclose) errors.push('low assurance MUST NOT disclose a name');
35
+ } else if (assurance === 'high') {
36
+ if (method === 'as_vouched') {
37
+ if (trust_root !== 'as') errors.push('as_vouched requires trust_root "as"');
38
+ if (!verifier) errors.push('as_vouched requires a verifier');
39
+ } else if (method === 'eudi') {
40
+ if (trust_root !== 'external') errors.push('eudi requires trust_root "external"');
41
+ if (!verifier) errors.push('eudi requires a verifier');
42
+ if (!owner_signature) errors.push('eudi requires an owner_signature');
43
+ } else {
44
+ errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
45
+ }
46
+ if (disclose && typeof disclose.name !== 'string') errors.push('disclose.name must be a string');
47
+ } else {
48
+ errors.push(`assurance must be "low" or "high", got "${assurance as string}"`);
49
+ }
50
+
51
+ return { valid: errors.length === 0, errors };
52
+ }
53
+
54
+ /**
55
+ * The footer line for a subject — the ONE place this wording lives.
56
+ * The name appears only at `high` with a disclosed name; otherwise no name.
57
+ *
58
+ * - low / no disclosure → "Sent by an AI agent via «operator»"
59
+ * - high / as_vouched → "Sent by an AI agent of «name» — verified by «operator»"
60
+ * - high / eudi → "Sent by an AI agent of «name» — identity verified (EUDI)"
61
+ *
62
+ * `operatorName` is the rendered AS operator (e.g. "Suveren") — never hardcode it
63
+ * upstream; a different operator self-vouches under its own name.
64
+ */
65
+ export function deriveIdentityLine(
66
+ subject: Subject | undefined | null,
67
+ { operatorName }: { operatorName: string },
68
+ ): string {
69
+ const name = subject?.assurance === 'high' ? subject.disclose?.name : undefined;
70
+ if (!name) {
71
+ return `Sent by an AI agent via ${operatorName}`;
72
+ }
73
+ if (subject!.method === 'eudi') {
74
+ return `Sent by an AI agent of ${name} — identity verified (EUDI)`;
75
+ }
76
+ return `Sent by an AI agent of ${name} — verified by ${operatorName}`;
77
+ }
@@ -7,6 +7,8 @@
7
7
  export * from './types';
8
8
  export * from './canonicalize';
9
9
  export * from './content-binding';
10
+ export * from './intent-disclosure';
11
+ export * from './identity';
10
12
  export * from './frame';
11
13
  export * from './attestation';
12
14
  export * from './gatekeeper';
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Intent hashing + intent-disclosure binding (HAP v0.5).
3
+ *
4
+ * Two shared, environment-independent hashes that the attester (gateway), the
5
+ * Authority Server, and any approver MUST compute identically:
6
+ *
7
+ * 1. {@link computeIntentHash} — `gate_content_hashes.intent`. The intent text
8
+ * is canonicalized (Unicode NFC, LF line endings, trailing-whitespace strip
9
+ * — the same rule as content-binding's `canonicalizeText`) before hashing,
10
+ * so independent multi-owner approvers reproduce an identical hash from the
11
+ * same logical statement. See protocol.md → "Intent canonicalization".
12
+ *
13
+ * 2. {@link computeIntentDisclosureHash} — `intent_disclosure_hash`, the C2
14
+ * binding from companion spec `intent-disclosure@0.1`. It commits the
15
+ * encrypted disclosure object (`intent_ciphertext` + `approvers_frozen`)
16
+ * into the SIGNED attestation payload, so a compromised AS cannot swap the
17
+ * ciphertext/wrapped keys or widen/shrink the approver set without breaking
18
+ * the attestation signature.
19
+ *
20
+ * Both produce `sha256:<hex>` to match the frame/bounds/context hash format.
21
+ */
22
+
23
+ import { createHash } from 'crypto';
24
+ import { canonicalize } from './canonicalize';
25
+ import { canonicalizeText } from './content-binding';
26
+
27
+ function sha256Hex(bytes: string): string {
28
+ return createHash('sha256').update(bytes, 'utf8').digest('hex');
29
+ }
30
+
31
+ /**
32
+ * `gate_content_hashes.intent` for an intent statement.
33
+ * Canonicalizes the text (NFC / LF / trailing-whitespace strip) before hashing.
34
+ */
35
+ export function computeIntentHash(intentText: string): string {
36
+ return `sha256:${sha256Hex(canonicalizeText(intentText))}`;
37
+ }
38
+
39
+ /**
40
+ * The exact bytes `intent_disclosure_hash` is taken over, WITHOUT hashing —
41
+ * exposed so an implementer debugging a mismatch can inspect the serialization
42
+ * both sides must agree on:
43
+ *
44
+ * intent_ciphertext ‖ "\n" ‖ JCS(sort(approvers_frozen))
45
+ *
46
+ * where `intent_ciphertext` is the exact base64url string of the disclosure
47
+ * object, `sort` orders the approver DIDs by Unicode code point, and `JCS` is
48
+ * RFC 8785 canonicalization of the sorted array (via {@link canonicalize}).
49
+ */
50
+ export function intentDisclosureCanonicalBytes(
51
+ intentCiphertext: string,
52
+ approversFrozen: string[],
53
+ ): string {
54
+ const sorted = [...approversFrozen].sort();
55
+ return `${intentCiphertext}\n${canonicalize(sorted)}`;
56
+ }
57
+
58
+ /**
59
+ * `intent_disclosure_hash` — `sha256:<hex>` over
60
+ * {@link intentDisclosureCanonicalBytes}. Computed by the attester after
61
+ * encryption and signed into the attestation; recomputed by the AS (at
62
+ * issuance) and by each approver (on receipt) and compared, fail-closed.
63
+ */
64
+ export function computeIntentDisclosureHash(
65
+ intentCiphertext: string,
66
+ approversFrozen: string[],
67
+ ): string {
68
+ return `sha256:${sha256Hex(intentDisclosureCanonicalBytes(intentCiphertext, approversFrozen))}`;
69
+ }
@@ -19,7 +19,7 @@ export interface ResolvedDomain {
19
19
 
20
20
  export interface AttestationPayload {
21
21
  attestation_id: string;
22
- version: '0.3' | '0.4';
22
+ version: '0.3' | '0.4' | '0.5';
23
23
  profile_id: string;
24
24
  /** v0.3 (deprecated) — hash of the authorization frame */
25
25
  frame_hash?: string;
@@ -28,8 +28,37 @@ export interface AttestationPayload {
28
28
  /** v0.4 — hash of the context parameters */
29
29
  context_hash?: string;
30
30
  execution_context_hash: string;
31
- resolved_domains: ResolvedDomain[];
31
+ /**
32
+ * v0.4 (deprecated in v0.5) — domain-scoped owners. Optional so v0.5
33
+ * attestations that carry only `resolved_owners` remain valid. During the
34
+ * transition the AS emits BOTH `resolved_domains` (internal coverage) and
35
+ * `resolved_owners` (the v0.5 signed wire field).
36
+ */
37
+ resolved_domains?: ResolvedDomain[];
38
+ /**
39
+ * v0.5 — the Decision Owner DIDs this attestation covers (person-centric
40
+ * model; replaces the abstract domain in `resolved_domains`). Carried in the
41
+ * signed payload so verifiers bind the action to the human(s) who authorized it.
42
+ */
43
+ resolved_owners?: string[];
32
44
  gate_content_hashes: Record<string, string>;
45
+ /**
46
+ * v0.5 (companion spec `intent-disclosure@0.1`) — present iff the attestation
47
+ * carries an encrypted-intent disclosure object. `sha256:`-prefixed hash that
48
+ * binds the disclosure's `intent_ciphertext` + `approvers_frozen` into the
49
+ * signed payload (see {@link computeIntentDisclosureHash}), so a compromised
50
+ * AS cannot swap the ciphertext/wrapped keys or alter the approver set
51
+ * without invalidating the attestation signature (companion invariant C2).
52
+ */
53
+ intent_disclosure_hash?: string;
54
+ /**
55
+ * v0.6 (Identity Assurance) — optional signed overlay carrying the verified
56
+ * real-world identity of the Decision Owner(s), one entry per owner. Present
57
+ * only when identity is disclosed; an attestation with no `subjects` renders
58
+ * as `low` (pseudonymous, no name). See {@link Subject} and
59
+ * {@link deriveIdentityLine}.
60
+ */
61
+ subjects?: Subject[];
33
62
  /**
34
63
  * v0.4 — commitment mode chosen by the decision owner at attestation time.
35
64
  * - 'automatic': agent may invoke bounded tools without per-action review.
@@ -44,6 +73,37 @@ export interface AttestationPayload {
44
73
  expires_at: number;
45
74
  }
46
75
 
76
+ /**
77
+ * v0.6 Identity Assurance — a signed overlay binding a Decision Owner's verified
78
+ * real-world identity to the attestation, gated by HOW the identity was verified.
79
+ *
80
+ * Two display levels (`assurance`): `low` discloses no name; `high` MAY disclose a
81
+ * name. At `high`, two trust roots: `as` (the AS operator vouches — valid only
82
+ * within its own domain) and `external` (an external eID such as EUDI — carries the
83
+ * owner's own signature, AS-independent). See review.md → "Identity Assurance".
84
+ */
85
+ export interface Subject {
86
+ /** The Decision Owner DID this subject describes (matches an entry in resolved_owners). */
87
+ did: string;
88
+ /** `low` → no name shown; `high` → the name MAY be shown. */
89
+ assurance: 'low' | 'high';
90
+ /** How identity was established. */
91
+ method: 'self_declared' | 'as_vouched' | 'eudi';
92
+ /** Who vouches: `self` (owner's claim), `as` (operator), `external` (eID scheme). */
93
+ trust_root: 'self' | 'as' | 'external';
94
+ /** Verifier id — the AS operator (as_vouched) or the eID scheme (eudi). Required at `high`. */
95
+ verifier?: string;
96
+ /** Disclosed attributes. `name` present ONLY at `assurance:"high"` and when disclosure is on. */
97
+ disclose?: { name: string };
98
+ /** When the underlying verification was performed (unix seconds). */
99
+ verified_at?: number;
100
+ /**
101
+ * eudi only (Phase 2) — the owner's per-event wallet signature, making the
102
+ * identity claim non-repudiable independent of the AS. Null/absent otherwise.
103
+ */
104
+ owner_signature?: string | null;
105
+ }
106
+
47
107
  export interface Attestation {
48
108
  header: AttestationHeader;
49
109
  payload: AttestationPayload;
@@ -51,7 +51,14 @@ $ npm install body-parser
51
51
  ## API
52
52
 
53
53
  ```js
54
+ // Import all parsers
54
55
  const bodyParser = require('body-parser')
56
+
57
+ // Or import individual parsers directly
58
+ const json = require('body-parser/json')
59
+ const urlencoded = require('body-parser/urlencoded')
60
+ const raw = require('body-parser/raw')
61
+ const text = require('body-parser/text')
55
62
  ```
56
63
 
57
64
  The `bodyParser` object exposes various factories to create middlewares. All
@@ -93,11 +100,13 @@ specifies the number of bytes; if it is a string, the value is passed to the
93
100
  [bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
94
101
  to `'100kb'`.
95
102
 
103
+ > It’s recommended not to configure a very high limit and to use the default value whenever possible. Allowing larger payloads increases memory usage because of the resources required for decoding and transformations, and it can also lead to longer response times as more data is processed. By ‘very high’, we mean values above the default, for example payloads of 5 MB or more can already start to introduce these risks. With the default limits, these issues do not occur.
104
+
96
105
  ##### reviver
97
106
 
98
107
  The `reviver` option is passed directly to `JSON.parse` as the second
99
108
  argument. You can find more information on this argument
100
- [in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Example.3A_Using_the_reviver_parameter).
109
+ [in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#the_reviver_parameter).
101
110
 
102
111
  ##### strict
103
112
 
@@ -149,6 +158,8 @@ specifies the number of bytes; if it is a string, the value is passed to the
149
158
  [bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
150
159
  to `'100kb'`.
151
160
 
161
+ > It’s recommended not to configure a very high limit and to use the default value whenever possible. Allowing larger payloads increases memory usage because of the resources required for decoding and transformations, and it can also lead to longer response times as more data is processed. By ‘very high’, we mean values above the default, for example payloads of 5 MB or more can already start to introduce these risks. With the default limits, these issues do not occur.
162
+
152
163
  ##### type
153
164
 
154
165
  The `type` option is used to determine what media type the middleware will
@@ -200,6 +211,8 @@ specifies the number of bytes; if it is a string, the value is passed to the
200
211
  [bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
201
212
  to `'100kb'`.
202
213
 
214
+ > It’s recommended not to configure a very high limit and to use the default value whenever possible. Allowing larger payloads increases memory usage because of the resources required for decoding and transformations, and it can also lead to longer response times as more data is processed. By ‘very high’, we mean values above the default, for example payloads of 5 MB or more can already start to introduce these risks. With the default limits, these issues do not occur.
215
+
203
216
  ##### type
204
217
 
205
218
  The `type` option is used to determine what media type the middleware will
@@ -255,6 +268,8 @@ specifies the number of bytes; if it is a string, the value is passed to the
255
268
  [bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
256
269
  to `'100kb'`.
257
270
 
271
+ > It’s recommended not to configure a very high limit and to use the default value whenever possible. Allowing larger payloads increases memory usage because of the resources required for decoding and transformations, and it can also lead to longer response times as more data is processed. By ‘very high’, we mean values above the default, for example payloads of 5 MB or more can already start to introduce these risks. With the default limits, these issues do not occur.
272
+
258
273
  ##### parameterLimit
259
274
 
260
275
  The `parameterLimit` option controls the maximum number of parameters that
@@ -24,41 +24,25 @@ exports = module.exports = bodyParser
24
24
  * JSON parser.
25
25
  * @public
26
26
  */
27
- Object.defineProperty(exports, 'json', {
28
- configurable: true,
29
- enumerable: true,
30
- get: () => require('./lib/types/json')
31
- })
27
+ exports.json = require('./lib/types/json')
32
28
 
33
29
  /**
34
30
  * Raw parser.
35
31
  * @public
36
32
  */
37
- Object.defineProperty(exports, 'raw', {
38
- configurable: true,
39
- enumerable: true,
40
- get: () => require('./lib/types/raw')
41
- })
33
+ exports.raw = require('./lib/types/raw')
42
34
 
43
35
  /**
44
36
  * Text parser.
45
37
  * @public
46
38
  */
47
- Object.defineProperty(exports, 'text', {
48
- configurable: true,
49
- enumerable: true,
50
- get: () => require('./lib/types/text')
51
- })
39
+ exports.text = require('./lib/types/text')
52
40
 
53
41
  /**
54
42
  * URL-encoded parser.
55
43
  * @public
56
44
  */
57
- Object.defineProperty(exports, 'urlencoded', {
58
- configurable: true,
59
- enumerable: true,
60
- get: () => require('./lib/types/urlencoded')
61
- })
45
+ exports.urlencoded = require('./lib/types/urlencoded')
62
46
 
63
47
  /**
64
48
  * Create a middleware to parse json and urlencoded bodies.
@@ -11,13 +11,13 @@
11
11
  * @private
12
12
  */
13
13
 
14
- var createError = require('http-errors')
15
- var getBody = require('raw-body')
16
- var iconv = require('iconv-lite')
17
- var onFinished = require('on-finished')
18
- var zlib = require('node:zlib')
19
- var hasBody = require('type-is').hasBody
20
- var { getCharset } = require('./utils')
14
+ const createError = require('http-errors')
15
+ const getBody = require('raw-body')
16
+ const iconv = require('iconv-lite')
17
+ const onFinished = require('on-finished')
18
+ const zlib = require('node:zlib')
19
+ const hasBody = require('type-is').hasBody
20
+ const { getCharset } = require('./utils')
21
21
 
22
22
  /**
23
23
  * Module exports.
@@ -63,7 +63,7 @@ function read (req, res, next, parse, debug, options) {
63
63
  return
64
64
  }
65
65
 
66
- var encoding = null
66
+ let encoding = null
67
67
  if (options?.skipCharset !== true) {
68
68
  encoding = getCharset(req) || options.defaultCharset
69
69
 
@@ -78,12 +78,12 @@ function read (req, res, next, parse, debug, options) {
78
78
  }
79
79
  }
80
80
 
81
- var length
82
- var opts = options
83
- var stream
81
+ let length
82
+ const opts = options
83
+ let stream
84
84
 
85
85
  // read options
86
- var verify = opts.verify
86
+ const verify = opts.verify
87
87
 
88
88
  try {
89
89
  // get the content stream
@@ -112,7 +112,7 @@ function read (req, res, next, parse, debug, options) {
112
112
  debug('read body')
113
113
  getBody(stream, opts, function (error, body) {
114
114
  if (error) {
115
- var _error
115
+ let _error
116
116
 
117
117
  if (error.type === 'encoding.unsupported') {
118
118
  // echo back charset
@@ -153,7 +153,7 @@ function read (req, res, next, parse, debug, options) {
153
153
  }
154
154
 
155
155
  // parse
156
- var str = body
156
+ let str = body
157
157
  try {
158
158
  debug('parse body')
159
159
  str = typeof body !== 'string' && encoding !== null
@@ -182,8 +182,8 @@ function read (req, res, next, parse, debug, options) {
182
182
  * @private
183
183
  */
184
184
  function contentstream (req, debug, inflate) {
185
- var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
186
- var length = req.headers['content-length']
185
+ const encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
186
+ const length = req.headers['content-length']
187
187
 
188
188
  debug('content-encoding "%s"', encoding)
189
189
 
@@ -199,7 +199,7 @@ function contentstream (req, debug, inflate) {
199
199
  return req
200
200
  }
201
201
 
202
- var stream = createDecompressionStream(encoding, debug)
202
+ const stream = createDecompressionStream(encoding, debug)
203
203
  req.pipe(stream)
204
204
  return stream
205
205
  }