@request-seal/core 0.1.0 → 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 (128) hide show
  1. package/CHANGELOG.md +49 -1
  2. package/README.md +342 -114
  3. package/dist/accept-signature.d.ts +11 -0
  4. package/dist/accept-signature.js +7 -0
  5. package/dist/adapters/fetch/index.d.ts +10 -0
  6. package/dist/adapters/fetch/index.js +21 -4
  7. package/dist/adapters/fetch/runtime.d.ts +1 -0
  8. package/dist/adapters/fetch/runtime.js +1 -0
  9. package/dist/adapters/node-http/index.d.ts +23 -0
  10. package/dist/adapters/node-http/index.js +28 -8
  11. package/dist/brand.d.ts +5 -0
  12. package/dist/brand.js +12 -0
  13. package/dist/bytes.d.ts +4 -0
  14. package/dist/bytes.js +5 -0
  15. package/dist/constants.d.ts +64 -4
  16. package/dist/constants.js +62 -2
  17. package/dist/context.d.ts +4 -0
  18. package/dist/context.js +3 -0
  19. package/dist/crypto/algorithm.d.ts +9 -0
  20. package/dist/crypto/algorithm.js +12 -0
  21. package/dist/crypto/index.d.ts +12 -2
  22. package/dist/crypto/index.js +34 -11
  23. package/dist/crypto/options.d.ts +3 -0
  24. package/dist/crypto/options.js +15 -0
  25. package/dist/crypto/primitive.d.ts +7 -3
  26. package/dist/crypto/primitive.js +36 -10
  27. package/dist/custody/content_key.d.ts +4 -0
  28. package/dist/custody/content_key.js +4 -2
  29. package/dist/custody/index.d.ts +2 -0
  30. package/dist/custody/index.js +5 -1
  31. package/dist/custody/local.d.ts +17 -1
  32. package/dist/custody/local.js +10 -5
  33. package/dist/custody/ssh-agent/index.d.ts +2 -0
  34. package/dist/custody/ssh-agent/index.js +11 -181
  35. package/dist/custody/ssh-agent/port.d.ts +19 -0
  36. package/dist/custody/ssh-agent/port.js +183 -0
  37. package/dist/digest.d.ts +22 -0
  38. package/dist/digest.js +15 -0
  39. package/dist/discovery/body.d.ts +19 -2
  40. package/dist/discovery/body.js +36 -11
  41. package/dist/discovery/cache.d.ts +21 -2
  42. package/dist/discovery/cache.js +105 -39
  43. package/dist/discovery/headers.d.ts +3 -0
  44. package/dist/discovery/headers.js +27 -1
  45. package/dist/discovery/index.d.ts +13 -1
  46. package/dist/discovery/index.js +31 -7
  47. package/dist/discovery/node-connection.d.ts +1 -1
  48. package/dist/discovery/node-connection.js +2 -2
  49. package/dist/discovery/node-trust.d.ts +2 -0
  50. package/dist/discovery/node-trust.js +9 -0
  51. package/dist/discovery/node.d.ts +9 -1
  52. package/dist/discovery/node.js +150 -26
  53. package/dist/discovery/proof.js +2 -2
  54. package/dist/discovery/snapshot.js +3 -0
  55. package/dist/discovery/source.d.ts +7 -0
  56. package/dist/discovery/source.js +5 -0
  57. package/dist/discovery/support.js +2 -3
  58. package/dist/errors/parity.d.ts +44 -3
  59. package/dist/errors/parity.js +45 -4
  60. package/dist/errors/types.d.ts +17 -3
  61. package/dist/errors/types.js +16 -2
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.js +1 -0
  64. package/dist/jose/header.d.ts +1 -0
  65. package/dist/jose/jwe.d.ts +9 -0
  66. package/dist/jose/jwe.js +17 -3
  67. package/dist/jose/jws.d.ts +11 -0
  68. package/dist/jose/jws.js +14 -2
  69. package/dist/jose/key-management.d.ts +11 -0
  70. package/dist/jose/key-management.js +13 -2
  71. package/dist/jose/nested.d.ts +6 -0
  72. package/dist/jose/nested.js +32 -2
  73. package/dist/json.d.ts +7 -0
  74. package/dist/key_handle.d.ts +12 -3
  75. package/dist/key_handle.js +32 -25
  76. package/dist/key_identity.d.ts +7 -0
  77. package/dist/key_identity.js +10 -2
  78. package/dist/message/index.d.ts +24 -0
  79. package/dist/message/index.js +22 -1
  80. package/dist/numbers.d.ts +6 -0
  81. package/dist/numbers.js +5 -0
  82. package/dist/policy.d.ts +27 -0
  83. package/dist/policy.js +42 -21
  84. package/dist/profile.d.ts +36 -0
  85. package/dist/profile.js +215 -0
  86. package/dist/public_key.d.ts +11 -2
  87. package/dist/public_key.js +31 -8
  88. package/dist/quorum/evaluation.d.ts +1 -1
  89. package/dist/quorum/evaluation.js +18 -4
  90. package/dist/quorum/verification.d.ts +2 -0
  91. package/dist/quorum/verification.js +1 -0
  92. package/dist/quorum.d.ts +9 -0
  93. package/dist/quorum.js +7 -1
  94. package/dist/replay/claim.d.ts +5 -0
  95. package/dist/replay/claim.js +9 -1
  96. package/dist/replay/index.d.ts +6 -0
  97. package/dist/replay/index.js +13 -1
  98. package/dist/replay/postgres.d.ts +3 -0
  99. package/dist/replay/postgres.js +7 -0
  100. package/dist/replay/receipt.d.ts +1 -0
  101. package/dist/replay/receipt.js +1 -0
  102. package/dist/replay/store.d.ts +1 -0
  103. package/dist/replay/store.js +1 -0
  104. package/dist/replay-port.d.ts +6 -1
  105. package/dist/result.d.ts +3 -0
  106. package/dist/result.js +2 -0
  107. package/dist/sign.d.ts +26 -0
  108. package/dist/sign.js +25 -3
  109. package/dist/signature-base/index.d.ts +2 -0
  110. package/dist/signature-base/index.js +5 -1
  111. package/dist/signature-fields.d.ts +5 -1
  112. package/dist/signature-fields.js +9 -1
  113. package/dist/structured-fields/index.d.ts +21 -0
  114. package/dist/structured-fields/index.js +14 -1
  115. package/dist/verification.d.ts +5 -1
  116. package/dist/verification.js +9 -6
  117. package/dist/verify-quorum.d.ts +1 -0
  118. package/dist/verify-quorum.js +30 -8
  119. package/dist/verify.d.ts +23 -0
  120. package/dist/verify.js +46 -14
  121. package/dist/web-bot-auth/index.d.ts +3 -0
  122. package/dist/web-bot-auth/index.js +3 -0
  123. package/dist/web-bot-auth/policy.d.ts +8 -0
  124. package/dist/web-bot-auth/policy.js +8 -4
  125. package/dist/web-bot-auth/verification.d.ts +8 -0
  126. package/dist/web-bot-auth/verification.js +4 -2
  127. package/dist/web-bot-auth/verify.js +2 -2
  128. package/package.json +29 -6
@@ -3,12 +3,13 @@ import { isMessage, Message } from "./message/index.js";
3
3
  import { isQuorum } from "./quorum.js";
4
4
  import { isContext, withDeadline, remainingMs } from "./context.js";
5
5
  import { isAcceptSignature, applicable, } from "./accept-signature.js";
6
- import { dictionaries, parameters, identities, ensure, fail, bounded, } from "./signature-fields.js";
6
+ import { dictionaries, parameters, identities, ensure, fail, envelope, } from "./signature-fields.js";
7
7
  import { verifyCandidate } from "./verify.js";
8
8
  import { evaluateQuorum } from "./quorum/evaluation.js";
9
9
  import { completeQuorum, } from "./quorum/verification.js";
10
10
  import { custodyError, isRequestSealError } from "./errors/types.js";
11
11
  import { ok, err } from "./result.js";
12
+ /** Verify bounded HTTP signatures and evaluate the configured quorum policy. */
12
13
  export async function verifyQuorum(message, quorum, options) {
13
14
  try {
14
15
  const a = snapshotOptions(options, ["acceptSignature", "context"]);
@@ -55,10 +56,30 @@ export async function verifyQuorum(message, quorum, options) {
55
56
  let qualified = false, last;
56
57
  const baseCache = new Map();
57
58
  for (const slot of slots) {
58
- const r = await verifyCandidate(m, slot.policy, {
59
- label: l,
60
- context: c.value,
61
- }, { input, signature: signatures.get(l), baseCache });
59
+ let r;
60
+ try {
61
+ r = await verifyCandidate(m, slot.policy, {
62
+ label: l,
63
+ context: c.value,
64
+ }, { input, signature: signatures.get(l), baseCache });
65
+ }
66
+ catch (fault) {
67
+ // Per-label rescue isolates a defective label as its own
68
+ // nonqualifying internal fact, never a whole-call failure.
69
+ if (isRequestSealError(fault) && fault.kind === "custody")
70
+ return err(fault);
71
+ const faulted = envelope(fault);
72
+ if (faulted.kind !== "core")
73
+ throw fault;
74
+ last = {
75
+ label: l,
76
+ slot: slot.id,
77
+ reason: faulted.reason,
78
+ layer: faulted.layer,
79
+ };
80
+ facts.push(last);
81
+ continue;
82
+ }
62
83
  if (r.ok) {
63
84
  const record = Object.freeze({
64
85
  slot,
@@ -107,8 +128,9 @@ export async function verifyQuorum(message, quorum, options) {
107
128
  const original = candidates.find((c) => c.slot === r.slot && c.label === r.label);
108
129
  results[r.label] = verified.get(original);
109
130
  }
110
- if (remainingMs(c.value) <= 0)
111
- throw custodyError("deadline_exceeded");
131
+ // The wall-clock bound lives in the assignment search: an accepted
132
+ // assignment, including one accepted at deadline exhaustion, completes.
133
+ // The remaining work here is synchronous and bounded.
112
134
  return completeQuorum({
113
135
  profile: { name: "rfc9421" },
114
136
  mode: quorum.mode,
@@ -139,6 +161,6 @@ export async function verifyQuorum(message, quorum, options) {
139
161
  catch (e) {
140
162
  if (isRequestSealError(e) && e.kind === "custody")
141
163
  return err(e);
142
- return err(bounded(e, "invalid_options", "input"));
164
+ return err(envelope(e));
143
165
  }
144
166
  }
package/dist/verify.d.ts CHANGED
@@ -4,7 +4,10 @@ import { KeyIdentity } from "./key_identity.ts";
4
4
  import { build } from "./signature-base/index.ts";
5
5
  import { type Verification } from "./verification.ts";
6
6
  import type { InnerList, Item } from "./structured-fields/index.ts";
7
+ import type { ReplayFacts } from "./replay-port.ts";
7
8
  import { type Result } from "./result.ts";
9
+ /** Internal coverage/freshness pipeline shared by selected verification and preflight. */
10
+ export declare function authenticationPreflight(input: InnerList, components: InnerList, extra: Policy["extraComponents"], freshnessPolicy: Policy["freshness"] | (() => Policy["freshness"]), context: Context): Promise<Verification["freshness"]>;
8
11
  /** Internal shared commit boundary; profile facts are supplied only after authentication. */
9
12
  export declare function commitReplay<F>(facts: F, retainUntil: number, policy: Omit<import('./policy.ts').ReplayPolicy, 'commitment'> & {
10
13
  commitment: (facts: F, context: Context) => Result<Uint8Array> | Promise<Result<Uint8Array>>;
@@ -16,6 +19,26 @@ type ParsedCandidate = Readonly<{
16
19
  signature: Item;
17
20
  baseCache: BaseCache;
18
21
  }>;
22
+ export declare function verifyLabel(message: unknown, policy: unknown, options: unknown, observeIdentity?: (identity: KeyIdentity) => void, parsed?: ParsedCandidate, profile?: ReplayFacts["profile"]): Promise<Result<Verification>>;
23
+ /**
24
+ * Verify one HTTP signature under an explicit branded policy.
25
+ *
26
+ * Checks exactly the named label; it never tries other labels. Success means the
27
+ * cryptography, coverage, freshness, content, and replay layers all passed. It does not
28
+ * authorize: the Verification records `authorization: "not_evaluated"`.
29
+ *
30
+ * @param message - A branded Message carrying the signature.
31
+ * @param policy - A branded Policy from `Policy.create`.
32
+ * @param options - `{label, context?, representation?, digestState?}`. `representation` is a
33
+ * complete retained Body and needs a `"representation"` content policy. `digestState` is a
34
+ * DigestState of the policy's content kind that you fed with a streamed body; verify finishes
35
+ * it. The two are mutually exclusive.
36
+ * @returns `ok(verification)`, or a bounded error such as `core` `crypto.invalid_signature`,
37
+ * `freshness.expired`, `content.digest_mismatch`, `key.unknown_key`, or `replay.replayed`.
38
+ * @see [Signing and verifying guide](https://github.com/baselabs/request-seal-typescript/blob/main/docs/guides/signing-and-verifying.md),
39
+ * [reference](https://github.com/baselabs/request-seal-typescript/blob/main/docs/reference/sign-and-verify.md), and the
40
+ * [error reasons](https://github.com/baselabs/request-seal-typescript/blob/main/docs/reference/errors.md).
41
+ */
19
42
  export declare function verify(message: unknown, policy: unknown, options: unknown): Promise<Result<Verification>>;
20
43
  /** Internal composition of the single-label pipeline; identities stay private. */
21
44
  export declare function verifyCandidate(message: unknown, policy: Policy, options: unknown, parsed?: ParsedCandidate): Promise<Result<Readonly<{
package/dist/verify.js CHANGED
@@ -10,7 +10,7 @@ import { isPublicKey } from "./public_key.js";
10
10
  import { isKeyIdentity, KeyIdentity } from "./key_identity.js";
11
11
  import { build } from "./signature-base/index.js";
12
12
  import { digestField, checkDigest, isDigestState } from "./digest.js";
13
- import { label, dictionaries, validInner, parameters, identities, ensure, fail, bounded, } from "./signature-fields.js";
13
+ import { label, dictionaries, validInner, parameters, identities, ensure, fail, bounded, envelope, } from "./signature-fields.js";
14
14
  import { completed, } from "./verification.js";
15
15
  import { createClaim, retainUntil as retention } from "./replay/claim.js";
16
16
  import { createReceipt } from "./replay/receipt.js";
@@ -50,6 +50,14 @@ async function freshness(params, policy, context) {
50
50
  skew: policy.skew,
51
51
  });
52
52
  }
53
+ /** Internal coverage/freshness pipeline shared by selected verification and preflight. */
54
+ export async function authenticationPreflight(input, components, extra, freshnessPolicy, context) {
55
+ const covered = new Set(identities(input)), needed = new Set(identities(components));
56
+ ensure([...needed].every((id) => covered.has(id)), "missing_required_component", "policy");
57
+ ensure(extra === "allow" ||
58
+ [...covered].every((id) => needed.has(id)), "unexpected_component", "policy");
59
+ return freshness(parameters(input), typeof freshnessPolicy === "function" ? freshnessPolicy() : freshnessPolicy, context);
60
+ }
53
61
  async function content(m, input, policy, options) {
54
62
  if (policy === "not_required")
55
63
  return policy;
@@ -90,7 +98,7 @@ async function content(m, input, policy, options) {
90
98
  }
91
99
  return checked.value;
92
100
  }
93
- async function replay(params, algorithm, fresh, policy, context) {
101
+ async function replay(params, algorithm, fresh, policy, context, profile) {
94
102
  if (policy === "not_required")
95
103
  return policy;
96
104
  ensure(typeof params.nonce === "string" && params.nonce.length > 0, "missing_replay_identifier", "replay");
@@ -112,7 +120,7 @@ async function replay(params, algorithm, fresh, policy, context) {
112
120
  tag: (params.tag ?? null),
113
121
  created: f.created,
114
122
  expires: f.expires,
115
- profile: Object.freeze({ name: "rfc9421" }),
123
+ profile,
116
124
  });
117
125
  return commitReplay(facts, retainUntil, policy, context);
118
126
  }
@@ -172,7 +180,7 @@ export async function commitReplay(facts, retainUntil, policy, context) {
172
180
  releaseContext(child.value);
173
181
  }
174
182
  }
175
- async function verifyLabel(message, policy, options, observeIdentity, parsed) {
183
+ export async function verifyLabel(message, policy, options, observeIdentity, parsed, profile = Object.freeze({ name: "rfc9421" })) {
176
184
  try {
177
185
  // Reserved stamps have their own bounded reason even when other options reject.
178
186
  ensure(options !== null && typeof options === "object" && !Array.isArray(options), "invalid_options", "input");
@@ -222,11 +230,7 @@ async function verifyLabel(message, policy, options, observeIdentity, parsed) {
222
230
  ensure(params.nonce === undefined ||
223
231
  (typeof params.nonce === "string" &&
224
232
  params.nonce.length <= SIGNATURE_MAX_BYTES), "limit", "input");
225
- const covered = new Set(identities(input)), needed = new Set(identities(policy.components));
226
- ensure([...needed].every((id) => covered.has(id)), "missing_required_component", "policy");
227
- ensure(policy.extraComponents === "allow" ||
228
- [...covered].every((id) => needed.has(id)), "unexpected_component", "policy");
229
- const fresh = await freshness(params, policy.freshness, c.value);
233
+ const fresh = await authenticationPreflight(input, policy.components, policy.extraComponents, policy.freshness, c.value);
230
234
  ensure(!Object.hasOwn(params, "alg") ||
231
235
  policy.algorithms.some(a => typeof a === "string" && a === params.alg), "algorithm_not_permitted", "policy");
232
236
  const resolution = await run(c.value, async (context) => {
@@ -336,7 +340,7 @@ async function verifyLabel(message, policy, options, observeIdentity, parsed) {
336
340
  throw contentResult.error;
337
341
  fail("digest_unsupported", "content");
338
342
  }
339
- const replayFacts = await replay(params, algorithm, fresh, policy.replay, c.value);
343
+ const replayFacts = await replay(params, algorithm, fresh, policy.replay, c.value, profile);
340
344
  let identity;
341
345
  if (observeIdentity) {
342
346
  const id = await run(c.value, async () => {
@@ -364,19 +368,47 @@ async function verifyLabel(message, policy, options, observeIdentity, parsed) {
364
368
  keyid: (params.keyid ?? null),
365
369
  crypto: "valid",
366
370
  },
367
- profile: { name: "rfc9421" }, content: contentResult.value, freshness: fresh,
371
+ profile, content: contentResult.value, freshness: fresh,
368
372
  replay: replayFacts, principal: "unattributed", authorization: "not_evaluated",
369
373
  });
370
- if (result.ok && identity)
371
- observeIdentity(identity);
374
+ if (result.ok && identity) {
375
+ // An extension pipeline step is a caller callback: its fault keeps the
376
+ // profile's deliberate invalid_policy classification, like a failing
377
+ // signer keeps signer_failed.
378
+ try {
379
+ observeIdentity(identity);
380
+ }
381
+ catch {
382
+ fail("invalid_policy", "input");
383
+ }
384
+ }
372
385
  return result;
373
386
  }
374
387
  catch (e) {
375
388
  if (isRequestSealError(e) && e.kind === "custody" && e.reason === "deadline_exceeded")
376
389
  return err(e);
377
- return err(bounded(e, "invalid_options", "input"));
390
+ return err(envelope(e));
378
391
  }
379
392
  }
393
+ /**
394
+ * Verify one HTTP signature under an explicit branded policy.
395
+ *
396
+ * Checks exactly the named label; it never tries other labels. Success means the
397
+ * cryptography, coverage, freshness, content, and replay layers all passed. It does not
398
+ * authorize: the Verification records `authorization: "not_evaluated"`.
399
+ *
400
+ * @param message - A branded Message carrying the signature.
401
+ * @param policy - A branded Policy from `Policy.create`.
402
+ * @param options - `{label, context?, representation?, digestState?}`. `representation` is a
403
+ * complete retained Body and needs a `"representation"` content policy. `digestState` is a
404
+ * DigestState of the policy's content kind that you fed with a streamed body; verify finishes
405
+ * it. The two are mutually exclusive.
406
+ * @returns `ok(verification)`, or a bounded error such as `core` `crypto.invalid_signature`,
407
+ * `freshness.expired`, `content.digest_mismatch`, `key.unknown_key`, or `replay.replayed`.
408
+ * @see [Signing and verifying guide](https://github.com/baselabs/request-seal-typescript/blob/main/docs/guides/signing-and-verifying.md),
409
+ * [reference](https://github.com/baselabs/request-seal-typescript/blob/main/docs/reference/sign-and-verify.md), and the
410
+ * [error reasons](https://github.com/baselabs/request-seal-typescript/blob/main/docs/reference/errors.md).
411
+ */
380
412
  export function verify(message, policy, options) {
381
413
  return verifyLabel(message, policy, options);
382
414
  }
@@ -1,10 +1,13 @@
1
1
  import { verifyWebBotAuth } from "./verify.ts";
2
2
  import { signWebBotAuth } from "./sign.ts";
3
+ /** Web Bot Auth policy construction, signing, and verification operations. */
3
4
  export declare const WebBotAuth: Readonly<{
4
5
  Policy: Readonly<{
5
6
  create: typeof import("./policy.ts").createPolicy;
6
7
  }>;
8
+ /** Verify an agent signature envelope under explicit trust, freshness, and replay rules. */
7
9
  verify: typeof verifyWebBotAuth;
10
+ /** Add a Web Bot Auth signature using explicit agent and freshness parameters. */
8
11
  sign: typeof signWebBotAuth;
9
12
  }>;
10
13
  export { isWebBotAuthVerification } from "./verification.ts";
@@ -1,9 +1,12 @@
1
1
  import { WebBotAuthPolicy } from "./policy.js";
2
2
  import { verifyWebBotAuth } from "./verify.js";
3
3
  import { signWebBotAuth } from "./sign.js";
4
+ /** Web Bot Auth policy construction, signing, and verification operations. */
4
5
  export const WebBotAuth = Object.freeze({
5
6
  Policy: WebBotAuthPolicy,
7
+ /** Verify an agent signature envelope under explicit trust, freshness, and replay rules. */
6
8
  verify: verifyWebBotAuth,
9
+ /** Add a Web Bot Auth signature using explicit agent and freshness parameters. */
7
10
  sign: signWebBotAuth,
8
11
  });
9
12
  export { isWebBotAuthVerification } from "./verification.js";
@@ -5,15 +5,20 @@ import type { KeySet } from "../discovery/body.ts";
5
5
  import type { Context } from "../context.ts";
6
6
  import type { Result } from "../result.ts";
7
7
  import type { WebBotAuthReplayFacts } from "./verification.ts";
8
+ /** Agent location, type, and deadline context passed to a trust callback. */
8
9
  export type AgentRequest = Readonly<{
9
10
  identifier: string;
10
11
  location: string;
11
12
  type: DiscoverySource["type"];
12
13
  }>;
14
+ /** Resolve a trusted discovery key set for an agent request. */
13
15
  export type AgentTrust = (agent: AgentRequest) => Result<DiscoverySource | KeySet | null> | Promise<Result<DiscoverySource | KeySet | null>>;
16
+ /** Replay rules with a commitment callback over the verified signature envelope. */
14
17
  export type WebBotAuthReplayPolicy = Omit<ReplayPolicy, "commitment"> & Readonly<{
18
+ /** Derive caller-owned replay commitment bytes from verified facts. */
15
19
  commitment: (facts: WebBotAuthReplayFacts, context: Context) => Result<Uint8Array> | Promise<Result<Uint8Array>>;
16
20
  }>;
21
+ /** A branded agent-trust policy for Web Bot Auth verification. */
17
22
  export type WebBotAuthPolicy = Omit<GenericPolicy, "keyResolver" | "replay" | "freshness"> & Readonly<{
18
23
  agents: AgentTrust;
19
24
  cache: DiscoveryCache | null;
@@ -26,8 +31,11 @@ export type WebBotAuthPolicy = Omit<GenericPolicy, "keyResolver" | "replay" | "f
26
31
  testKeys: "reject" | "allow";
27
32
  untagged: "reject" | "ignore";
28
33
  }>;
34
+ /** Check the private brand of a Web Bot Auth policy. */
29
35
  export declare function isWebBotAuthPolicy(v: unknown): v is WebBotAuthPolicy;
30
36
  export declare function createPolicy(input: unknown): Result<WebBotAuthPolicy>;
37
+ /** A branded agent-trust policy for Web Bot Auth verification. */
31
38
  export declare const WebBotAuthPolicy: Readonly<{
39
+ /** Snapshot, validate, and brand a Web Bot Auth trust policy. */
32
40
  create: typeof createPolicy;
33
41
  }>;
@@ -1,10 +1,11 @@
1
- import { snapshotOptions } from "../brand.js";
1
+ import { inspection, snapshotOptions } from "../brand.js";
2
2
  import { Policy, } from "../policy.js";
3
3
  import { isDiscoveryCache } from "../discovery/cache.js";
4
4
  import { ok, err } from "../result.js";
5
5
  import { ensure, bounded } from "../signature-fields.js";
6
6
  import { resolveAlgorithm } from "../crypto/algorithm.js";
7
7
  const policies = new WeakSet();
8
+ /** Check the private brand of a Web Bot Auth policy. */
8
9
  export function isWebBotAuthPolicy(v) {
9
10
  return !!v && typeof v === "object" && policies.has(v);
10
11
  }
@@ -92,8 +93,7 @@ export function createPolicy(input) {
92
93
  });
93
94
  Object.defineProperties(p, {
94
95
  toJSON: { value: inspect },
95
- [Symbol.for("nodejs.util.inspect.custom")]: { value: inspect },
96
- [Symbol.for("Deno.customInspect")]: { value: inspect },
96
+ ...inspection(inspect),
97
97
  });
98
98
  Object.freeze(p);
99
99
  policies.add(p);
@@ -103,4 +103,8 @@ export function createPolicy(input) {
103
103
  return err(bounded(e, "invalid_policy", "input"));
104
104
  }
105
105
  }
106
- export const WebBotAuthPolicy = Object.freeze({ create: createPolicy });
106
+ /** A branded agent-trust policy for Web Bot Auth verification. */
107
+ export const WebBotAuthPolicy = Object.freeze({
108
+ /** Snapshot, validate, and brand a Web Bot Auth trust policy. */
109
+ create: createPolicy
110
+ });
@@ -2,11 +2,14 @@ import type { Verification } from "../verification.ts";
2
2
  import type { ReplayReceipt } from "../replay-port.ts";
3
3
  import type { SigningAlgorithm } from "../crypto/algorithm.ts";
4
4
  import type { Proof } from "../discovery/body.ts";
5
+ /** Pinned Web Bot Auth profile metadata. */
5
6
  export declare const WEB_BOT_AUTH_PROFILE: Readonly<{
6
7
  name: "web_bot_auth";
7
8
  revision: "draft-ietf-webbotauth-httpsig-protocol-00";
8
9
  }>;
10
+ /** Profile name `web_bot_auth` and revision `draft-ietf-webbotauth-httpsig-protocol-00`. */
9
11
  export type WebBotAuthProfile = typeof WEB_BOT_AUTH_PROFILE;
12
+ /** A verified agent identity with discovery provenance and key thumbprint. */
10
13
  export type AgentPrincipal = Readonly<{
11
14
  kind: "agent";
12
15
  identifier: string;
@@ -20,14 +23,17 @@ export type AgentPrincipal = Readonly<{
20
23
  revision: string | null;
21
24
  }>;
22
25
  }>;
26
+ /** A verified held-key principal identified by its thumbprint. */
23
27
  export type HeldPrincipal = Readonly<{
24
28
  kind: "key";
25
29
  thumbprint: string;
26
30
  }>;
31
+ /** Single-signature verification facts narrowed to Web Bot Auth principals. */
27
32
  export type WebBotAuthSignature = Omit<Verification, "profile" | "principal"> & Readonly<{
28
33
  profile: WebBotAuthProfile;
29
34
  principal: AgentPrincipal | HeldPrincipal;
30
35
  }>;
36
+ /** Verified Web Bot Auth envelope facts supplied to a replay commitment callback. */
31
37
  export type WebBotAuthReplayFacts = Readonly<{
32
38
  profile: WebBotAuthProfile;
33
39
  signatures: readonly Readonly<{
@@ -40,6 +46,7 @@ export type WebBotAuthReplayFacts = Readonly<{
40
46
  expires: number;
41
47
  }>[];
42
48
  }>;
49
+ /** A branded Web Bot Auth envelope with per-label facts, evidence, and replay status. */
43
50
  export type WebBotAuthVerification = Readonly<{
44
51
  profile: WebBotAuthProfile;
45
52
  signatures: Readonly<Record<string, WebBotAuthSignature>>;
@@ -49,6 +56,7 @@ export type WebBotAuthVerification = Readonly<{
49
56
  authorization: "not_evaluated";
50
57
  }>;
51
58
  import { type Result } from "../result.ts";
59
+ /** Check the private brand of a Web Bot Auth verification envelope. */
52
60
  export declare function isWebBotAuthVerification(v: unknown): v is WebBotAuthVerification;
53
61
  /** Internal constructor; single-label facts retain their independent brands. */
54
62
  export declare function completedEnvelope(signatures: Record<string, WebBotAuthSignature>, evidence: Record<string, readonly string[]>, ignored: readonly string[], replay: WebBotAuthVerification["replay"]): Result<WebBotAuthVerification>;
@@ -1,3 +1,5 @@
1
+ import { inspection } from "../brand.js";
2
+ /** Pinned Web Bot Auth profile metadata. */
1
3
  export const WEB_BOT_AUTH_PROFILE = Object.freeze({
2
4
  name: "web_bot_auth",
3
5
  revision: "draft-ietf-webbotauth-httpsig-protocol-00",
@@ -7,6 +9,7 @@ import { coreError } from "../errors/types.js";
7
9
  import { verificationFacts, isVerification } from "../verification.js";
8
10
  import { isReplayReceipt } from "../replay/receipt.js";
9
11
  const envelopes = new WeakSet();
12
+ /** Check the private brand of a Web Bot Auth verification envelope. */
10
13
  export function isWebBotAuthVerification(v) {
11
14
  return !!v && typeof v === "object" && envelopes.has(v);
12
15
  }
@@ -31,8 +34,7 @@ export function completedEnvelope(signatures, evidence, ignored, replay) {
31
34
  });
32
35
  Object.defineProperties(result, {
33
36
  toJSON: { value: inspect },
34
- [Symbol.for("nodejs.util.inspect.custom")]: { value: inspect },
35
- [Symbol.for("Deno.customInspect")]: { value: inspect },
37
+ ...inspection(inspect),
36
38
  });
37
39
  envelopes.add(result);
38
40
  return ok(Object.freeze(result));
@@ -10,7 +10,7 @@ import { isDiscoverySource, sourceOrigin, thumbprintId, } from "../discovery/sou
10
10
  import { isKeySet, isResolution } from "../discovery/body.js";
11
11
  import { isPublicKey } from "../public_key.js";
12
12
  import { coreError, isRequestSealError, } from "../errors/types.js";
13
- import { dictionaries, validInner, parameters, identities, ensure, fail, bounded, } from "../signature-fields.js";
13
+ import { dictionaries, validInner, parameters, identities, ensure, fail, envelope, } from "../signature-fields.js";
14
14
  import { CLOCK_MAX_SECONDS } from "../constants.js";
15
15
  import { retentionEnd } from "../replay/claim.js";
16
16
  import { agents, identifier, originAgent, component, required, nested, schemas, } from "./support.js";
@@ -336,7 +336,7 @@ export async function verifyWebBotAuth(message, policy, options = {}) {
336
336
  catch (e) {
337
337
  if (isRequestSealError(e))
338
338
  return err(e);
339
- return err(bounded(e, "invalid_options", "input"));
339
+ return err(envelope(e));
340
340
  }
341
341
  finally {
342
342
  if (owned)
package/package.json CHANGED
@@ -1,9 +1,21 @@
1
1
  {
2
2
  "name": "@request-seal/core",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Standards-based HTTP message signatures and JOSE for web-standard runtimes.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
+ "keywords": [
8
+ "http-message-signatures",
9
+ "rfc9421",
10
+ "rfc9530",
11
+ "content-digest",
12
+ "web-bot-auth",
13
+ "jose",
14
+ "jws",
15
+ "jwe",
16
+ "webcrypto",
17
+ "replay-protection"
18
+ ],
7
19
  "main": "./dist/index.js",
8
20
  "types": "./dist/index.d.ts",
9
21
  "exports": {
@@ -61,19 +73,30 @@
61
73
  "url": "https://github.com/baselabs/request-seal-typescript/issues"
62
74
  },
63
75
  "scripts": {
64
- "typecheck": "tsc -p tsconfig.json",
65
- "typecheck:web": "tsc -p tsconfig.web.json",
76
+ "typecheck": "node node_modules/typescript/bin/tsc -p tsconfig.json",
77
+ "typecheck:web": "node node_modules/typescript/bin/tsc -p tsconfig.web.json",
66
78
  "test": "node --unhandled-rejections=strict --experimental-strip-types --no-warnings --test-concurrency=4 --test test/*.test.ts",
67
79
  "test:red": "node --unhandled-rejections=strict --experimental-strip-types --no-warnings --test test/red/planted.test.ts",
68
80
  "self-checks": "node --experimental-strip-types --no-warnings self-checks.ts",
69
- "build": "node --experimental-strip-types --no-warnings scripts/clean-dist.ts && tsc -p tsconfig.build.json",
81
+ "build": "node --experimental-strip-types --no-warnings scripts/clean-dist.ts && node node_modules/typescript/bin/tsc -p tsconfig.build.json",
70
82
  "conformance": "node --experimental-strip-types --no-warnings tools/conformance/cli.ts --corpus test/fixtures/corpus-smoke --smoke && node --experimental-strip-types --no-warnings tools/conformance/cli.ts --corpus conformance --pending-ok",
71
83
  "pack:check": "node --experimental-strip-types --no-warnings scripts/pack-check.ts",
72
- "check": "npm run typecheck && npm run typecheck:web && npm test && npm run self-checks && npm run build && npm run conformance && npm run pack:check"
84
+ "check": "npm run typecheck && npm run typecheck:web && npm run build && npm test && npm run self-checks && npm run conformance && npm run pack:check",
85
+ "docs": "node --import ./scripts/typedoc-register.mjs node_modules/typedoc/bin/typedoc --options typedoc.json"
73
86
  },
74
87
  "devDependencies": {
88
+ "@types/markdown-it": "14.1.2",
75
89
  "@types/node": "26.6.4",
76
90
  "fast-check": "4.10.2",
77
- "typescript": "7.0.2"
91
+ "github-slugger": "2.0.0",
92
+ "markdown-it": "14.3.2",
93
+ "typedoc": "0.28.20",
94
+ "typescript": "7.0.2",
95
+ "typescript-docs": "npm:typescript@6.0.3"
96
+ },
97
+ "overrides": {
98
+ "typedoc": {
99
+ "typescript": "$typescript"
100
+ }
78
101
  }
79
102
  }