@wayai/executor-sdk 0.1.0 → 0.1.1

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.
package/README.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # @wayai/executor-sdk
2
2
 
3
- Receive and verify [Rekor](https://github.com/wayai-pro/rekor)'s HMAC-signed, idempotency-keyed
4
- webhooks and proxied calls — the **executor** side of the Rekor Executor Contract. Verify the
5
- signature, enforce timestamp skew, dedupe retries, and return the right HTTP status, in a few lines.
3
+ Receive and verify [WayAI Data](https://wayai.pro/docs/data/executors)'s HMAC-signed,
4
+ idempotency-keyed webhooks and proxied calls — the **executor** side of the WayAI Data executor
5
+ contract. Verify the signature, enforce timestamp skew, dedupe retries, and return the right HTTP
6
+ status, in a few lines.
6
7
 
7
- Zero dependencies, dual ESM/CJS, runs anywhere with Web Crypto (Node ≥20, Cloudflare Workers, Deno,
8
- Bun). The executor is v0's capability; future capabilities will be added as exports here or under
9
- `@wayai/executor-sdk/<capability>` subpaths.
8
+ Zero dependencies, dual ESM/CJS, runs anywhere with Web Crypto (Node ≥24 per the package's declared
9
+ `engines`, Cloudflare Workers, Deno, Bun). The executor is v0's capability; future capabilities will
10
+ be added as exports here or under `@wayai/executor-sdk/<capability>` subpaths.
10
11
 
11
12
  ```bash
12
13
  npm i @wayai/executor-sdk
@@ -14,7 +15,7 @@ npm i @wayai/executor-sdk
14
15
 
15
16
  ## The wire
16
17
 
17
- Rekor signs every request it sends over **Signing v1**. The signature is HMAC-SHA256 over a canonical
18
+ Data signs every request it sends over **Signing v1**. The signature is HMAC-SHA256 over a canonical
18
19
  string, sent as `X-Data-Signature: v1,<hex>` (a space-delimited list so a key rotation can carry old +
19
20
  new). The canonical is:
20
21
 
@@ -31,7 +32,7 @@ new). The canonical is:
31
32
  | `X-Data-Delivery-Id` | per-delivery id on webhooks (for tracing) |
32
33
 
33
34
  `path` is the receiver URL's `pathname + search`. `body` is the **raw** request bytes. Verify
34
- **before** parsing the body. Skew enforcement is your job — Rekor only emits.
35
+ **before** parsing the body. Skew enforcement is your job — Data only emits.
35
36
 
36
37
  ## Quickstart — Fetch (Hono / Next.js / Workers / Deno)
37
38
 
@@ -39,7 +40,7 @@ new). The canonical is:
39
40
  import { createExecutor, toFetchHandler, ExecutorError } from '@wayai/executor-sdk';
40
41
 
41
42
  const handler = toFetchHandler(createExecutor({
42
- secret: process.env.REKOR_SIGNING_SECRET!,
43
+ secret: process.env.SIGNING_SECRET!,
43
44
  async handler(ctx) {
44
45
  // ctx: { id, idempotencyKey?, deliveryId?, timestamp, body } — body is the raw string
45
46
  const event = JSON.parse(ctx.body);
@@ -48,7 +49,7 @@ const handler = toFetchHandler(createExecutor({
48
49
  },
49
50
  }));
50
51
 
51
- // Hono: app.post('/rekor', (c) => handler(c.req.raw));
52
+ // Hono: app.post('/executor', (c) => handler(c.req.raw));
52
53
  // Next.js: export const POST = (req: Request) => handler(req);
53
54
  // CF Workers: export default { fetch: handler };
54
55
  ```
@@ -56,8 +57,8 @@ const handler = toFetchHandler(createExecutor({
56
57
  Throw an `ExecutorError` to control the response — `retriable` maps to the HTTP status family:
57
58
 
58
59
  ```ts
59
- throw new ExecutorError({ code: 'UPSTREAM_DOWN', message: 'try later', retriable: true }); // → 503 (Rekor retries)
60
- throw new ExecutorError({ code: 'BAD_INPUT', message: 'no', retriable: false, status: 422 }); // → 422 (Rekor drops)
60
+ throw new ExecutorError({ code: 'UPSTREAM_DOWN', message: 'try later', retriable: true }); // → 503 (Data retries)
61
+ throw new ExecutorError({ code: 'BAD_INPUT', message: 'no', retriable: false, status: 422 }); // → 422 (Data drops)
61
62
  ```
62
63
 
63
64
  ## Quickstart — Node / Express
@@ -67,26 +68,26 @@ import express from 'express';
67
68
  import { createExecutor, toNodeHandler } from '@wayai/executor-sdk';
68
69
 
69
70
  const app = express();
70
- const rekor = toNodeHandler(createExecutor({ secret: process.env.REKOR_SIGNING_SECRET!, handler: async (ctx) => { /* ... */ } }));
71
+ const executor = toNodeHandler(createExecutor({ secret: process.env.SIGNING_SECRET!, handler: async (ctx) => { /* ... */ } }));
71
72
 
72
73
  // ⚠️ Mount BEFORE any body parser (express.json(), body-parser…). The adapter reads the raw stream;
73
74
  // if a parser consumed it first, the raw bytes are gone and verification fails.
74
- app.post('/rekor', rekor);
75
+ app.post('/executor', executor);
75
76
  ```
76
77
 
77
78
  ## Verifying responses → retry behavior
78
79
 
79
- Return the right **status** — that's the contract Rekor's retry logic reads:
80
+ Return the right **status** — that's the contract Data's retry logic reads:
80
81
 
81
- | Your response | Rekor |
82
- |---------------|-------|
82
+ | Your response | Data |
83
+ |---------------|------|
83
84
  | `2xx` | ack, done |
84
85
  | `4xx` (except 429) | permanent failure — **not** retried |
85
86
  | `429` / `5xx` | transient — retried with backoff |
86
87
 
87
- > Note: Rekor's delivery currently retries **any** non-2xx response until the attempt budget is
88
+ > Note: delivery currently retries **any** non-2xx response until the attempt budget is
88
89
  > exhausted, then dead-letters it. Returning a `4xx` is the right signal for "permanent, don't retry"
89
- > and is the convention to code against — Rekor's backend is converging on honoring it.
90
+ > and is the convention to code against — the Data backend is converging on honoring it.
90
91
 
91
92
  `createExecutor` does this for you: a valid request runs your handler; a thrown `ExecutorError`
92
93
  becomes `{ status, code, message, retriable }` with the matching status; an unexpected throw becomes a
@@ -112,18 +113,19 @@ createExecutor({ secret, store, handler });
112
113
  (Read-only proxied requests carry a fresh id, so wrapping them is a harmless no-op — a network store
113
114
  turns every read into a write, so you needn't route reads through the executor.)
114
115
 
115
- ## Signing requests to Rekor (async write-back)
116
+ ## Signing requests back to Data (async write-back)
116
117
 
117
- For a slow/async action, an executor does the work and POSTs the result back to a Rekor **inbound webhook**. That
118
- inbound request must be signed the same way Rekor signs outbound — `signRequest` produces the headers
119
- from the inbound webhook's shared secret (the inverse of the verify side; same canonical, conformance-pinned):
118
+ For a slow/async action, an executor does the work and POSTs the result back to an **inbound webhook**
119
+ on the base. That inbound request must be signed the same way Data signs outbound — `signRequest`
120
+ produces the headers from the inbound webhook's shared secret (the inverse of the verify side; same
121
+ canonical, conformance-pinned):
120
122
 
121
123
  ```ts
122
124
  import { signRequest } from '@wayai/executor-sdk';
123
125
 
124
- const url = 'https://api.rekor.pro/inbound/<org>/<db>/<inbound_webhook_id>/ingest';
126
+ const url = 'https://data.wayai.pro/inbound/<org>/<base>/<inbound_webhook_id>/ingest';
125
127
  const body = JSON.stringify({ collection: 'results', data: { ok: true } });
126
- const headers = await signRequest({ secret: process.env.REKOR_HOOK_SECRET!, method: 'POST', url, body });
128
+ const headers = await signRequest({ secret: process.env.HOOK_SECRET!, method: 'POST', url, body });
127
129
 
128
130
  await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json', ...headers }, body });
129
131
  ```
@@ -146,24 +148,28 @@ const cert = await fetchExecutorCredential(req, 'partner-cert'); // { name, cont
146
148
  const pfx = Buffer.from(cert.value, 'base64'); // value is verbatim; decode per contentType
147
149
  ```
148
150
 
149
- `value` is delivered verbatim (Rekor stores binary credentials base64-encoded — decode per `contentType`).
151
+ `value` is delivered verbatim (binary credentials are stored base64-encoded — decode per `contentType`).
150
152
  The grant is **single-use per name**, so pull each credential **once per invocation and reuse** it; a
151
153
  re-pull fails. Throws `ExecutorError` if the request carries no grant, the name isn't declared, or the
152
154
  pull is denied (`403`, never retried) / unavailable (transient `5xx`/network → retried once).
153
155
 
154
156
  **Call only on a verified request** (e.g. inside your `createExecutor` handler). The grant headers aren't
155
- part of the request signature, so if your executor sits behind a TLS-terminating proxy, pin Rekor's origin
156
- to reject a rewritten callback URL: `fetchExecutorCredential(req, name, { allowedOrigins: ['https://api.rekor.pro'] })`.
157
+ part of the request signature, so if your executor sits behind a TLS-terminating proxy, pin the Data
158
+ origin to reject a rewritten callback URL: `fetchExecutorCredential(req, name, { allowedOrigins: ['https://data.wayai.pro'] })`.
159
+
160
+ A pin is an exact-origin allowlist, so it fails closed if the origin it names stops being the one
161
+ Data sends from: the pull throws `credential_untrusted_url` and is never retried. If you pin, list every
162
+ origin your deployment may send from and add a new one **before** it starts arriving, not after.
157
163
 
158
164
  ## Path matching (reverse proxies)
159
165
 
160
- The signature binds `path` = `new URL(receiverUrl).pathname + search` exactly as Rekor parsed it, with
166
+ The signature binds `path` = `new URL(receiverUrl).pathname + search` exactly as Data parsed it, with
161
167
  **no normalization**. A proxy that adds/strips a trailing slash, reorders or re-encodes query params,
162
168
  or normalizes percent-encoding will change the path your runtime sees and break verification. If you
163
- sit behind such a proxy, pass `pathOverride(req)` returning the path Rekor *signed*, byte-for-byte:
169
+ sit behind such a proxy, pass `pathOverride(req)` returning the path Data *signed*, byte-for-byte:
164
170
 
165
171
  ```ts
166
- createExecutor({ secret, handler, pathOverride: () => '/rekor' });
172
+ createExecutor({ secret, handler, pathOverride: () => '/executor' });
167
173
  ```
168
174
 
169
175
  ## Security notes
@@ -177,8 +183,8 @@ createExecutor({ secret, handler, pathOverride: () => '/rekor' });
177
183
  on the verified request; read them like any other field. Use them in memory, never persist.
178
184
  - **Signature list** — bounded to a few candidates, so a hostile header can't force unbounded HMACs.
179
185
  - **Body size** — the adapters read the full raw body into memory before verifying (verification needs
180
- the exact bytes). Cap request size at your framework/reverse proxy (e.g. a body-size limit on the
181
- Rekor route) so an oversized request can't exhaust memory ahead of verification.
186
+ the exact bytes). Cap request size at your framework/reverse proxy (e.g. a body-size limit on your
187
+ executor route) so an oversized request can't exhaust memory ahead of verification.
182
188
 
183
189
  ## Dev bypass
184
190
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/hmac.ts","../src/core/headers.ts","../src/core/canonical.ts","../src/core/verify.ts","../src/store/memory.ts","../src/errors/envelope.ts","../src/executor.ts","../src/adapters/fetch.ts","../src/adapters/node.ts","../src/core/sign.ts","../src/core/credential.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * HMAC-SHA256 over Web Crypto — pure, dependency-free, runs in any runtime with `crypto.subtle`\n * (Node ≥20, Cloudflare Workers, Deno, Bun, browsers). Ported verbatim from Rekor's backend\n * `workers/shared/src/hmac.ts`; the cross-package conformance vector (test/conformance.test.ts)\n * guards against drift.\n */\n\nexport async function signPayload(secret: string, payload: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(payload));\n return arrayBufferToHex(signature);\n}\n\n/** Constant-time-ish equality of two hex strings (length-checks first, then XOR-accumulate). */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let result = 0;\n for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return result === 0;\n}\n\nfunction arrayBufferToHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/** Case-insensitive header reader for a Fetch `Headers` or a plain object. A duplicate (array) value is\n * ambiguous and resolves to `undefined` (the signature/timestamp must be unambiguous). Shared by the\n * verify path and the dev-bypass path so they read headers identically. */\nexport type HeaderGetter = (name: string) => string | undefined;\n\nexport function headerGetter(headers: Headers | Record<string, string | string[] | undefined>): HeaderGetter {\n if (headers instanceof Headers) return (name) => headers.get(name) ?? undefined;\n const lower: Record<string, string | string[] | undefined> = {};\n for (const [k, v] of Object.entries(headers)) lower[k.toLowerCase()] = v;\n return (name) => {\n const v = lower[name];\n if (Array.isArray(v)) return v.length === 1 ? v[0] : undefined;\n return v;\n };\n}\n","/**\n * Rekor \"Signing v1\" canonical — the exact string Rekor HMAC-signs for every request it sends\n * (trigger webhooks + signed proxied calls). Ported verbatim from the backend's\n * `workers/shared/src/signing.ts`; kept byte-identical by the cross-package conformance vector.\n *\n * canonical = [\"v1\", id, String(unixTimestamp), method, path, body].join(\"\\n\")\n *\n * SECURITY INVARIANT: newline-joined; the only data-influenced field, `body`, is LAST, and every\n * other field is provably newline-free (the \"v1\" literal, an integer, a uuid/hex id, an HTTP method\n * token, and a URL pathname+search). The signature binds method + path so it can't be replayed as a\n * different request.\n */\n\nexport const SIGNING_SCHEME = 'v1';\n\nexport interface SignedParts {\n /** Stable message id — `X-Data-Id` (= delivery id on webhooks, idempotency key on proxied writes). */\n id: string;\n /** `X-Data-Timestamp`, unix seconds (integer). */\n timestamp: number;\n /** HTTP method (`POST` for trigger webhooks). */\n method: string;\n /** Request path: `new URL(receiverUrl).pathname + search`. */\n path: string;\n /** Exact raw request body string (`\"\"` when none). */\n body: string;\n}\n\n/** Build the canonical signing string. Component order and the `v1` prefix are part of the wire. */\nexport function buildCanonical(parts: SignedParts): string {\n return [SIGNING_SCHEME, parts.id, String(parts.timestamp), parts.method, parts.path, parts.body].join('\\n');\n}\n\n/** Current unix time in whole seconds (Standard Webhooks convention). `nowMs` override for tests. */\nexport function unixSeconds(nowMs?: number): number {\n return Math.floor((nowMs ?? Date.now()) / 1000);\n}\n","/**\n * Verify a Rekor \"Signing v1\" request: HMAC over the canonical, plus timestamp-skew enforcement\n * (the receiver's job — Rekor only emits). All inputs are the raw request bits the adapter extracts.\n */\n\nimport { signPayload, timingSafeEqual } from './hmac.js';\nimport { headerGetter } from './headers.js';\nimport { SIGNING_SCHEME, buildCanonical, unixSeconds, type SignedParts } from './canonical.js';\n\n/** Lowercased header names Rekor emits. */\nexport const DATA_HEADERS = {\n signature: 'x-data-signature',\n timestamp: 'x-data-timestamp',\n id: 'x-data-id',\n idempotencyKey: 'x-data-idempotency-key',\n deliveryId: 'x-data-delivery-id',\n // Executor credential pull: advertised on a proxied dispatch to a source declaring executor_secrets.\n credentialUrl: 'x-data-credential-url',\n credentialToken: 'x-data-credential-token',\n credentialNames: 'x-data-credential-names',\n} as const;\n\n/**\n * Max `scheme,hex` candidates we'll HMAC-check from one `X-Data-Signature` header. Rekor emits one\n * (two during a future key rotation); the cap stops a hostile header with thousands of entries from\n * forcing thousands of HMACs per request.\n */\nexport const MAX_SIG_ENTRIES = 4;\n\n/**\n * Verify a signature header value (`X-Data-Signature`) against the canonical of `parts`. The header\n * is a space-delimited list of `scheme,hex` entries; returns true if any `v1` entry matches (constant-\n * time compare). Malformed/empty/unknown-scheme entries are skipped, never throw. Candidates beyond\n * `MAX_SIG_ENTRIES` are ignored. Does NOT check timestamp skew (see {@link verify}).\n */\nexport async function verifySignatureHeader(secret: string, parts: SignedParts, header: string): Promise<boolean> {\n const canonical = buildCanonical(parts);\n let expected: string | undefined; // loop-invariant HMAC — computed at most once, lazily\n let checked = 0;\n let matched = false;\n for (const entry of header.split(' ')) {\n const comma = entry.indexOf(',');\n if (comma <= 0) continue; // no scheme/hex separator (e.g. \"v1\" or \"\")\n if (entry.slice(0, comma) !== SIGNING_SCHEME) continue;\n const hex = entry.slice(comma + 1);\n if (hex.length === 0) continue;\n if (++checked > MAX_SIG_ENTRIES) break;\n if (expected === undefined) expected = await signPayload(secret, canonical);\n if (timingSafeEqual(expected, hex)) matched = true;\n }\n return matched;\n}\n\nexport type VerifyResult =\n | { ok: true; id: string; timestamp: number; idempotencyKey?: string; deliveryId?: string; body: string }\n | { ok: false; reason: 'missing_signature' | 'invalid_signature' | 'stale_timestamp' | 'malformed' };\n\nexport interface VerifyInput {\n /** The HMAC signing secret shared with Rekor (the trigger/source signing secret). */\n secret: string;\n /** Request headers — a Fetch `Headers` or a plain object (case-insensitive lookup either way). */\n headers: Headers | Record<string, string | string[] | undefined>;\n /** HTTP method of the incoming request. */\n method: string;\n /** Request path the signature was computed over: `pathname + search` (see README path-matching). */\n path: string;\n /** RAW request body bytes as a string — NEVER a re-stringified parsed object. */\n rawBody: string;\n /** Allowed clock skew in seconds (both directions). A finite value is clamped to ≥1 (never 0);\n * unset / NaN / Infinity fall back to the 300s default (a bad value can't disable the window). */\n toleranceSec?: number;\n /** Override \"now\" in ms (tests). */\n now?: number;\n}\n\n/**\n * Verify an inbound Rekor request end to end: signature present + valid, and timestamp within the\n * skew window. Returns a discriminated result; never throws on bad input.\n */\nexport async function verify(input: VerifyInput): Promise<VerifyResult> {\n const get = headerGetter(input.headers);\n const signature = get(DATA_HEADERS.signature);\n if (!signature) return { ok: false, reason: 'missing_signature' };\n\n const tsRaw = get(DATA_HEADERS.timestamp);\n const id = get(DATA_HEADERS.id);\n if (!tsRaw || !id) return { ok: false, reason: 'malformed' };\n if (!/^\\d+$/.test(tsRaw)) return { ok: false, reason: 'malformed' };\n const timestamp = Number(tsRaw);\n if (!Number.isFinite(timestamp)) return { ok: false, reason: 'malformed' };\n\n // Non-finite (unset / NaN / Infinity, e.g. `Number(process.env.X)` with X missing) → the 300s\n // default, so a misconfigured tolerance can never silently disable the replay window. A finite value\n // is clamped to ≥1 (never 0). To accept a wider window, pass an explicit large finite number.\n const t = input.toleranceSec;\n const tolerance = Number.isFinite(t) ? Math.max(1, Math.floor(t as number)) : 300;\n const now = unixSeconds(input.now);\n if (Math.abs(now - timestamp) > tolerance) return { ok: false, reason: 'stale_timestamp' };\n\n const parts: SignedParts = { id, timestamp, method: input.method, path: input.path, body: input.rawBody };\n const valid = await verifySignatureHeader(input.secret, parts, signature);\n if (!valid) return { ok: false, reason: 'invalid_signature' };\n\n return {\n ok: true,\n id,\n timestamp,\n idempotencyKey: get(DATA_HEADERS.idempotencyKey),\n deliveryId: get(DATA_HEADERS.deliveryId),\n body: input.rawBody,\n };\n}\n","import type { ClaimResult, IdempotencyStore, StoredResult } from './types.js';\n\ninterface Entry {\n state: 'pending' | 'done';\n result?: StoredResult;\n expiresAt: number; // epoch ms\n}\n\n/**\n * In-memory idempotency store (per-process). Single-threaded JS gives the atomic first-wins claim for\n * free. An accessed key expires lazily; since every delivery has a unique key, completed entries are\n * otherwise dropped by a periodic full sweep (every {@link SWEEP_EVERY} writes) — so the map is bounded\n * by the deliveries within one TTL window (default 24h), not unbounded. For multiple instances, or to\n * cap memory tightly, implement {@link IdempotencyStore} over a shared backend instead.\n */\nconst DEFAULT_SWEEP_EVERY = 1000;\n\nexport class MemoryStore implements IdempotencyStore {\n private readonly map = new Map<string, Entry>();\n private readonly nowMs: () => number;\n private readonly sweepEvery: number;\n private writes = 0;\n\n constructor(opts?: { now?: () => number; sweepEvery?: number }) {\n this.nowMs = opts?.now ?? (() => Date.now());\n this.sweepEvery = opts?.sweepEvery ?? DEFAULT_SWEEP_EVERY;\n }\n\n /** Number of live entries currently held (after lazily ignoring nothing — counts raw map size). */\n get size(): number {\n return this.map.size;\n }\n\n private live(key: string): Entry | undefined {\n const e = this.map.get(key);\n if (!e) return undefined;\n if (e.expiresAt <= this.nowMs()) {\n this.map.delete(key);\n return undefined;\n }\n return e;\n }\n\n /** Amortized O(1)/write: every SWEEP_EVERY writes, drop all expired entries so the map can't grow forever. */\n private maybeSweep(): void {\n if (++this.writes < this.sweepEvery) return;\n this.writes = 0;\n const now = this.nowMs();\n for (const [k, e] of this.map) if (e.expiresAt <= now) this.map.delete(k);\n }\n\n async claim(key: string, ttlSec: number): Promise<ClaimResult> {\n const e = this.live(key);\n if (!e) {\n this.map.set(key, { state: 'pending', expiresAt: this.nowMs() + ttlSec * 1000 });\n this.maybeSweep();\n return { state: 'fresh' };\n }\n if (e.state === 'done' && e.result) return { state: 'done', result: e.result };\n return { state: 'in_progress' };\n }\n\n async complete(key: string, result: StoredResult, ttlSec: number): Promise<void> {\n this.map.set(key, { state: 'done', result, expiresAt: this.nowMs() + ttlSec * 1000 });\n this.maybeSweep();\n }\n\n async fail(key: string): Promise<void> {\n this.map.delete(key);\n }\n}\n","/**\n * The normalized error envelope an executor returns. The HTTP STATUS is the real contract a sender's\n * retry logic reads (2xx ack, 4xx permanent/no-retry, 5xx & 429 retry); the JSON body is for humans.\n */\nexport interface ErrorEnvelope {\n status: number;\n code: string;\n message: string;\n retriable: boolean;\n}\n\n/**\n * Throw this from a handler to fail with a specific code/retriability. `retriable` decides the status\n * family ({@link toHttpStatus}) so the sender retries (5xx/429) or drops (4xx) correctly.\n */\nexport class ExecutorError extends Error {\n readonly code: string;\n readonly retriable: boolean;\n readonly httpStatus?: number;\n\n constructor(args: { code: string; message: string; retriable: boolean; status?: number }) {\n super(args.message);\n this.name = 'ExecutorError';\n this.code = args.code;\n this.retriable = args.retriable;\n this.httpStatus = args.status;\n }\n\n toEnvelope(): ErrorEnvelope {\n return { status: toHttpStatus(this), code: this.code, message: this.message, retriable: this.retriable };\n }\n}\n\n/**\n * Map an error to an HTTP status. An explicit `status` is honored when it agrees with `retriable`\n * (5xx/429 for retriable, 4xx for permanent); otherwise the family default is used: retriable → 503,\n * permanent → 400.\n */\nexport function toHttpStatus(e: { retriable: boolean; httpStatus?: number }): number {\n const s = e.httpStatus;\n if (e.retriable) {\n return s !== undefined && (s === 429 || (s >= 500 && s <= 599)) ? s : 503;\n }\n return s !== undefined && s >= 400 && s <= 499 ? s : 400;\n}\n\n/** Serialize an envelope to a JSON body string. */\nexport function jsonResponseBody(env: ErrorEnvelope): string {\n return JSON.stringify(env);\n}\n","import { verify, DATA_HEADERS, type VerifyResult } from './core/verify.js';\nimport { headerGetter } from './core/headers.js';\nimport { MemoryStore } from './store/memory.js';\nimport type { IdempotencyStore, StoredResult } from './store/types.js';\nimport { ExecutorError, toHttpStatus, jsonResponseBody, type ErrorEnvelope } from './errors/envelope.js';\n\n/** Framework-neutral request the adapters hand to the executor. `rawBody` is the RAW bytes as a string. */\nexport interface NormalizedRequest {\n method: string;\n path: string;\n headers: Headers | Record<string, string | string[] | undefined>;\n rawBody: string;\n}\n\nexport interface NormalizedResponse {\n status: number;\n body: string;\n contentType: string;\n}\n\n/** Verified request context passed to your handler. Never contains the signing secret. */\nexport interface ExecutorContext {\n id: string;\n idempotencyKey?: string;\n deliveryId?: string;\n timestamp: number;\n body: string;\n}\n\n/** Your business logic. Return a `StoredResult` (cached for idempotent replays) or nothing (→ 204). */\nexport type ExecutorHandler = (ctx: ExecutorContext) => Promise<StoredResult | void> | StoredResult | void;\n\nexport interface CreateExecutorOptions {\n /** HMAC signing secret shared with Rekor. */\n secret: string;\n handler: ExecutorHandler;\n /** Idempotency store. Default: a per-process {@link MemoryStore}. */\n store?: IdempotencyStore;\n /** Clock-skew tolerance (seconds, both directions). Default 300; clamped to ≥1. */\n toleranceSec?: number;\n /** Skip signature/skew verification (LOCAL DEV ONLY). Dedup still applies if headers are present. */\n devBypass?: boolean;\n /** Idempotency record TTL (seconds). Default 86400 (24h). */\n idempotencyTtlSec?: number;\n /** Reports unexpected handler errors. MUST NOT log secrets/PII — it never receives the secret. */\n onError?: (err: unknown, ctx?: ExecutorContext) => void;\n /**\n * Reproduce the path Rekor SIGNED when a reverse proxy rewrites it. Must return the exact\n * `pathname+search` Rekor used (see README path-matching), not necessarily the incoming path.\n */\n pathOverride?: (req: NormalizedRequest) => string;\n}\n\nexport interface Executor {\n handle(req: NormalizedRequest): Promise<NormalizedResponse>;\n}\n\nconst JSON_CT = 'application/json';\n\nfunction errorResponse(env: ErrorEnvelope): NormalizedResponse {\n return { status: env.status, body: jsonResponseBody(env), contentType: JSON_CT };\n}\n\nfunction reasonResponse(reason: Exclude<VerifyResult, { ok: true }>['reason']): NormalizedResponse {\n // missing/invalid → 401 (unauthenticated); stale → 403; malformed → 400.\n const status = reason === 'malformed' ? 400 : reason === 'stale_timestamp' ? 403 : 401;\n return errorResponse({ status, code: reason.toUpperCase(), message: `signature ${reason}`, retriable: false });\n}\n\n/** Read the dedupe context from headers without verifying (dev-bypass path). */\nfunction bypassContext(req: NormalizedRequest): ExecutorContext | null {\n const get = headerGetter(req.headers);\n const id = get(DATA_HEADERS.id);\n const tsRaw = get(DATA_HEADERS.timestamp);\n return {\n id: id ?? '',\n idempotencyKey: get(DATA_HEADERS.idempotencyKey),\n deliveryId: get(DATA_HEADERS.deliveryId),\n timestamp: tsRaw && /^\\d+$/.test(tsRaw) ? Number(tsRaw) : 0,\n body: req.rawBody,\n };\n}\n\nexport function createExecutor(opts: CreateExecutorOptions): Executor {\n const store = opts.store ?? new MemoryStore();\n const ttl = opts.idempotencyTtlSec ?? 86_400;\n\n async function run(ctx: ExecutorContext): Promise<NormalizedResponse> {\n // Dedupe key: the logical idempotency key (proxy writes) or the stable message id (webhooks).\n // Absent both (e.g. dev-bypass with no Rekor headers) → run without dedupe.\n const key = ctx.idempotencyKey || ctx.id;\n if (!key) return await invoke(ctx);\n\n const claim = await store.claim(key, ttl);\n if (claim.state === 'done') {\n return { status: claim.result.status, body: claim.result.body, contentType: claim.result.contentType ?? JSON_CT };\n }\n if (claim.state === 'in_progress') {\n // A concurrent attempt holds this key — tell the sender to retry. Use a retriable (5xx) status,\n // not 409, so it matches `retriable: true` and the sender's retry convention.\n return errorResponse({ status: 503, code: 'IN_PROGRESS', message: 'duplicate delivery in progress', retriable: true });\n }\n try {\n const res = await invoke(ctx);\n await store.complete(key, { status: res.status, body: res.body, contentType: res.contentType }, ttl);\n return res;\n } catch (err) {\n const env = toEnvelope(err);\n const res = errorResponse(env);\n if (env.retriable) {\n await store.fail(key); // retriable: release the key so the retry re-runs (don't cache failures)\n } else {\n // permanent: cache the 4xx so a duplicate delivery replays it instead of re-running.\n await store.complete(key, { status: res.status, body: res.body, contentType: res.contentType }, ttl);\n }\n if (!(err instanceof ExecutorError)) opts.onError?.(err, ctx);\n return res;\n }\n }\n\n async function invoke(ctx: ExecutorContext): Promise<NormalizedResponse> {\n const result = await opts.handler(ctx);\n if (!result) return { status: 204, body: '', contentType: JSON_CT };\n return { status: result.status, body: result.body, contentType: result.contentType ?? JSON_CT };\n }\n\n return {\n async handle(req: NormalizedRequest): Promise<NormalizedResponse> {\n const path = opts.pathOverride ? opts.pathOverride(req) : req.path;\n\n if (opts.devBypass) {\n const ctx = bypassContext(req);\n return ctx ? await run(ctx) : reasonResponse('malformed');\n }\n\n const result = await verify({\n secret: opts.secret,\n headers: req.headers,\n method: req.method,\n path,\n rawBody: req.rawBody,\n toleranceSec: opts.toleranceSec,\n });\n if (!result.ok) return reasonResponse(result.reason);\n\n return await run({\n id: result.id,\n idempotencyKey: result.idempotencyKey,\n deliveryId: result.deliveryId,\n timestamp: result.timestamp,\n body: result.body,\n });\n },\n };\n}\n\nfunction toEnvelope(err: unknown): ErrorEnvelope {\n if (err instanceof ExecutorError) return err.toEnvelope();\n // Unknown error → retriable 500 so the sender retries; message is generic (no secret/PII).\n return { status: toHttpStatus({ retriable: true }), code: 'INTERNAL', message: 'handler error', retriable: true };\n}\n","import type { Executor } from '../executor.js';\n\n/**\n * Wrap an {@link Executor} as a Fetch-API handler `(Request) => Promise<Response>` — works on Hono,\n * Next.js route handlers, Cloudflare Workers, Deno, and Bun.\n *\n * Reads the RAW body via `req.text()` BEFORE any parsing (verifying a re-stringified parsed body would\n * fail). The signed `path` is `new URL(req.url).pathname + search`; if a reverse proxy rewrites the\n * path, pass `pathOverride` to `createExecutor`.\n */\nexport function toFetchHandler(executor: Executor): (req: Request) => Promise<Response> {\n return async (req: Request): Promise<Response> => {\n const rawBody = await req.text();\n const url = new URL(req.url);\n const res = await executor.handle({\n method: req.method,\n path: url.pathname + url.search,\n headers: req.headers,\n rawBody,\n });\n // 204/205/304 are null-body statuses — passing any body (even '') throws in spec-compliant\n // runtimes, so send `null` when the body is empty.\n return new Response(res.body || null, { status: res.status, headers: { 'content-type': res.contentType } });\n };\n}\n","import type { Executor } from '../executor.js';\n\n/**\n * Minimal structural shapes of Node's `http.IncomingMessage` / `http.ServerResponse` (and Express's\n * `req`/`res`). Declared structurally so the package imports no Node built-ins at runtime.\n */\nexport interface NodeReq extends AsyncIterable<unknown> {\n method?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n}\nexport interface NodeRes {\n writeHead(status: number, headers?: Record<string, string>): unknown;\n end(chunk?: string): unknown;\n}\n\n/**\n * Wrap an {@link Executor} as a Node/Express handler `(req, res) => Promise<void>`.\n *\n * IMPORTANT: this reads the RAW request stream itself, so it MUST run BEFORE any body parser\n * (`express.json()`, `body-parser`, …). If a parser has already consumed the stream, the raw bytes are\n * gone and verification fails. Mount this on the Rekor route before such middleware.\n *\n * The signed `path` comes from `req.url` (already `pathname+search`); use `pathOverride` for proxies.\n */\nexport function toNodeHandler(executor: Executor): (req: NodeReq, res: NodeRes) => Promise<void> {\n return async (req: NodeReq, res: NodeRes): Promise<void> => {\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer));\n }\n const rawBody = Buffer.concat(chunks).toString('utf8');\n\n const out = await executor.handle({\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: req.headers,\n rawBody,\n });\n res.writeHead(out.status, { 'content-type': out.contentType });\n res.end(out.body);\n };\n}\n","/**\n * Sign a request TO Rekor — the inverse of the executor's verify side. Use this when an executor POSTs\n * a result back to a Rekor inbound webhook (the async write-back leg): Rekor's inbound webhook ingest verifies a Signing v1\n * signature, and this produces it from the inbound webhook's shared secret.\n *\n * Returns the three headers to attach (`X-Data-Signature`, `X-Data-Timestamp`, `X-Data-Id`). The\n * canonical is byte-identical to the verify side (the cross-package conformance vector guards drift).\n */\n\nimport { buildCanonical, SIGNING_SCHEME, unixSeconds } from './canonical.js';\nimport { signPayload } from './hmac.js';\n\nexport interface SignRequestInput {\n /** HMAC shared secret for the target Rekor inbound webhook. */\n secret: string;\n /** HTTP method of the request being signed (e.g. 'POST'). */\n method: string;\n /** Full target URL — its `pathname + search` is bound into the signature. */\n url: string;\n /** Raw request body string (default `''`). MUST be the exact bytes sent. */\n body?: string;\n /** Message id → `X-Data-Id`, bound into the signature. Default: a generated UUID. */\n id?: string;\n /** Unix seconds → `X-Data-Timestamp`. Default: now. */\n timestamp?: number;\n}\n\nexport interface SignedHeaders {\n 'X-Data-Signature': string;\n 'X-Data-Timestamp': string;\n 'X-Data-Id': string;\n}\n\n/** Sign a request to Rekor and return the headers to attach. */\nexport async function signRequest(input: SignRequestInput): Promise<SignedHeaders> {\n const id = input.id ?? crypto.randomUUID();\n const timestamp = input.timestamp ?? unixSeconds();\n const body = input.body ?? '';\n const { pathname, search } = new URL(input.url);\n const hex = await signPayload(input.secret, buildCanonical({ id, timestamp, method: input.method, path: pathname + search, body }));\n return {\n 'X-Data-Signature': `${SIGNING_SCHEME},${hex}`,\n 'X-Data-Timestamp': String(timestamp),\n 'X-Data-Id': id,\n };\n}\n","/**\n * Executor credential pull — the client side of Rekor's scoped, single-use binary-credential delivery.\n *\n * When a Rekor source declares `executor_secrets`, every proxied dispatch to the executor carries\n * `X-Data-Credential-Url`/`-Token`/`-Names`. The executor calls `fetchExecutorCredential` with the\n * INBOUND request to pull a declared credential by name (e.g. an mTLS client cert it must present to a\n * downstream upstream). The grant is single-use per name and short-lived, so fetch each credential ONCE\n * per invocation and reuse the value — a re-pull of the same name fails (the grant is already claimed).\n *\n * The returned `value` is delivered verbatim (Rekor stores binary credentials base64-encoded); decode it\n * per `contentType` (e.g. base64-decode an `application/x-pkcs12` bundle). This helper never decodes.\n *\n * SECURITY: call this ONLY from a handler whose inbound request the SDK has already verified (e.g. inside\n * `createExecutor`'s handler). The grant headers are NOT part of the request's HMAC signature, so anything\n * that can rewrite headers in front of your executor (a TLS-terminating proxy/gateway) could swap the\n * callback URL. Pass `allowedOrigins` (your Rekor origin, e.g. `['https://api.rekor.pro']`) to reject a\n * rewritten URL — recommended whenever your executor sits behind such an intermediary.\n */\n\nimport { headerGetter } from './headers.js';\nimport { DATA_HEADERS } from './verify.js';\nimport { ExecutorError } from '../errors/envelope.js';\n\n/** A pulled credential. `value` is delivered verbatim (base64 for binary); decode per `contentType`. */\nexport interface ExecutorCredential {\n name: string;\n contentType: string;\n value: string;\n}\n\nexport interface FetchCredentialOptions {\n /** Override the fetch implementation (defaults to the global `fetch`). */\n fetchImpl?: typeof fetch;\n /**\n * If set (and non-empty), the callback URL's origin must be one of these — rejects a URL rewritten in\n * transit (the grant headers are NOT covered by the request signature). Pass your Rekor origin(s),\n * e.g. `['https://api.rekor.pro']`. Recommended when the executor sits behind a TLS-terminating proxy.\n */\n allowedOrigins?: string[];\n}\n\n/** Anything carrying the inbound request headers: a NormalizedRequest, a Fetch `Request`, or raw headers. */\ntype HeaderSource =\n | Headers\n | Record<string, string | string[] | undefined>\n | { headers: Headers | Record<string, string | string[] | undefined> };\n\nfunction resolveHeaders(source: HeaderSource): Headers | Record<string, string | string[] | undefined> {\n if (source instanceof Headers) return source;\n // A NormalizedRequest / Fetch Request carries headers under `.headers` (a Headers or a record); a bare\n // headers record does not (its `.headers`, if any, would be a string value, not an object) — so guard\n // on the shape rather than `'headers' in source`, which a record with a \"headers\" key would spoof.\n const nested = (source as { headers?: unknown }).headers;\n if (nested instanceof Headers) return nested;\n if (nested && typeof nested === 'object') return nested as Record<string, string | string[] | undefined>;\n return source as Record<string, string | string[] | undefined>;\n}\n\n/**\n * Pull a declared credential by name from Rekor, using the grant advertised on the inbound dispatch.\n * Throws `ExecutorError` if the request carries no grant, the name isn't in the grant, or the pull is\n * denied/fails. A 403 (grant invalid/expired/already-used) is never retried; a transient 5xx/network\n * error is retried once.\n */\nexport async function fetchExecutorCredential(\n source: HeaderSource,\n name: string,\n opts: FetchCredentialOptions = {},\n): Promise<ExecutorCredential> {\n const get = headerGetter(resolveHeaders(source));\n const url = get(DATA_HEADERS.credentialUrl);\n const token = get(DATA_HEADERS.credentialToken);\n const namesHeader = get(DATA_HEADERS.credentialNames);\n if (!url || !token || namesHeader === undefined) {\n throw new ExecutorError({ code: 'no_credential_grant', message: 'Request carries no executor credential grant', retriable: false });\n }\n // The callback URL is transport-trusted, not signature-bound — pin it when an allowlist is supplied so a\n // rewritten URL can't redirect the single-use grant token to an attacker host.\n if (opts.allowedOrigins && opts.allowedOrigins.length > 0) {\n let origin: string;\n try { origin = new URL(url).origin; } catch { throw new ExecutorError({ code: 'credential_bad_url', message: 'Credential callback URL is not a valid URL', retriable: false }); }\n // Normalize each allowlist entry through URL.origin too, so a caller's trailing slash / explicit\n // default port (e.g. 'https://api.rekor.pro/' or ':443') still matches. A malformed entry keeps its\n // raw form and simply never matches (it can't grant), rather than breaking the whole check.\n const allowed = opts.allowedOrigins.map((o) => { try { return new URL(o).origin; } catch { return o; } });\n if (!allowed.includes(origin)) {\n throw new ExecutorError({ code: 'credential_untrusted_url', message: `Credential callback URL origin '${origin}' is not in allowedOrigins`, retriable: false });\n }\n }\n const names = namesHeader.split(',').map((n) => n.trim()).filter(Boolean);\n if (!names.includes(name)) {\n throw new ExecutorError({ code: 'undeclared_credential', message: `Credential '${name}' is not in the grant (declared: ${names.join(', ') || 'none'})`, retriable: false });\n }\n\n const doFetch = opts.fetchImpl ?? fetch;\n const body = JSON.stringify({ token, name });\n\n // Up to 2 attempts: retry once on a transient 5xx/network error, never on a 403 (single-use — a retry\n // would just hit `already_redeemed`). A 5xx that landed AFTER the grant was claimed surfaces as a clean\n // 403 on retry, which is the correct terminal outcome (the credential is consumed; Rekor mints a fresh\n // grant on the next dispatch).\n for (let attempt = 0; attempt < 2; attempt++) {\n let res: Response;\n try {\n // `redirect: 'manual'` so a 3xx from the (pinned) URL can't re-POST the token-bearing body to\n // another host — a redirect surfaces as a non-ok status → credential_fetch_failed below.\n res = await doFetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body, redirect: 'manual' });\n } catch {\n if (attempt === 0) continue; // network error → retry once\n throw new ExecutorError({ code: 'credential_unreachable', message: 'Credential pull is unreachable', retriable: true });\n }\n\n if (res.status === 403) {\n throw new ExecutorError({ code: 'credential_denied', message: `Credential '${name}' pull was denied (grant invalid, expired, or already used)`, retriable: false, status: 403 });\n }\n if (res.status >= 500) {\n if (attempt === 0) continue; // transient → retry once\n throw new ExecutorError({ code: 'credential_unavailable', message: `Credential pull failed (status ${res.status})`, retriable: true, status: res.status });\n }\n if (!res.ok) {\n throw new ExecutorError({ code: 'credential_fetch_failed', message: `Credential pull failed (status ${res.status})`, retriable: false, status: res.status });\n }\n\n const parsed = (await res.json().catch(() => null)) as { data?: { name?: string; content_type?: string; value?: string } } | null;\n const data = parsed?.data;\n if (!data || typeof data.value !== 'string') {\n throw new ExecutorError({ code: 'credential_malformed', message: 'Credential pull returned an unexpected body', retriable: false });\n }\n return { name: data.name ?? name, contentType: data.content_type ?? 'application/octet-stream', value: data.value };\n }\n\n // Unreachable: the loop returns or throws on every path.\n throw new ExecutorError({ code: 'credential_unavailable', message: 'Credential pull failed', retriable: true });\n}\n"]}
1
+ {"version":3,"sources":["../src/core/hmac.ts","../src/core/headers.ts","../src/core/canonical.ts","../src/core/verify.ts","../src/store/memory.ts","../src/errors/envelope.ts","../src/executor.ts","../src/adapters/fetch.ts","../src/adapters/node.ts","../src/core/sign.ts","../src/core/credential.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * HMAC-SHA256 over Web Crypto — pure, dependency-free, runs in any runtime with `crypto.subtle`\n * (Cloudflare Workers, Deno, Bun, browsers, and Node — see the package's declared `engines`).\n * Ported verbatim from the backend's implementation; the cross-package conformance vector\n * (test/conformance.test.ts) guards against drift.\n */\n\nexport async function signPayload(secret: string, payload: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(payload));\n return arrayBufferToHex(signature);\n}\n\n/** Constant-time-ish equality of two hex strings (length-checks first, then XOR-accumulate). */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let result = 0;\n for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return result === 0;\n}\n\nfunction arrayBufferToHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/** Case-insensitive header reader for a Fetch `Headers` or a plain object. A duplicate (array) value is\n * ambiguous and resolves to `undefined` (the signature/timestamp must be unambiguous). Shared by the\n * verify path and the dev-bypass path so they read headers identically. */\nexport type HeaderGetter = (name: string) => string | undefined;\n\nexport function headerGetter(headers: Headers | Record<string, string | string[] | undefined>): HeaderGetter {\n if (headers instanceof Headers) return (name) => headers.get(name) ?? undefined;\n const lower: Record<string, string | string[] | undefined> = {};\n for (const [k, v] of Object.entries(headers)) lower[k.toLowerCase()] = v;\n return (name) => {\n const v = lower[name];\n if (Array.isArray(v)) return v.length === 1 ? v[0] : undefined;\n return v;\n };\n}\n","/**\n * The \"Signing v1\" canonical — the exact string Data HMAC-signs for every request it sends\n * (trigger webhooks + signed proxied calls). Ported verbatim from the backend's implementation and\n * kept byte-identical to it by the cross-package conformance vector (test/conformance.test.ts).\n *\n * canonical = [\"v1\", id, String(unixTimestamp), method, path, body].join(\"\\n\")\n *\n * SECURITY INVARIANT: newline-joined; the only data-influenced field, `body`, is LAST, and every\n * other field is provably newline-free (the \"v1\" literal, an integer, a uuid/hex id, an HTTP method\n * token, and a URL pathname+search). The signature binds method + path so it can't be replayed as a\n * different request.\n */\n\nexport const SIGNING_SCHEME = 'v1';\n\nexport interface SignedParts {\n /** Stable message id — `X-Data-Id` (= delivery id on webhooks, idempotency key on proxied writes). */\n id: string;\n /** `X-Data-Timestamp`, unix seconds (integer). */\n timestamp: number;\n /** HTTP method (`POST` for trigger webhooks). */\n method: string;\n /** Request path: `new URL(receiverUrl).pathname + search`. */\n path: string;\n /** Exact raw request body string (`\"\"` when none). */\n body: string;\n}\n\n/** Build the canonical signing string. Component order and the `v1` prefix are part of the wire. */\nexport function buildCanonical(parts: SignedParts): string {\n return [SIGNING_SCHEME, parts.id, String(parts.timestamp), parts.method, parts.path, parts.body].join('\\n');\n}\n\n/** Current unix time in whole seconds (Standard Webhooks convention). `nowMs` override for tests. */\nexport function unixSeconds(nowMs?: number): number {\n return Math.floor((nowMs ?? Date.now()) / 1000);\n}\n","/**\n * Verify a \"Signing v1\" request: HMAC over the canonical, plus timestamp-skew enforcement\n * (the receiver's job — Data only emits). All inputs are the raw request bits the adapter extracts.\n */\n\nimport { signPayload, timingSafeEqual } from './hmac.js';\nimport { headerGetter } from './headers.js';\nimport { SIGNING_SCHEME, buildCanonical, unixSeconds, type SignedParts } from './canonical.js';\n\n/** Lowercased header names Data emits. */\nexport const DATA_HEADERS = {\n signature: 'x-data-signature',\n timestamp: 'x-data-timestamp',\n id: 'x-data-id',\n idempotencyKey: 'x-data-idempotency-key',\n deliveryId: 'x-data-delivery-id',\n // Executor credential pull: advertised on a proxied dispatch to a source declaring executor_secrets.\n credentialUrl: 'x-data-credential-url',\n credentialToken: 'x-data-credential-token',\n credentialNames: 'x-data-credential-names',\n} as const;\n\n/**\n * Max `scheme,hex` candidates we'll HMAC-check from one `X-Data-Signature` header. Data emits one\n * (two during a future key rotation); the cap stops a hostile header with thousands of entries from\n * forcing thousands of HMACs per request.\n */\nexport const MAX_SIG_ENTRIES = 4;\n\n/**\n * Verify a signature header value (`X-Data-Signature`) against the canonical of `parts`. The header\n * is a space-delimited list of `scheme,hex` entries; returns true if any `v1` entry matches (constant-\n * time compare). Malformed/empty/unknown-scheme entries are skipped, never throw. Candidates beyond\n * `MAX_SIG_ENTRIES` are ignored. Does NOT check timestamp skew (see {@link verify}).\n */\nexport async function verifySignatureHeader(secret: string, parts: SignedParts, header: string): Promise<boolean> {\n const canonical = buildCanonical(parts);\n let expected: string | undefined; // loop-invariant HMAC — computed at most once, lazily\n let checked = 0;\n let matched = false;\n for (const entry of header.split(' ')) {\n const comma = entry.indexOf(',');\n if (comma <= 0) continue; // no scheme/hex separator (e.g. \"v1\" or \"\")\n if (entry.slice(0, comma) !== SIGNING_SCHEME) continue;\n const hex = entry.slice(comma + 1);\n if (hex.length === 0) continue;\n if (++checked > MAX_SIG_ENTRIES) break;\n if (expected === undefined) expected = await signPayload(secret, canonical);\n if (timingSafeEqual(expected, hex)) matched = true;\n }\n return matched;\n}\n\nexport type VerifyResult =\n | { ok: true; id: string; timestamp: number; idempotencyKey?: string; deliveryId?: string; body: string }\n | { ok: false; reason: 'missing_signature' | 'invalid_signature' | 'stale_timestamp' | 'malformed' };\n\nexport interface VerifyInput {\n /** The HMAC signing secret shared with Data (the trigger/source signing secret). */\n secret: string;\n /** Request headers — a Fetch `Headers` or a plain object (case-insensitive lookup either way). */\n headers: Headers | Record<string, string | string[] | undefined>;\n /** HTTP method of the incoming request. */\n method: string;\n /** Request path the signature was computed over: `pathname + search` (see README path-matching). */\n path: string;\n /** RAW request body bytes as a string — NEVER a re-stringified parsed object. */\n rawBody: string;\n /** Allowed clock skew in seconds (both directions). A finite value is clamped to ≥1 (never 0);\n * unset / NaN / Infinity fall back to the 300s default (a bad value can't disable the window). */\n toleranceSec?: number;\n /** Override \"now\" in ms (tests). */\n now?: number;\n}\n\n/**\n * Verify an inbound Data request end to end: signature present + valid, and timestamp within the\n * skew window. Returns a discriminated result; never throws on bad input.\n */\nexport async function verify(input: VerifyInput): Promise<VerifyResult> {\n const get = headerGetter(input.headers);\n const signature = get(DATA_HEADERS.signature);\n if (!signature) return { ok: false, reason: 'missing_signature' };\n\n const tsRaw = get(DATA_HEADERS.timestamp);\n const id = get(DATA_HEADERS.id);\n if (!tsRaw || !id) return { ok: false, reason: 'malformed' };\n if (!/^\\d+$/.test(tsRaw)) return { ok: false, reason: 'malformed' };\n const timestamp = Number(tsRaw);\n if (!Number.isFinite(timestamp)) return { ok: false, reason: 'malformed' };\n\n // Non-finite (unset / NaN / Infinity, e.g. `Number(process.env.X)` with X missing) → the 300s\n // default, so a misconfigured tolerance can never silently disable the replay window. A finite value\n // is clamped to ≥1 (never 0). To accept a wider window, pass an explicit large finite number.\n const t = input.toleranceSec;\n const tolerance = Number.isFinite(t) ? Math.max(1, Math.floor(t as number)) : 300;\n const now = unixSeconds(input.now);\n if (Math.abs(now - timestamp) > tolerance) return { ok: false, reason: 'stale_timestamp' };\n\n const parts: SignedParts = { id, timestamp, method: input.method, path: input.path, body: input.rawBody };\n const valid = await verifySignatureHeader(input.secret, parts, signature);\n if (!valid) return { ok: false, reason: 'invalid_signature' };\n\n return {\n ok: true,\n id,\n timestamp,\n idempotencyKey: get(DATA_HEADERS.idempotencyKey),\n deliveryId: get(DATA_HEADERS.deliveryId),\n body: input.rawBody,\n };\n}\n","import type { ClaimResult, IdempotencyStore, StoredResult } from './types.js';\n\ninterface Entry {\n state: 'pending' | 'done';\n result?: StoredResult;\n expiresAt: number; // epoch ms\n}\n\n/**\n * In-memory idempotency store (per-process). Single-threaded JS gives the atomic first-wins claim for\n * free. An accessed key expires lazily; since every delivery has a unique key, completed entries are\n * otherwise dropped by a periodic full sweep (every {@link SWEEP_EVERY} writes) — so the map is bounded\n * by the deliveries within one TTL window (default 24h), not unbounded. For multiple instances, or to\n * cap memory tightly, implement {@link IdempotencyStore} over a shared backend instead.\n */\nconst DEFAULT_SWEEP_EVERY = 1000;\n\nexport class MemoryStore implements IdempotencyStore {\n private readonly map = new Map<string, Entry>();\n private readonly nowMs: () => number;\n private readonly sweepEvery: number;\n private writes = 0;\n\n constructor(opts?: { now?: () => number; sweepEvery?: number }) {\n this.nowMs = opts?.now ?? (() => Date.now());\n this.sweepEvery = opts?.sweepEvery ?? DEFAULT_SWEEP_EVERY;\n }\n\n /** Number of live entries currently held (after lazily ignoring nothing — counts raw map size). */\n get size(): number {\n return this.map.size;\n }\n\n private live(key: string): Entry | undefined {\n const e = this.map.get(key);\n if (!e) return undefined;\n if (e.expiresAt <= this.nowMs()) {\n this.map.delete(key);\n return undefined;\n }\n return e;\n }\n\n /** Amortized O(1)/write: every SWEEP_EVERY writes, drop all expired entries so the map can't grow forever. */\n private maybeSweep(): void {\n if (++this.writes < this.sweepEvery) return;\n this.writes = 0;\n const now = this.nowMs();\n for (const [k, e] of this.map) if (e.expiresAt <= now) this.map.delete(k);\n }\n\n async claim(key: string, ttlSec: number): Promise<ClaimResult> {\n const e = this.live(key);\n if (!e) {\n this.map.set(key, { state: 'pending', expiresAt: this.nowMs() + ttlSec * 1000 });\n this.maybeSweep();\n return { state: 'fresh' };\n }\n if (e.state === 'done' && e.result) return { state: 'done', result: e.result };\n return { state: 'in_progress' };\n }\n\n async complete(key: string, result: StoredResult, ttlSec: number): Promise<void> {\n this.map.set(key, { state: 'done', result, expiresAt: this.nowMs() + ttlSec * 1000 });\n this.maybeSweep();\n }\n\n async fail(key: string): Promise<void> {\n this.map.delete(key);\n }\n}\n","/**\n * The normalized error envelope an executor returns. The HTTP STATUS is the real contract a sender's\n * retry logic reads (2xx ack, 4xx permanent/no-retry, 5xx & 429 retry); the JSON body is for humans.\n */\nexport interface ErrorEnvelope {\n status: number;\n code: string;\n message: string;\n retriable: boolean;\n}\n\n/**\n * Throw this from a handler to fail with a specific code/retriability. `retriable` decides the status\n * family ({@link toHttpStatus}) so the sender retries (5xx/429) or drops (4xx) correctly.\n */\nexport class ExecutorError extends Error {\n readonly code: string;\n readonly retriable: boolean;\n readonly httpStatus?: number;\n\n constructor(args: { code: string; message: string; retriable: boolean; status?: number }) {\n super(args.message);\n this.name = 'ExecutorError';\n this.code = args.code;\n this.retriable = args.retriable;\n this.httpStatus = args.status;\n }\n\n toEnvelope(): ErrorEnvelope {\n return { status: toHttpStatus(this), code: this.code, message: this.message, retriable: this.retriable };\n }\n}\n\n/**\n * Map an error to an HTTP status. An explicit `status` is honored when it agrees with `retriable`\n * (5xx/429 for retriable, 4xx for permanent); otherwise the family default is used: retriable → 503,\n * permanent → 400.\n */\nexport function toHttpStatus(e: { retriable: boolean; httpStatus?: number }): number {\n const s = e.httpStatus;\n if (e.retriable) {\n return s !== undefined && (s === 429 || (s >= 500 && s <= 599)) ? s : 503;\n }\n return s !== undefined && s >= 400 && s <= 499 ? s : 400;\n}\n\n/** Serialize an envelope to a JSON body string. */\nexport function jsonResponseBody(env: ErrorEnvelope): string {\n return JSON.stringify(env);\n}\n","import { verify, DATA_HEADERS, type VerifyResult } from './core/verify.js';\nimport { headerGetter } from './core/headers.js';\nimport { MemoryStore } from './store/memory.js';\nimport type { IdempotencyStore, StoredResult } from './store/types.js';\nimport { ExecutorError, toHttpStatus, jsonResponseBody, type ErrorEnvelope } from './errors/envelope.js';\n\n/** Framework-neutral request the adapters hand to the executor. `rawBody` is the RAW bytes as a string. */\nexport interface NormalizedRequest {\n method: string;\n path: string;\n headers: Headers | Record<string, string | string[] | undefined>;\n rawBody: string;\n}\n\nexport interface NormalizedResponse {\n status: number;\n body: string;\n contentType: string;\n}\n\n/** Verified request context passed to your handler. Never contains the signing secret. */\nexport interface ExecutorContext {\n id: string;\n idempotencyKey?: string;\n deliveryId?: string;\n timestamp: number;\n body: string;\n}\n\n/** Your business logic. Return a `StoredResult` (cached for idempotent replays) or nothing (→ 204). */\nexport type ExecutorHandler = (ctx: ExecutorContext) => Promise<StoredResult | void> | StoredResult | void;\n\nexport interface CreateExecutorOptions {\n /** HMAC signing secret shared with Data. */\n secret: string;\n handler: ExecutorHandler;\n /** Idempotency store. Default: a per-process {@link MemoryStore}. */\n store?: IdempotencyStore;\n /** Clock-skew tolerance (seconds, both directions). Default 300; clamped to ≥1. */\n toleranceSec?: number;\n /** Skip signature/skew verification (LOCAL DEV ONLY). Dedup still applies if headers are present. */\n devBypass?: boolean;\n /** Idempotency record TTL (seconds). Default 86400 (24h). */\n idempotencyTtlSec?: number;\n /** Reports unexpected handler errors. MUST NOT log secrets/PII — it never receives the secret. */\n onError?: (err: unknown, ctx?: ExecutorContext) => void;\n /**\n * Reproduce the path Data SIGNED when a reverse proxy rewrites it. Must return the exact\n * `pathname+search` Data used (see README path-matching), not necessarily the incoming path.\n */\n pathOverride?: (req: NormalizedRequest) => string;\n}\n\nexport interface Executor {\n handle(req: NormalizedRequest): Promise<NormalizedResponse>;\n}\n\nconst JSON_CT = 'application/json';\n\nfunction errorResponse(env: ErrorEnvelope): NormalizedResponse {\n return { status: env.status, body: jsonResponseBody(env), contentType: JSON_CT };\n}\n\nfunction reasonResponse(reason: Exclude<VerifyResult, { ok: true }>['reason']): NormalizedResponse {\n // missing/invalid → 401 (unauthenticated); stale → 403; malformed → 400.\n const status = reason === 'malformed' ? 400 : reason === 'stale_timestamp' ? 403 : 401;\n return errorResponse({ status, code: reason.toUpperCase(), message: `signature ${reason}`, retriable: false });\n}\n\n/** Read the dedupe context from headers without verifying (dev-bypass path). */\nfunction bypassContext(req: NormalizedRequest): ExecutorContext | null {\n const get = headerGetter(req.headers);\n const id = get(DATA_HEADERS.id);\n const tsRaw = get(DATA_HEADERS.timestamp);\n return {\n id: id ?? '',\n idempotencyKey: get(DATA_HEADERS.idempotencyKey),\n deliveryId: get(DATA_HEADERS.deliveryId),\n timestamp: tsRaw && /^\\d+$/.test(tsRaw) ? Number(tsRaw) : 0,\n body: req.rawBody,\n };\n}\n\nexport function createExecutor(opts: CreateExecutorOptions): Executor {\n const store = opts.store ?? new MemoryStore();\n const ttl = opts.idempotencyTtlSec ?? 86_400;\n\n async function run(ctx: ExecutorContext): Promise<NormalizedResponse> {\n // Dedupe key: the logical idempotency key (proxy writes) or the stable message id (webhooks).\n // Absent both (e.g. dev-bypass with no signed headers) → run without dedupe.\n const key = ctx.idempotencyKey || ctx.id;\n if (!key) return await invoke(ctx);\n\n const claim = await store.claim(key, ttl);\n if (claim.state === 'done') {\n return { status: claim.result.status, body: claim.result.body, contentType: claim.result.contentType ?? JSON_CT };\n }\n if (claim.state === 'in_progress') {\n // A concurrent attempt holds this key — tell the sender to retry. Use a retriable (5xx) status,\n // not 409, so it matches `retriable: true` and the sender's retry convention.\n return errorResponse({ status: 503, code: 'IN_PROGRESS', message: 'duplicate delivery in progress', retriable: true });\n }\n try {\n const res = await invoke(ctx);\n await store.complete(key, { status: res.status, body: res.body, contentType: res.contentType }, ttl);\n return res;\n } catch (err) {\n const env = toEnvelope(err);\n const res = errorResponse(env);\n if (env.retriable) {\n await store.fail(key); // retriable: release the key so the retry re-runs (don't cache failures)\n } else {\n // permanent: cache the 4xx so a duplicate delivery replays it instead of re-running.\n await store.complete(key, { status: res.status, body: res.body, contentType: res.contentType }, ttl);\n }\n if (!(err instanceof ExecutorError)) opts.onError?.(err, ctx);\n return res;\n }\n }\n\n async function invoke(ctx: ExecutorContext): Promise<NormalizedResponse> {\n const result = await opts.handler(ctx);\n if (!result) return { status: 204, body: '', contentType: JSON_CT };\n return { status: result.status, body: result.body, contentType: result.contentType ?? JSON_CT };\n }\n\n return {\n async handle(req: NormalizedRequest): Promise<NormalizedResponse> {\n const path = opts.pathOverride ? opts.pathOverride(req) : req.path;\n\n if (opts.devBypass) {\n const ctx = bypassContext(req);\n return ctx ? await run(ctx) : reasonResponse('malformed');\n }\n\n const result = await verify({\n secret: opts.secret,\n headers: req.headers,\n method: req.method,\n path,\n rawBody: req.rawBody,\n toleranceSec: opts.toleranceSec,\n });\n if (!result.ok) return reasonResponse(result.reason);\n\n return await run({\n id: result.id,\n idempotencyKey: result.idempotencyKey,\n deliveryId: result.deliveryId,\n timestamp: result.timestamp,\n body: result.body,\n });\n },\n };\n}\n\nfunction toEnvelope(err: unknown): ErrorEnvelope {\n if (err instanceof ExecutorError) return err.toEnvelope();\n // Unknown error → retriable 500 so the sender retries; message is generic (no secret/PII).\n return { status: toHttpStatus({ retriable: true }), code: 'INTERNAL', message: 'handler error', retriable: true };\n}\n","import type { Executor } from '../executor.js';\n\n/**\n * Wrap an {@link Executor} as a Fetch-API handler `(Request) => Promise<Response>` — works on Hono,\n * Next.js route handlers, Cloudflare Workers, Deno, and Bun.\n *\n * Reads the RAW body via `req.text()` BEFORE any parsing (verifying a re-stringified parsed body would\n * fail). The signed `path` is `new URL(req.url).pathname + search`; if a reverse proxy rewrites the\n * path, pass `pathOverride` to `createExecutor`.\n */\nexport function toFetchHandler(executor: Executor): (req: Request) => Promise<Response> {\n return async (req: Request): Promise<Response> => {\n const rawBody = await req.text();\n const url = new URL(req.url);\n const res = await executor.handle({\n method: req.method,\n path: url.pathname + url.search,\n headers: req.headers,\n rawBody,\n });\n // 204/205/304 are null-body statuses — passing any body (even '') throws in spec-compliant\n // runtimes, so send `null` when the body is empty.\n return new Response(res.body || null, { status: res.status, headers: { 'content-type': res.contentType } });\n };\n}\n","import type { Executor } from '../executor.js';\n\n/**\n * Minimal structural shapes of Node's `http.IncomingMessage` / `http.ServerResponse` (and Express's\n * `req`/`res`). Declared structurally so the package imports no Node built-ins at runtime.\n */\nexport interface NodeReq extends AsyncIterable<unknown> {\n method?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n}\nexport interface NodeRes {\n writeHead(status: number, headers?: Record<string, string>): unknown;\n end(chunk?: string): unknown;\n}\n\n/**\n * Wrap an {@link Executor} as a Node/Express handler `(req, res) => Promise<void>`.\n *\n * IMPORTANT: this reads the RAW request stream itself, so it MUST run BEFORE any body parser\n * (`express.json()`, `body-parser`, …). If a parser has already consumed the stream, the raw bytes are\n * gone and verification fails. Mount this on your executor route before such middleware.\n *\n * The signed `path` comes from `req.url` (already `pathname+search`); use `pathOverride` for proxies.\n */\nexport function toNodeHandler(executor: Executor): (req: NodeReq, res: NodeRes) => Promise<void> {\n return async (req: NodeReq, res: NodeRes): Promise<void> => {\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer));\n }\n const rawBody = Buffer.concat(chunks).toString('utf8');\n\n const out = await executor.handle({\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: req.headers,\n rawBody,\n });\n res.writeHead(out.status, { 'content-type': out.contentType });\n res.end(out.body);\n };\n}\n","/**\n * Sign a request TO Data — the inverse of the executor's verify side. Use this when an executor POSTs\n * a result back to an inbound webhook on a base (the async write-back leg): the inbound webhook ingest\n * verifies a Signing v1 signature, and this produces it from the inbound webhook's shared secret.\n *\n * Returns the three headers to attach (`X-Data-Signature`, `X-Data-Timestamp`, `X-Data-Id`). The\n * canonical is byte-identical to the verify side (the cross-package conformance vector guards drift).\n */\n\nimport { buildCanonical, SIGNING_SCHEME, unixSeconds } from './canonical.js';\nimport { signPayload } from './hmac.js';\n\nexport interface SignRequestInput {\n /** HMAC shared secret for the target inbound webhook. */\n secret: string;\n /** HTTP method of the request being signed (e.g. 'POST'). */\n method: string;\n /** Full target URL — its `pathname + search` is bound into the signature. */\n url: string;\n /** Raw request body string (default `''`). MUST be the exact bytes sent. */\n body?: string;\n /** Message id → `X-Data-Id`, bound into the signature. Default: a generated UUID. */\n id?: string;\n /** Unix seconds → `X-Data-Timestamp`. Default: now. */\n timestamp?: number;\n}\n\nexport interface SignedHeaders {\n 'X-Data-Signature': string;\n 'X-Data-Timestamp': string;\n 'X-Data-Id': string;\n}\n\n/** Sign a request to Data and return the headers to attach. */\nexport async function signRequest(input: SignRequestInput): Promise<SignedHeaders> {\n const id = input.id ?? crypto.randomUUID();\n const timestamp = input.timestamp ?? unixSeconds();\n const body = input.body ?? '';\n const { pathname, search } = new URL(input.url);\n const hex = await signPayload(input.secret, buildCanonical({ id, timestamp, method: input.method, path: pathname + search, body }));\n return {\n 'X-Data-Signature': `${SIGNING_SCHEME},${hex}`,\n 'X-Data-Timestamp': String(timestamp),\n 'X-Data-Id': id,\n };\n}\n","/**\n * Executor credential pull — the client side of Data's scoped, single-use binary-credential delivery.\n *\n * When a source declares `executor_secrets`, every proxied dispatch to the executor carries\n * `X-Data-Credential-Url`/`-Token`/`-Names`. The executor calls `fetchExecutorCredential` with the\n * INBOUND request to pull a declared credential by name (e.g. an mTLS client cert it must present to a\n * downstream upstream). The grant is single-use per name and short-lived, so fetch each credential ONCE\n * per invocation and reuse the value — a re-pull of the same name fails (the grant is already claimed).\n *\n * The returned `value` is delivered verbatim (binary credentials are stored base64-encoded); decode it\n * per `contentType` (e.g. base64-decode an `application/x-pkcs12` bundle). This helper never decodes.\n *\n * SECURITY: call this ONLY from a handler whose inbound request the SDK has already verified (e.g. inside\n * `createExecutor`'s handler). The grant headers are NOT part of the request's HMAC signature, so anything\n * that can rewrite headers in front of your executor (a TLS-terminating proxy/gateway) could swap the\n * callback URL. Pass `allowedOrigins` (your Data origin, e.g. `['https://data.wayai.pro']`) to reject a\n * rewritten URL — recommended whenever your executor sits behind such an intermediary.\n */\n\nimport { headerGetter } from './headers.js';\nimport { DATA_HEADERS } from './verify.js';\nimport { ExecutorError } from '../errors/envelope.js';\n\n/** A pulled credential. `value` is delivered verbatim (base64 for binary); decode per `contentType`. */\nexport interface ExecutorCredential {\n name: string;\n contentType: string;\n value: string;\n}\n\nexport interface FetchCredentialOptions {\n /** Override the fetch implementation (defaults to the global `fetch`). */\n fetchImpl?: typeof fetch;\n /**\n * If set (and non-empty), the callback URL's origin must be one of these — rejects a URL rewritten in\n * transit (the grant headers are NOT covered by the request signature). Pass your Data origin(s),\n * e.g. `['https://data.wayai.pro']`. Recommended when the executor sits behind a TLS-terminating proxy.\n *\n * Fails closed on an exact origin match: if the Data origin changes and this list still names only\n * the old one, every pull throws `credential_untrusted_url` (never retried). List every origin your\n * deployment may send from, and add a new one BEFORE it starts arriving.\n */\n allowedOrigins?: string[];\n}\n\n/** Anything carrying the inbound request headers: a NormalizedRequest, a Fetch `Request`, or raw headers. */\ntype HeaderSource =\n | Headers\n | Record<string, string | string[] | undefined>\n | { headers: Headers | Record<string, string | string[] | undefined> };\n\nfunction resolveHeaders(source: HeaderSource): Headers | Record<string, string | string[] | undefined> {\n if (source instanceof Headers) return source;\n // A NormalizedRequest / Fetch Request carries headers under `.headers` (a Headers or a record); a bare\n // headers record does not (its `.headers`, if any, would be a string value, not an object) — so guard\n // on the shape rather than `'headers' in source`, which a record with a \"headers\" key would spoof.\n const nested = (source as { headers?: unknown }).headers;\n if (nested instanceof Headers) return nested;\n if (nested && typeof nested === 'object') return nested as Record<string, string | string[] | undefined>;\n return source as Record<string, string | string[] | undefined>;\n}\n\n/**\n * Pull a declared credential by name from Data, using the grant advertised on the inbound dispatch.\n * Throws `ExecutorError` if the request carries no grant, the name isn't in the grant, or the pull is\n * denied/fails. A 403 (grant invalid/expired/already-used) is never retried; a transient 5xx/network\n * error is retried once.\n */\nexport async function fetchExecutorCredential(\n source: HeaderSource,\n name: string,\n opts: FetchCredentialOptions = {},\n): Promise<ExecutorCredential> {\n const get = headerGetter(resolveHeaders(source));\n const url = get(DATA_HEADERS.credentialUrl);\n const token = get(DATA_HEADERS.credentialToken);\n const namesHeader = get(DATA_HEADERS.credentialNames);\n if (!url || !token || namesHeader === undefined) {\n throw new ExecutorError({ code: 'no_credential_grant', message: 'Request carries no executor credential grant', retriable: false });\n }\n // The callback URL is transport-trusted, not signature-bound — pin it when an allowlist is supplied so a\n // rewritten URL can't redirect the single-use grant token to an attacker host.\n if (opts.allowedOrigins && opts.allowedOrigins.length > 0) {\n let origin: string;\n try { origin = new URL(url).origin; } catch { throw new ExecutorError({ code: 'credential_bad_url', message: 'Credential callback URL is not a valid URL', retriable: false }); }\n // Normalize each allowlist entry through URL.origin too, so a caller's trailing slash / explicit\n // default port (e.g. 'https://data.wayai.pro/' or ':443') still matches. A malformed entry keeps its\n // raw form and simply never matches (it can't grant), rather than breaking the whole check.\n const allowed = opts.allowedOrigins.map((o) => { try { return new URL(o).origin; } catch { return o; } });\n if (!allowed.includes(origin)) {\n throw new ExecutorError({ code: 'credential_untrusted_url', message: `Credential callback URL origin '${origin}' is not in allowedOrigins`, retriable: false });\n }\n }\n const names = namesHeader.split(',').map((n) => n.trim()).filter(Boolean);\n if (!names.includes(name)) {\n throw new ExecutorError({ code: 'undeclared_credential', message: `Credential '${name}' is not in the grant (declared: ${names.join(', ') || 'none'})`, retriable: false });\n }\n\n const doFetch = opts.fetchImpl ?? fetch;\n const body = JSON.stringify({ token, name });\n\n // Up to 2 attempts: retry once on a transient 5xx/network error, never on a 403 (single-use — a retry\n // would just hit `already_redeemed`). A 5xx that landed AFTER the grant was claimed surfaces as a clean\n // 403 on retry, which is the correct terminal outcome (the credential is consumed; Data mints a fresh\n // grant on the next dispatch).\n for (let attempt = 0; attempt < 2; attempt++) {\n let res: Response;\n try {\n // `redirect: 'manual'` so a 3xx from the (pinned) URL can't re-POST the token-bearing body to\n // another host — a redirect surfaces as a non-ok status → credential_fetch_failed below.\n res = await doFetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body, redirect: 'manual' });\n } catch {\n if (attempt === 0) continue; // network error → retry once\n throw new ExecutorError({ code: 'credential_unreachable', message: 'Credential pull is unreachable', retriable: true });\n }\n\n if (res.status === 403) {\n throw new ExecutorError({ code: 'credential_denied', message: `Credential '${name}' pull was denied (grant invalid, expired, or already used)`, retriable: false, status: 403 });\n }\n if (res.status >= 500) {\n if (attempt === 0) continue; // transient → retry once\n throw new ExecutorError({ code: 'credential_unavailable', message: `Credential pull failed (status ${res.status})`, retriable: true, status: res.status });\n }\n if (!res.ok) {\n throw new ExecutorError({ code: 'credential_fetch_failed', message: `Credential pull failed (status ${res.status})`, retriable: false, status: res.status });\n }\n\n const parsed = (await res.json().catch(() => null)) as { data?: { name?: string; content_type?: string; value?: string } } | null;\n const data = parsed?.data;\n if (!data || typeof data.value !== 'string') {\n throw new ExecutorError({ code: 'credential_malformed', message: 'Credential pull returned an unexpected body', retriable: false });\n }\n return { name: data.name ?? name, contentType: data.content_type ?? 'application/octet-stream', value: data.value };\n }\n\n // Unreachable: the loop returns or throws on every path.\n throw new ExecutorError({ code: 'credential_unavailable', message: 'Credential pull failed', retriable: true });\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -49,7 +49,7 @@ interface ExecutorContext {
49
49
  /** Your business logic. Return a `StoredResult` (cached for idempotent replays) or nothing (→ 204). */
50
50
  type ExecutorHandler = (ctx: ExecutorContext) => Promise<StoredResult | void> | StoredResult | void;
51
51
  interface CreateExecutorOptions {
52
- /** HMAC signing secret shared with Rekor. */
52
+ /** HMAC signing secret shared with Data. */
53
53
  secret: string;
54
54
  handler: ExecutorHandler;
55
55
  /** Idempotency store. Default: a per-process {@link MemoryStore}. */
@@ -63,8 +63,8 @@ interface CreateExecutorOptions {
63
63
  /** Reports unexpected handler errors. MUST NOT log secrets/PII — it never receives the secret. */
64
64
  onError?: (err: unknown, ctx?: ExecutorContext) => void;
65
65
  /**
66
- * Reproduce the path Rekor SIGNED when a reverse proxy rewrites it. Must return the exact
67
- * `pathname+search` Rekor used (see README path-matching), not necessarily the incoming path.
66
+ * Reproduce the path Data SIGNED when a reverse proxy rewrites it. Must return the exact
67
+ * `pathname+search` Data used (see README path-matching), not necessarily the incoming path.
68
68
  */
69
69
  pathOverride?: (req: NormalizedRequest) => string;
70
70
  }
@@ -101,16 +101,16 @@ interface NodeRes {
101
101
  *
102
102
  * IMPORTANT: this reads the RAW request stream itself, so it MUST run BEFORE any body parser
103
103
  * (`express.json()`, `body-parser`, …). If a parser has already consumed the stream, the raw bytes are
104
- * gone and verification fails. Mount this on the Rekor route before such middleware.
104
+ * gone and verification fails. Mount this on your executor route before such middleware.
105
105
  *
106
106
  * The signed `path` comes from `req.url` (already `pathname+search`); use `pathOverride` for proxies.
107
107
  */
108
108
  declare function toNodeHandler(executor: Executor): (req: NodeReq, res: NodeRes) => Promise<void>;
109
109
 
110
110
  /**
111
- * Rekor "Signing v1" canonical — the exact string Rekor HMAC-signs for every request it sends
112
- * (trigger webhooks + signed proxied calls). Ported verbatim from the backend's
113
- * `workers/shared/src/signing.ts`; kept byte-identical by the cross-package conformance vector.
111
+ * The "Signing v1" canonical — the exact string Data HMAC-signs for every request it sends
112
+ * (trigger webhooks + signed proxied calls). Ported verbatim from the backend's implementation and
113
+ * kept byte-identical to it by the cross-package conformance vector (test/conformance.test.ts).
114
114
  *
115
115
  * canonical = ["v1", id, String(unixTimestamp), method, path, body].join("\n")
116
116
  *
@@ -138,11 +138,11 @@ declare function buildCanonical(parts: SignedParts): string;
138
138
  declare function unixSeconds(nowMs?: number): number;
139
139
 
140
140
  /**
141
- * Verify a Rekor "Signing v1" request: HMAC over the canonical, plus timestamp-skew enforcement
142
- * (the receiver's job — Rekor only emits). All inputs are the raw request bits the adapter extracts.
141
+ * Verify a "Signing v1" request: HMAC over the canonical, plus timestamp-skew enforcement
142
+ * (the receiver's job — Data only emits). All inputs are the raw request bits the adapter extracts.
143
143
  */
144
144
 
145
- /** Lowercased header names Rekor emits. */
145
+ /** Lowercased header names Data emits. */
146
146
  declare const DATA_HEADERS: {
147
147
  readonly signature: "x-data-signature";
148
148
  readonly timestamp: "x-data-timestamp";
@@ -154,7 +154,7 @@ declare const DATA_HEADERS: {
154
154
  readonly credentialNames: "x-data-credential-names";
155
155
  };
156
156
  /**
157
- * Max `scheme,hex` candidates we'll HMAC-check from one `X-Data-Signature` header. Rekor emits one
157
+ * Max `scheme,hex` candidates we'll HMAC-check from one `X-Data-Signature` header. Data emits one
158
158
  * (two during a future key rotation); the cap stops a hostile header with thousands of entries from
159
159
  * forcing thousands of HMACs per request.
160
160
  */
@@ -178,7 +178,7 @@ type VerifyResult = {
178
178
  reason: 'missing_signature' | 'invalid_signature' | 'stale_timestamp' | 'malformed';
179
179
  };
180
180
  interface VerifyInput {
181
- /** The HMAC signing secret shared with Rekor (the trigger/source signing secret). */
181
+ /** The HMAC signing secret shared with Data (the trigger/source signing secret). */
182
182
  secret: string;
183
183
  /** Request headers — a Fetch `Headers` or a plain object (case-insensitive lookup either way). */
184
184
  headers: Headers | Record<string, string | string[] | undefined>;
@@ -195,21 +195,21 @@ interface VerifyInput {
195
195
  now?: number;
196
196
  }
197
197
  /**
198
- * Verify an inbound Rekor request end to end: signature present + valid, and timestamp within the
198
+ * Verify an inbound Data request end to end: signature present + valid, and timestamp within the
199
199
  * skew window. Returns a discriminated result; never throws on bad input.
200
200
  */
201
201
  declare function verify(input: VerifyInput): Promise<VerifyResult>;
202
202
 
203
203
  /**
204
- * Sign a request TO Rekor — the inverse of the executor's verify side. Use this when an executor POSTs
205
- * a result back to a Rekor inbound webhook (the async write-back leg): Rekor's inbound webhook ingest verifies a Signing v1
206
- * signature, and this produces it from the inbound webhook's shared secret.
204
+ * Sign a request TO Data — the inverse of the executor's verify side. Use this when an executor POSTs
205
+ * a result back to an inbound webhook on a base (the async write-back leg): the inbound webhook ingest
206
+ * verifies a Signing v1 signature, and this produces it from the inbound webhook's shared secret.
207
207
  *
208
208
  * Returns the three headers to attach (`X-Data-Signature`, `X-Data-Timestamp`, `X-Data-Id`). The
209
209
  * canonical is byte-identical to the verify side (the cross-package conformance vector guards drift).
210
210
  */
211
211
  interface SignRequestInput {
212
- /** HMAC shared secret for the target Rekor inbound webhook. */
212
+ /** HMAC shared secret for the target inbound webhook. */
213
213
  secret: string;
214
214
  /** HTTP method of the request being signed (e.g. 'POST'). */
215
215
  method: string;
@@ -227,25 +227,25 @@ interface SignedHeaders {
227
227
  'X-Data-Timestamp': string;
228
228
  'X-Data-Id': string;
229
229
  }
230
- /** Sign a request to Rekor and return the headers to attach. */
230
+ /** Sign a request to Data and return the headers to attach. */
231
231
  declare function signRequest(input: SignRequestInput): Promise<SignedHeaders>;
232
232
 
233
233
  /**
234
- * Executor credential pull — the client side of Rekor's scoped, single-use binary-credential delivery.
234
+ * Executor credential pull — the client side of Data's scoped, single-use binary-credential delivery.
235
235
  *
236
- * When a Rekor source declares `executor_secrets`, every proxied dispatch to the executor carries
236
+ * When a source declares `executor_secrets`, every proxied dispatch to the executor carries
237
237
  * `X-Data-Credential-Url`/`-Token`/`-Names`. The executor calls `fetchExecutorCredential` with the
238
238
  * INBOUND request to pull a declared credential by name (e.g. an mTLS client cert it must present to a
239
239
  * downstream upstream). The grant is single-use per name and short-lived, so fetch each credential ONCE
240
240
  * per invocation and reuse the value — a re-pull of the same name fails (the grant is already claimed).
241
241
  *
242
- * The returned `value` is delivered verbatim (Rekor stores binary credentials base64-encoded); decode it
242
+ * The returned `value` is delivered verbatim (binary credentials are stored base64-encoded); decode it
243
243
  * per `contentType` (e.g. base64-decode an `application/x-pkcs12` bundle). This helper never decodes.
244
244
  *
245
245
  * SECURITY: call this ONLY from a handler whose inbound request the SDK has already verified (e.g. inside
246
246
  * `createExecutor`'s handler). The grant headers are NOT part of the request's HMAC signature, so anything
247
247
  * that can rewrite headers in front of your executor (a TLS-terminating proxy/gateway) could swap the
248
- * callback URL. Pass `allowedOrigins` (your Rekor origin, e.g. `['https://api.rekor.pro']`) to reject a
248
+ * callback URL. Pass `allowedOrigins` (your Data origin, e.g. `['https://data.wayai.pro']`) to reject a
249
249
  * rewritten URL — recommended whenever your executor sits behind such an intermediary.
250
250
  */
251
251
  /** A pulled credential. `value` is delivered verbatim (base64 for binary); decode per `contentType`. */
@@ -259,8 +259,12 @@ interface FetchCredentialOptions {
259
259
  fetchImpl?: typeof fetch;
260
260
  /**
261
261
  * If set (and non-empty), the callback URL's origin must be one of these — rejects a URL rewritten in
262
- * transit (the grant headers are NOT covered by the request signature). Pass your Rekor origin(s),
263
- * e.g. `['https://api.rekor.pro']`. Recommended when the executor sits behind a TLS-terminating proxy.
262
+ * transit (the grant headers are NOT covered by the request signature). Pass your Data origin(s),
263
+ * e.g. `['https://data.wayai.pro']`. Recommended when the executor sits behind a TLS-terminating proxy.
264
+ *
265
+ * Fails closed on an exact origin match: if the Data origin changes and this list still names only
266
+ * the old one, every pull throws `credential_untrusted_url` (never retried). List every origin your
267
+ * deployment may send from, and add a new one BEFORE it starts arriving.
264
268
  */
265
269
  allowedOrigins?: string[];
266
270
  }
@@ -269,7 +273,7 @@ type HeaderSource = Headers | Record<string, string | string[] | undefined> | {
269
273
  headers: Headers | Record<string, string | string[] | undefined>;
270
274
  };
271
275
  /**
272
- * Pull a declared credential by name from Rekor, using the grant advertised on the inbound dispatch.
276
+ * Pull a declared credential by name from Data, using the grant advertised on the inbound dispatch.
273
277
  * Throws `ExecutorError` if the request carries no grant, the name isn't in the grant, or the pull is
274
278
  * denied/fails. A 403 (grant invalid/expired/already-used) is never retried; a transient 5xx/network
275
279
  * error is retried once.
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ interface ExecutorContext {
49
49
  /** Your business logic. Return a `StoredResult` (cached for idempotent replays) or nothing (→ 204). */
50
50
  type ExecutorHandler = (ctx: ExecutorContext) => Promise<StoredResult | void> | StoredResult | void;
51
51
  interface CreateExecutorOptions {
52
- /** HMAC signing secret shared with Rekor. */
52
+ /** HMAC signing secret shared with Data. */
53
53
  secret: string;
54
54
  handler: ExecutorHandler;
55
55
  /** Idempotency store. Default: a per-process {@link MemoryStore}. */
@@ -63,8 +63,8 @@ interface CreateExecutorOptions {
63
63
  /** Reports unexpected handler errors. MUST NOT log secrets/PII — it never receives the secret. */
64
64
  onError?: (err: unknown, ctx?: ExecutorContext) => void;
65
65
  /**
66
- * Reproduce the path Rekor SIGNED when a reverse proxy rewrites it. Must return the exact
67
- * `pathname+search` Rekor used (see README path-matching), not necessarily the incoming path.
66
+ * Reproduce the path Data SIGNED when a reverse proxy rewrites it. Must return the exact
67
+ * `pathname+search` Data used (see README path-matching), not necessarily the incoming path.
68
68
  */
69
69
  pathOverride?: (req: NormalizedRequest) => string;
70
70
  }
@@ -101,16 +101,16 @@ interface NodeRes {
101
101
  *
102
102
  * IMPORTANT: this reads the RAW request stream itself, so it MUST run BEFORE any body parser
103
103
  * (`express.json()`, `body-parser`, …). If a parser has already consumed the stream, the raw bytes are
104
- * gone and verification fails. Mount this on the Rekor route before such middleware.
104
+ * gone and verification fails. Mount this on your executor route before such middleware.
105
105
  *
106
106
  * The signed `path` comes from `req.url` (already `pathname+search`); use `pathOverride` for proxies.
107
107
  */
108
108
  declare function toNodeHandler(executor: Executor): (req: NodeReq, res: NodeRes) => Promise<void>;
109
109
 
110
110
  /**
111
- * Rekor "Signing v1" canonical — the exact string Rekor HMAC-signs for every request it sends
112
- * (trigger webhooks + signed proxied calls). Ported verbatim from the backend's
113
- * `workers/shared/src/signing.ts`; kept byte-identical by the cross-package conformance vector.
111
+ * The "Signing v1" canonical — the exact string Data HMAC-signs for every request it sends
112
+ * (trigger webhooks + signed proxied calls). Ported verbatim from the backend's implementation and
113
+ * kept byte-identical to it by the cross-package conformance vector (test/conformance.test.ts).
114
114
  *
115
115
  * canonical = ["v1", id, String(unixTimestamp), method, path, body].join("\n")
116
116
  *
@@ -138,11 +138,11 @@ declare function buildCanonical(parts: SignedParts): string;
138
138
  declare function unixSeconds(nowMs?: number): number;
139
139
 
140
140
  /**
141
- * Verify a Rekor "Signing v1" request: HMAC over the canonical, plus timestamp-skew enforcement
142
- * (the receiver's job — Rekor only emits). All inputs are the raw request bits the adapter extracts.
141
+ * Verify a "Signing v1" request: HMAC over the canonical, plus timestamp-skew enforcement
142
+ * (the receiver's job — Data only emits). All inputs are the raw request bits the adapter extracts.
143
143
  */
144
144
 
145
- /** Lowercased header names Rekor emits. */
145
+ /** Lowercased header names Data emits. */
146
146
  declare const DATA_HEADERS: {
147
147
  readonly signature: "x-data-signature";
148
148
  readonly timestamp: "x-data-timestamp";
@@ -154,7 +154,7 @@ declare const DATA_HEADERS: {
154
154
  readonly credentialNames: "x-data-credential-names";
155
155
  };
156
156
  /**
157
- * Max `scheme,hex` candidates we'll HMAC-check from one `X-Data-Signature` header. Rekor emits one
157
+ * Max `scheme,hex` candidates we'll HMAC-check from one `X-Data-Signature` header. Data emits one
158
158
  * (two during a future key rotation); the cap stops a hostile header with thousands of entries from
159
159
  * forcing thousands of HMACs per request.
160
160
  */
@@ -178,7 +178,7 @@ type VerifyResult = {
178
178
  reason: 'missing_signature' | 'invalid_signature' | 'stale_timestamp' | 'malformed';
179
179
  };
180
180
  interface VerifyInput {
181
- /** The HMAC signing secret shared with Rekor (the trigger/source signing secret). */
181
+ /** The HMAC signing secret shared with Data (the trigger/source signing secret). */
182
182
  secret: string;
183
183
  /** Request headers — a Fetch `Headers` or a plain object (case-insensitive lookup either way). */
184
184
  headers: Headers | Record<string, string | string[] | undefined>;
@@ -195,21 +195,21 @@ interface VerifyInput {
195
195
  now?: number;
196
196
  }
197
197
  /**
198
- * Verify an inbound Rekor request end to end: signature present + valid, and timestamp within the
198
+ * Verify an inbound Data request end to end: signature present + valid, and timestamp within the
199
199
  * skew window. Returns a discriminated result; never throws on bad input.
200
200
  */
201
201
  declare function verify(input: VerifyInput): Promise<VerifyResult>;
202
202
 
203
203
  /**
204
- * Sign a request TO Rekor — the inverse of the executor's verify side. Use this when an executor POSTs
205
- * a result back to a Rekor inbound webhook (the async write-back leg): Rekor's inbound webhook ingest verifies a Signing v1
206
- * signature, and this produces it from the inbound webhook's shared secret.
204
+ * Sign a request TO Data — the inverse of the executor's verify side. Use this when an executor POSTs
205
+ * a result back to an inbound webhook on a base (the async write-back leg): the inbound webhook ingest
206
+ * verifies a Signing v1 signature, and this produces it from the inbound webhook's shared secret.
207
207
  *
208
208
  * Returns the three headers to attach (`X-Data-Signature`, `X-Data-Timestamp`, `X-Data-Id`). The
209
209
  * canonical is byte-identical to the verify side (the cross-package conformance vector guards drift).
210
210
  */
211
211
  interface SignRequestInput {
212
- /** HMAC shared secret for the target Rekor inbound webhook. */
212
+ /** HMAC shared secret for the target inbound webhook. */
213
213
  secret: string;
214
214
  /** HTTP method of the request being signed (e.g. 'POST'). */
215
215
  method: string;
@@ -227,25 +227,25 @@ interface SignedHeaders {
227
227
  'X-Data-Timestamp': string;
228
228
  'X-Data-Id': string;
229
229
  }
230
- /** Sign a request to Rekor and return the headers to attach. */
230
+ /** Sign a request to Data and return the headers to attach. */
231
231
  declare function signRequest(input: SignRequestInput): Promise<SignedHeaders>;
232
232
 
233
233
  /**
234
- * Executor credential pull — the client side of Rekor's scoped, single-use binary-credential delivery.
234
+ * Executor credential pull — the client side of Data's scoped, single-use binary-credential delivery.
235
235
  *
236
- * When a Rekor source declares `executor_secrets`, every proxied dispatch to the executor carries
236
+ * When a source declares `executor_secrets`, every proxied dispatch to the executor carries
237
237
  * `X-Data-Credential-Url`/`-Token`/`-Names`. The executor calls `fetchExecutorCredential` with the
238
238
  * INBOUND request to pull a declared credential by name (e.g. an mTLS client cert it must present to a
239
239
  * downstream upstream). The grant is single-use per name and short-lived, so fetch each credential ONCE
240
240
  * per invocation and reuse the value — a re-pull of the same name fails (the grant is already claimed).
241
241
  *
242
- * The returned `value` is delivered verbatim (Rekor stores binary credentials base64-encoded); decode it
242
+ * The returned `value` is delivered verbatim (binary credentials are stored base64-encoded); decode it
243
243
  * per `contentType` (e.g. base64-decode an `application/x-pkcs12` bundle). This helper never decodes.
244
244
  *
245
245
  * SECURITY: call this ONLY from a handler whose inbound request the SDK has already verified (e.g. inside
246
246
  * `createExecutor`'s handler). The grant headers are NOT part of the request's HMAC signature, so anything
247
247
  * that can rewrite headers in front of your executor (a TLS-terminating proxy/gateway) could swap the
248
- * callback URL. Pass `allowedOrigins` (your Rekor origin, e.g. `['https://api.rekor.pro']`) to reject a
248
+ * callback URL. Pass `allowedOrigins` (your Data origin, e.g. `['https://data.wayai.pro']`) to reject a
249
249
  * rewritten URL — recommended whenever your executor sits behind such an intermediary.
250
250
  */
251
251
  /** A pulled credential. `value` is delivered verbatim (base64 for binary); decode per `contentType`. */
@@ -259,8 +259,12 @@ interface FetchCredentialOptions {
259
259
  fetchImpl?: typeof fetch;
260
260
  /**
261
261
  * If set (and non-empty), the callback URL's origin must be one of these — rejects a URL rewritten in
262
- * transit (the grant headers are NOT covered by the request signature). Pass your Rekor origin(s),
263
- * e.g. `['https://api.rekor.pro']`. Recommended when the executor sits behind a TLS-terminating proxy.
262
+ * transit (the grant headers are NOT covered by the request signature). Pass your Data origin(s),
263
+ * e.g. `['https://data.wayai.pro']`. Recommended when the executor sits behind a TLS-terminating proxy.
264
+ *
265
+ * Fails closed on an exact origin match: if the Data origin changes and this list still names only
266
+ * the old one, every pull throws `credential_untrusted_url` (never retried). List every origin your
267
+ * deployment may send from, and add a new one BEFORE it starts arriving.
264
268
  */
265
269
  allowedOrigins?: string[];
266
270
  }
@@ -269,7 +273,7 @@ type HeaderSource = Headers | Record<string, string | string[] | undefined> | {
269
273
  headers: Headers | Record<string, string | string[] | undefined>;
270
274
  };
271
275
  /**
272
- * Pull a declared credential by name from Rekor, using the grant advertised on the inbound dispatch.
276
+ * Pull a declared credential by name from Data, using the grant advertised on the inbound dispatch.
273
277
  * Throws `ExecutorError` if the request carries no grant, the name isn't in the grant, or the pull is
274
278
  * denied/fails. A 403 (grant invalid/expired/already-used) is never retried; a transient 5xx/network
275
279
  * error is retried once.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/hmac.ts","../src/core/headers.ts","../src/core/canonical.ts","../src/core/verify.ts","../src/store/memory.ts","../src/errors/envelope.ts","../src/executor.ts","../src/adapters/fetch.ts","../src/adapters/node.ts","../src/core/sign.ts","../src/core/credential.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * HMAC-SHA256 over Web Crypto — pure, dependency-free, runs in any runtime with `crypto.subtle`\n * (Node ≥20, Cloudflare Workers, Deno, Bun, browsers). Ported verbatim from Rekor's backend\n * `workers/shared/src/hmac.ts`; the cross-package conformance vector (test/conformance.test.ts)\n * guards against drift.\n */\n\nexport async function signPayload(secret: string, payload: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(payload));\n return arrayBufferToHex(signature);\n}\n\n/** Constant-time-ish equality of two hex strings (length-checks first, then XOR-accumulate). */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let result = 0;\n for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return result === 0;\n}\n\nfunction arrayBufferToHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/** Case-insensitive header reader for a Fetch `Headers` or a plain object. A duplicate (array) value is\n * ambiguous and resolves to `undefined` (the signature/timestamp must be unambiguous). Shared by the\n * verify path and the dev-bypass path so they read headers identically. */\nexport type HeaderGetter = (name: string) => string | undefined;\n\nexport function headerGetter(headers: Headers | Record<string, string | string[] | undefined>): HeaderGetter {\n if (headers instanceof Headers) return (name) => headers.get(name) ?? undefined;\n const lower: Record<string, string | string[] | undefined> = {};\n for (const [k, v] of Object.entries(headers)) lower[k.toLowerCase()] = v;\n return (name) => {\n const v = lower[name];\n if (Array.isArray(v)) return v.length === 1 ? v[0] : undefined;\n return v;\n };\n}\n","/**\n * Rekor \"Signing v1\" canonical — the exact string Rekor HMAC-signs for every request it sends\n * (trigger webhooks + signed proxied calls). Ported verbatim from the backend's\n * `workers/shared/src/signing.ts`; kept byte-identical by the cross-package conformance vector.\n *\n * canonical = [\"v1\", id, String(unixTimestamp), method, path, body].join(\"\\n\")\n *\n * SECURITY INVARIANT: newline-joined; the only data-influenced field, `body`, is LAST, and every\n * other field is provably newline-free (the \"v1\" literal, an integer, a uuid/hex id, an HTTP method\n * token, and a URL pathname+search). The signature binds method + path so it can't be replayed as a\n * different request.\n */\n\nexport const SIGNING_SCHEME = 'v1';\n\nexport interface SignedParts {\n /** Stable message id — `X-Data-Id` (= delivery id on webhooks, idempotency key on proxied writes). */\n id: string;\n /** `X-Data-Timestamp`, unix seconds (integer). */\n timestamp: number;\n /** HTTP method (`POST` for trigger webhooks). */\n method: string;\n /** Request path: `new URL(receiverUrl).pathname + search`. */\n path: string;\n /** Exact raw request body string (`\"\"` when none). */\n body: string;\n}\n\n/** Build the canonical signing string. Component order and the `v1` prefix are part of the wire. */\nexport function buildCanonical(parts: SignedParts): string {\n return [SIGNING_SCHEME, parts.id, String(parts.timestamp), parts.method, parts.path, parts.body].join('\\n');\n}\n\n/** Current unix time in whole seconds (Standard Webhooks convention). `nowMs` override for tests. */\nexport function unixSeconds(nowMs?: number): number {\n return Math.floor((nowMs ?? Date.now()) / 1000);\n}\n","/**\n * Verify a Rekor \"Signing v1\" request: HMAC over the canonical, plus timestamp-skew enforcement\n * (the receiver's job — Rekor only emits). All inputs are the raw request bits the adapter extracts.\n */\n\nimport { signPayload, timingSafeEqual } from './hmac.js';\nimport { headerGetter } from './headers.js';\nimport { SIGNING_SCHEME, buildCanonical, unixSeconds, type SignedParts } from './canonical.js';\n\n/** Lowercased header names Rekor emits. */\nexport const DATA_HEADERS = {\n signature: 'x-data-signature',\n timestamp: 'x-data-timestamp',\n id: 'x-data-id',\n idempotencyKey: 'x-data-idempotency-key',\n deliveryId: 'x-data-delivery-id',\n // Executor credential pull: advertised on a proxied dispatch to a source declaring executor_secrets.\n credentialUrl: 'x-data-credential-url',\n credentialToken: 'x-data-credential-token',\n credentialNames: 'x-data-credential-names',\n} as const;\n\n/**\n * Max `scheme,hex` candidates we'll HMAC-check from one `X-Data-Signature` header. Rekor emits one\n * (two during a future key rotation); the cap stops a hostile header with thousands of entries from\n * forcing thousands of HMACs per request.\n */\nexport const MAX_SIG_ENTRIES = 4;\n\n/**\n * Verify a signature header value (`X-Data-Signature`) against the canonical of `parts`. The header\n * is a space-delimited list of `scheme,hex` entries; returns true if any `v1` entry matches (constant-\n * time compare). Malformed/empty/unknown-scheme entries are skipped, never throw. Candidates beyond\n * `MAX_SIG_ENTRIES` are ignored. Does NOT check timestamp skew (see {@link verify}).\n */\nexport async function verifySignatureHeader(secret: string, parts: SignedParts, header: string): Promise<boolean> {\n const canonical = buildCanonical(parts);\n let expected: string | undefined; // loop-invariant HMAC — computed at most once, lazily\n let checked = 0;\n let matched = false;\n for (const entry of header.split(' ')) {\n const comma = entry.indexOf(',');\n if (comma <= 0) continue; // no scheme/hex separator (e.g. \"v1\" or \"\")\n if (entry.slice(0, comma) !== SIGNING_SCHEME) continue;\n const hex = entry.slice(comma + 1);\n if (hex.length === 0) continue;\n if (++checked > MAX_SIG_ENTRIES) break;\n if (expected === undefined) expected = await signPayload(secret, canonical);\n if (timingSafeEqual(expected, hex)) matched = true;\n }\n return matched;\n}\n\nexport type VerifyResult =\n | { ok: true; id: string; timestamp: number; idempotencyKey?: string; deliveryId?: string; body: string }\n | { ok: false; reason: 'missing_signature' | 'invalid_signature' | 'stale_timestamp' | 'malformed' };\n\nexport interface VerifyInput {\n /** The HMAC signing secret shared with Rekor (the trigger/source signing secret). */\n secret: string;\n /** Request headers — a Fetch `Headers` or a plain object (case-insensitive lookup either way). */\n headers: Headers | Record<string, string | string[] | undefined>;\n /** HTTP method of the incoming request. */\n method: string;\n /** Request path the signature was computed over: `pathname + search` (see README path-matching). */\n path: string;\n /** RAW request body bytes as a string — NEVER a re-stringified parsed object. */\n rawBody: string;\n /** Allowed clock skew in seconds (both directions). A finite value is clamped to ≥1 (never 0);\n * unset / NaN / Infinity fall back to the 300s default (a bad value can't disable the window). */\n toleranceSec?: number;\n /** Override \"now\" in ms (tests). */\n now?: number;\n}\n\n/**\n * Verify an inbound Rekor request end to end: signature present + valid, and timestamp within the\n * skew window. Returns a discriminated result; never throws on bad input.\n */\nexport async function verify(input: VerifyInput): Promise<VerifyResult> {\n const get = headerGetter(input.headers);\n const signature = get(DATA_HEADERS.signature);\n if (!signature) return { ok: false, reason: 'missing_signature' };\n\n const tsRaw = get(DATA_HEADERS.timestamp);\n const id = get(DATA_HEADERS.id);\n if (!tsRaw || !id) return { ok: false, reason: 'malformed' };\n if (!/^\\d+$/.test(tsRaw)) return { ok: false, reason: 'malformed' };\n const timestamp = Number(tsRaw);\n if (!Number.isFinite(timestamp)) return { ok: false, reason: 'malformed' };\n\n // Non-finite (unset / NaN / Infinity, e.g. `Number(process.env.X)` with X missing) → the 300s\n // default, so a misconfigured tolerance can never silently disable the replay window. A finite value\n // is clamped to ≥1 (never 0). To accept a wider window, pass an explicit large finite number.\n const t = input.toleranceSec;\n const tolerance = Number.isFinite(t) ? Math.max(1, Math.floor(t as number)) : 300;\n const now = unixSeconds(input.now);\n if (Math.abs(now - timestamp) > tolerance) return { ok: false, reason: 'stale_timestamp' };\n\n const parts: SignedParts = { id, timestamp, method: input.method, path: input.path, body: input.rawBody };\n const valid = await verifySignatureHeader(input.secret, parts, signature);\n if (!valid) return { ok: false, reason: 'invalid_signature' };\n\n return {\n ok: true,\n id,\n timestamp,\n idempotencyKey: get(DATA_HEADERS.idempotencyKey),\n deliveryId: get(DATA_HEADERS.deliveryId),\n body: input.rawBody,\n };\n}\n","import type { ClaimResult, IdempotencyStore, StoredResult } from './types.js';\n\ninterface Entry {\n state: 'pending' | 'done';\n result?: StoredResult;\n expiresAt: number; // epoch ms\n}\n\n/**\n * In-memory idempotency store (per-process). Single-threaded JS gives the atomic first-wins claim for\n * free. An accessed key expires lazily; since every delivery has a unique key, completed entries are\n * otherwise dropped by a periodic full sweep (every {@link SWEEP_EVERY} writes) — so the map is bounded\n * by the deliveries within one TTL window (default 24h), not unbounded. For multiple instances, or to\n * cap memory tightly, implement {@link IdempotencyStore} over a shared backend instead.\n */\nconst DEFAULT_SWEEP_EVERY = 1000;\n\nexport class MemoryStore implements IdempotencyStore {\n private readonly map = new Map<string, Entry>();\n private readonly nowMs: () => number;\n private readonly sweepEvery: number;\n private writes = 0;\n\n constructor(opts?: { now?: () => number; sweepEvery?: number }) {\n this.nowMs = opts?.now ?? (() => Date.now());\n this.sweepEvery = opts?.sweepEvery ?? DEFAULT_SWEEP_EVERY;\n }\n\n /** Number of live entries currently held (after lazily ignoring nothing — counts raw map size). */\n get size(): number {\n return this.map.size;\n }\n\n private live(key: string): Entry | undefined {\n const e = this.map.get(key);\n if (!e) return undefined;\n if (e.expiresAt <= this.nowMs()) {\n this.map.delete(key);\n return undefined;\n }\n return e;\n }\n\n /** Amortized O(1)/write: every SWEEP_EVERY writes, drop all expired entries so the map can't grow forever. */\n private maybeSweep(): void {\n if (++this.writes < this.sweepEvery) return;\n this.writes = 0;\n const now = this.nowMs();\n for (const [k, e] of this.map) if (e.expiresAt <= now) this.map.delete(k);\n }\n\n async claim(key: string, ttlSec: number): Promise<ClaimResult> {\n const e = this.live(key);\n if (!e) {\n this.map.set(key, { state: 'pending', expiresAt: this.nowMs() + ttlSec * 1000 });\n this.maybeSweep();\n return { state: 'fresh' };\n }\n if (e.state === 'done' && e.result) return { state: 'done', result: e.result };\n return { state: 'in_progress' };\n }\n\n async complete(key: string, result: StoredResult, ttlSec: number): Promise<void> {\n this.map.set(key, { state: 'done', result, expiresAt: this.nowMs() + ttlSec * 1000 });\n this.maybeSweep();\n }\n\n async fail(key: string): Promise<void> {\n this.map.delete(key);\n }\n}\n","/**\n * The normalized error envelope an executor returns. The HTTP STATUS is the real contract a sender's\n * retry logic reads (2xx ack, 4xx permanent/no-retry, 5xx & 429 retry); the JSON body is for humans.\n */\nexport interface ErrorEnvelope {\n status: number;\n code: string;\n message: string;\n retriable: boolean;\n}\n\n/**\n * Throw this from a handler to fail with a specific code/retriability. `retriable` decides the status\n * family ({@link toHttpStatus}) so the sender retries (5xx/429) or drops (4xx) correctly.\n */\nexport class ExecutorError extends Error {\n readonly code: string;\n readonly retriable: boolean;\n readonly httpStatus?: number;\n\n constructor(args: { code: string; message: string; retriable: boolean; status?: number }) {\n super(args.message);\n this.name = 'ExecutorError';\n this.code = args.code;\n this.retriable = args.retriable;\n this.httpStatus = args.status;\n }\n\n toEnvelope(): ErrorEnvelope {\n return { status: toHttpStatus(this), code: this.code, message: this.message, retriable: this.retriable };\n }\n}\n\n/**\n * Map an error to an HTTP status. An explicit `status` is honored when it agrees with `retriable`\n * (5xx/429 for retriable, 4xx for permanent); otherwise the family default is used: retriable → 503,\n * permanent → 400.\n */\nexport function toHttpStatus(e: { retriable: boolean; httpStatus?: number }): number {\n const s = e.httpStatus;\n if (e.retriable) {\n return s !== undefined && (s === 429 || (s >= 500 && s <= 599)) ? s : 503;\n }\n return s !== undefined && s >= 400 && s <= 499 ? s : 400;\n}\n\n/** Serialize an envelope to a JSON body string. */\nexport function jsonResponseBody(env: ErrorEnvelope): string {\n return JSON.stringify(env);\n}\n","import { verify, DATA_HEADERS, type VerifyResult } from './core/verify.js';\nimport { headerGetter } from './core/headers.js';\nimport { MemoryStore } from './store/memory.js';\nimport type { IdempotencyStore, StoredResult } from './store/types.js';\nimport { ExecutorError, toHttpStatus, jsonResponseBody, type ErrorEnvelope } from './errors/envelope.js';\n\n/** Framework-neutral request the adapters hand to the executor. `rawBody` is the RAW bytes as a string. */\nexport interface NormalizedRequest {\n method: string;\n path: string;\n headers: Headers | Record<string, string | string[] | undefined>;\n rawBody: string;\n}\n\nexport interface NormalizedResponse {\n status: number;\n body: string;\n contentType: string;\n}\n\n/** Verified request context passed to your handler. Never contains the signing secret. */\nexport interface ExecutorContext {\n id: string;\n idempotencyKey?: string;\n deliveryId?: string;\n timestamp: number;\n body: string;\n}\n\n/** Your business logic. Return a `StoredResult` (cached for idempotent replays) or nothing (→ 204). */\nexport type ExecutorHandler = (ctx: ExecutorContext) => Promise<StoredResult | void> | StoredResult | void;\n\nexport interface CreateExecutorOptions {\n /** HMAC signing secret shared with Rekor. */\n secret: string;\n handler: ExecutorHandler;\n /** Idempotency store. Default: a per-process {@link MemoryStore}. */\n store?: IdempotencyStore;\n /** Clock-skew tolerance (seconds, both directions). Default 300; clamped to ≥1. */\n toleranceSec?: number;\n /** Skip signature/skew verification (LOCAL DEV ONLY). Dedup still applies if headers are present. */\n devBypass?: boolean;\n /** Idempotency record TTL (seconds). Default 86400 (24h). */\n idempotencyTtlSec?: number;\n /** Reports unexpected handler errors. MUST NOT log secrets/PII — it never receives the secret. */\n onError?: (err: unknown, ctx?: ExecutorContext) => void;\n /**\n * Reproduce the path Rekor SIGNED when a reverse proxy rewrites it. Must return the exact\n * `pathname+search` Rekor used (see README path-matching), not necessarily the incoming path.\n */\n pathOverride?: (req: NormalizedRequest) => string;\n}\n\nexport interface Executor {\n handle(req: NormalizedRequest): Promise<NormalizedResponse>;\n}\n\nconst JSON_CT = 'application/json';\n\nfunction errorResponse(env: ErrorEnvelope): NormalizedResponse {\n return { status: env.status, body: jsonResponseBody(env), contentType: JSON_CT };\n}\n\nfunction reasonResponse(reason: Exclude<VerifyResult, { ok: true }>['reason']): NormalizedResponse {\n // missing/invalid → 401 (unauthenticated); stale → 403; malformed → 400.\n const status = reason === 'malformed' ? 400 : reason === 'stale_timestamp' ? 403 : 401;\n return errorResponse({ status, code: reason.toUpperCase(), message: `signature ${reason}`, retriable: false });\n}\n\n/** Read the dedupe context from headers without verifying (dev-bypass path). */\nfunction bypassContext(req: NormalizedRequest): ExecutorContext | null {\n const get = headerGetter(req.headers);\n const id = get(DATA_HEADERS.id);\n const tsRaw = get(DATA_HEADERS.timestamp);\n return {\n id: id ?? '',\n idempotencyKey: get(DATA_HEADERS.idempotencyKey),\n deliveryId: get(DATA_HEADERS.deliveryId),\n timestamp: tsRaw && /^\\d+$/.test(tsRaw) ? Number(tsRaw) : 0,\n body: req.rawBody,\n };\n}\n\nexport function createExecutor(opts: CreateExecutorOptions): Executor {\n const store = opts.store ?? new MemoryStore();\n const ttl = opts.idempotencyTtlSec ?? 86_400;\n\n async function run(ctx: ExecutorContext): Promise<NormalizedResponse> {\n // Dedupe key: the logical idempotency key (proxy writes) or the stable message id (webhooks).\n // Absent both (e.g. dev-bypass with no Rekor headers) → run without dedupe.\n const key = ctx.idempotencyKey || ctx.id;\n if (!key) return await invoke(ctx);\n\n const claim = await store.claim(key, ttl);\n if (claim.state === 'done') {\n return { status: claim.result.status, body: claim.result.body, contentType: claim.result.contentType ?? JSON_CT };\n }\n if (claim.state === 'in_progress') {\n // A concurrent attempt holds this key — tell the sender to retry. Use a retriable (5xx) status,\n // not 409, so it matches `retriable: true` and the sender's retry convention.\n return errorResponse({ status: 503, code: 'IN_PROGRESS', message: 'duplicate delivery in progress', retriable: true });\n }\n try {\n const res = await invoke(ctx);\n await store.complete(key, { status: res.status, body: res.body, contentType: res.contentType }, ttl);\n return res;\n } catch (err) {\n const env = toEnvelope(err);\n const res = errorResponse(env);\n if (env.retriable) {\n await store.fail(key); // retriable: release the key so the retry re-runs (don't cache failures)\n } else {\n // permanent: cache the 4xx so a duplicate delivery replays it instead of re-running.\n await store.complete(key, { status: res.status, body: res.body, contentType: res.contentType }, ttl);\n }\n if (!(err instanceof ExecutorError)) opts.onError?.(err, ctx);\n return res;\n }\n }\n\n async function invoke(ctx: ExecutorContext): Promise<NormalizedResponse> {\n const result = await opts.handler(ctx);\n if (!result) return { status: 204, body: '', contentType: JSON_CT };\n return { status: result.status, body: result.body, contentType: result.contentType ?? JSON_CT };\n }\n\n return {\n async handle(req: NormalizedRequest): Promise<NormalizedResponse> {\n const path = opts.pathOverride ? opts.pathOverride(req) : req.path;\n\n if (opts.devBypass) {\n const ctx = bypassContext(req);\n return ctx ? await run(ctx) : reasonResponse('malformed');\n }\n\n const result = await verify({\n secret: opts.secret,\n headers: req.headers,\n method: req.method,\n path,\n rawBody: req.rawBody,\n toleranceSec: opts.toleranceSec,\n });\n if (!result.ok) return reasonResponse(result.reason);\n\n return await run({\n id: result.id,\n idempotencyKey: result.idempotencyKey,\n deliveryId: result.deliveryId,\n timestamp: result.timestamp,\n body: result.body,\n });\n },\n };\n}\n\nfunction toEnvelope(err: unknown): ErrorEnvelope {\n if (err instanceof ExecutorError) return err.toEnvelope();\n // Unknown error → retriable 500 so the sender retries; message is generic (no secret/PII).\n return { status: toHttpStatus({ retriable: true }), code: 'INTERNAL', message: 'handler error', retriable: true };\n}\n","import type { Executor } from '../executor.js';\n\n/**\n * Wrap an {@link Executor} as a Fetch-API handler `(Request) => Promise<Response>` — works on Hono,\n * Next.js route handlers, Cloudflare Workers, Deno, and Bun.\n *\n * Reads the RAW body via `req.text()` BEFORE any parsing (verifying a re-stringified parsed body would\n * fail). The signed `path` is `new URL(req.url).pathname + search`; if a reverse proxy rewrites the\n * path, pass `pathOverride` to `createExecutor`.\n */\nexport function toFetchHandler(executor: Executor): (req: Request) => Promise<Response> {\n return async (req: Request): Promise<Response> => {\n const rawBody = await req.text();\n const url = new URL(req.url);\n const res = await executor.handle({\n method: req.method,\n path: url.pathname + url.search,\n headers: req.headers,\n rawBody,\n });\n // 204/205/304 are null-body statuses — passing any body (even '') throws in spec-compliant\n // runtimes, so send `null` when the body is empty.\n return new Response(res.body || null, { status: res.status, headers: { 'content-type': res.contentType } });\n };\n}\n","import type { Executor } from '../executor.js';\n\n/**\n * Minimal structural shapes of Node's `http.IncomingMessage` / `http.ServerResponse` (and Express's\n * `req`/`res`). Declared structurally so the package imports no Node built-ins at runtime.\n */\nexport interface NodeReq extends AsyncIterable<unknown> {\n method?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n}\nexport interface NodeRes {\n writeHead(status: number, headers?: Record<string, string>): unknown;\n end(chunk?: string): unknown;\n}\n\n/**\n * Wrap an {@link Executor} as a Node/Express handler `(req, res) => Promise<void>`.\n *\n * IMPORTANT: this reads the RAW request stream itself, so it MUST run BEFORE any body parser\n * (`express.json()`, `body-parser`, …). If a parser has already consumed the stream, the raw bytes are\n * gone and verification fails. Mount this on the Rekor route before such middleware.\n *\n * The signed `path` comes from `req.url` (already `pathname+search`); use `pathOverride` for proxies.\n */\nexport function toNodeHandler(executor: Executor): (req: NodeReq, res: NodeRes) => Promise<void> {\n return async (req: NodeReq, res: NodeRes): Promise<void> => {\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer));\n }\n const rawBody = Buffer.concat(chunks).toString('utf8');\n\n const out = await executor.handle({\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: req.headers,\n rawBody,\n });\n res.writeHead(out.status, { 'content-type': out.contentType });\n res.end(out.body);\n };\n}\n","/**\n * Sign a request TO Rekor — the inverse of the executor's verify side. Use this when an executor POSTs\n * a result back to a Rekor inbound webhook (the async write-back leg): Rekor's inbound webhook ingest verifies a Signing v1\n * signature, and this produces it from the inbound webhook's shared secret.\n *\n * Returns the three headers to attach (`X-Data-Signature`, `X-Data-Timestamp`, `X-Data-Id`). The\n * canonical is byte-identical to the verify side (the cross-package conformance vector guards drift).\n */\n\nimport { buildCanonical, SIGNING_SCHEME, unixSeconds } from './canonical.js';\nimport { signPayload } from './hmac.js';\n\nexport interface SignRequestInput {\n /** HMAC shared secret for the target Rekor inbound webhook. */\n secret: string;\n /** HTTP method of the request being signed (e.g. 'POST'). */\n method: string;\n /** Full target URL — its `pathname + search` is bound into the signature. */\n url: string;\n /** Raw request body string (default `''`). MUST be the exact bytes sent. */\n body?: string;\n /** Message id → `X-Data-Id`, bound into the signature. Default: a generated UUID. */\n id?: string;\n /** Unix seconds → `X-Data-Timestamp`. Default: now. */\n timestamp?: number;\n}\n\nexport interface SignedHeaders {\n 'X-Data-Signature': string;\n 'X-Data-Timestamp': string;\n 'X-Data-Id': string;\n}\n\n/** Sign a request to Rekor and return the headers to attach. */\nexport async function signRequest(input: SignRequestInput): Promise<SignedHeaders> {\n const id = input.id ?? crypto.randomUUID();\n const timestamp = input.timestamp ?? unixSeconds();\n const body = input.body ?? '';\n const { pathname, search } = new URL(input.url);\n const hex = await signPayload(input.secret, buildCanonical({ id, timestamp, method: input.method, path: pathname + search, body }));\n return {\n 'X-Data-Signature': `${SIGNING_SCHEME},${hex}`,\n 'X-Data-Timestamp': String(timestamp),\n 'X-Data-Id': id,\n };\n}\n","/**\n * Executor credential pull — the client side of Rekor's scoped, single-use binary-credential delivery.\n *\n * When a Rekor source declares `executor_secrets`, every proxied dispatch to the executor carries\n * `X-Data-Credential-Url`/`-Token`/`-Names`. The executor calls `fetchExecutorCredential` with the\n * INBOUND request to pull a declared credential by name (e.g. an mTLS client cert it must present to a\n * downstream upstream). The grant is single-use per name and short-lived, so fetch each credential ONCE\n * per invocation and reuse the value — a re-pull of the same name fails (the grant is already claimed).\n *\n * The returned `value` is delivered verbatim (Rekor stores binary credentials base64-encoded); decode it\n * per `contentType` (e.g. base64-decode an `application/x-pkcs12` bundle). This helper never decodes.\n *\n * SECURITY: call this ONLY from a handler whose inbound request the SDK has already verified (e.g. inside\n * `createExecutor`'s handler). The grant headers are NOT part of the request's HMAC signature, so anything\n * that can rewrite headers in front of your executor (a TLS-terminating proxy/gateway) could swap the\n * callback URL. Pass `allowedOrigins` (your Rekor origin, e.g. `['https://api.rekor.pro']`) to reject a\n * rewritten URL — recommended whenever your executor sits behind such an intermediary.\n */\n\nimport { headerGetter } from './headers.js';\nimport { DATA_HEADERS } from './verify.js';\nimport { ExecutorError } from '../errors/envelope.js';\n\n/** A pulled credential. `value` is delivered verbatim (base64 for binary); decode per `contentType`. */\nexport interface ExecutorCredential {\n name: string;\n contentType: string;\n value: string;\n}\n\nexport interface FetchCredentialOptions {\n /** Override the fetch implementation (defaults to the global `fetch`). */\n fetchImpl?: typeof fetch;\n /**\n * If set (and non-empty), the callback URL's origin must be one of these — rejects a URL rewritten in\n * transit (the grant headers are NOT covered by the request signature). Pass your Rekor origin(s),\n * e.g. `['https://api.rekor.pro']`. Recommended when the executor sits behind a TLS-terminating proxy.\n */\n allowedOrigins?: string[];\n}\n\n/** Anything carrying the inbound request headers: a NormalizedRequest, a Fetch `Request`, or raw headers. */\ntype HeaderSource =\n | Headers\n | Record<string, string | string[] | undefined>\n | { headers: Headers | Record<string, string | string[] | undefined> };\n\nfunction resolveHeaders(source: HeaderSource): Headers | Record<string, string | string[] | undefined> {\n if (source instanceof Headers) return source;\n // A NormalizedRequest / Fetch Request carries headers under `.headers` (a Headers or a record); a bare\n // headers record does not (its `.headers`, if any, would be a string value, not an object) — so guard\n // on the shape rather than `'headers' in source`, which a record with a \"headers\" key would spoof.\n const nested = (source as { headers?: unknown }).headers;\n if (nested instanceof Headers) return nested;\n if (nested && typeof nested === 'object') return nested as Record<string, string | string[] | undefined>;\n return source as Record<string, string | string[] | undefined>;\n}\n\n/**\n * Pull a declared credential by name from Rekor, using the grant advertised on the inbound dispatch.\n * Throws `ExecutorError` if the request carries no grant, the name isn't in the grant, or the pull is\n * denied/fails. A 403 (grant invalid/expired/already-used) is never retried; a transient 5xx/network\n * error is retried once.\n */\nexport async function fetchExecutorCredential(\n source: HeaderSource,\n name: string,\n opts: FetchCredentialOptions = {},\n): Promise<ExecutorCredential> {\n const get = headerGetter(resolveHeaders(source));\n const url = get(DATA_HEADERS.credentialUrl);\n const token = get(DATA_HEADERS.credentialToken);\n const namesHeader = get(DATA_HEADERS.credentialNames);\n if (!url || !token || namesHeader === undefined) {\n throw new ExecutorError({ code: 'no_credential_grant', message: 'Request carries no executor credential grant', retriable: false });\n }\n // The callback URL is transport-trusted, not signature-bound — pin it when an allowlist is supplied so a\n // rewritten URL can't redirect the single-use grant token to an attacker host.\n if (opts.allowedOrigins && opts.allowedOrigins.length > 0) {\n let origin: string;\n try { origin = new URL(url).origin; } catch { throw new ExecutorError({ code: 'credential_bad_url', message: 'Credential callback URL is not a valid URL', retriable: false }); }\n // Normalize each allowlist entry through URL.origin too, so a caller's trailing slash / explicit\n // default port (e.g. 'https://api.rekor.pro/' or ':443') still matches. A malformed entry keeps its\n // raw form and simply never matches (it can't grant), rather than breaking the whole check.\n const allowed = opts.allowedOrigins.map((o) => { try { return new URL(o).origin; } catch { return o; } });\n if (!allowed.includes(origin)) {\n throw new ExecutorError({ code: 'credential_untrusted_url', message: `Credential callback URL origin '${origin}' is not in allowedOrigins`, retriable: false });\n }\n }\n const names = namesHeader.split(',').map((n) => n.trim()).filter(Boolean);\n if (!names.includes(name)) {\n throw new ExecutorError({ code: 'undeclared_credential', message: `Credential '${name}' is not in the grant (declared: ${names.join(', ') || 'none'})`, retriable: false });\n }\n\n const doFetch = opts.fetchImpl ?? fetch;\n const body = JSON.stringify({ token, name });\n\n // Up to 2 attempts: retry once on a transient 5xx/network error, never on a 403 (single-use — a retry\n // would just hit `already_redeemed`). A 5xx that landed AFTER the grant was claimed surfaces as a clean\n // 403 on retry, which is the correct terminal outcome (the credential is consumed; Rekor mints a fresh\n // grant on the next dispatch).\n for (let attempt = 0; attempt < 2; attempt++) {\n let res: Response;\n try {\n // `redirect: 'manual'` so a 3xx from the (pinned) URL can't re-POST the token-bearing body to\n // another host — a redirect surfaces as a non-ok status → credential_fetch_failed below.\n res = await doFetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body, redirect: 'manual' });\n } catch {\n if (attempt === 0) continue; // network error → retry once\n throw new ExecutorError({ code: 'credential_unreachable', message: 'Credential pull is unreachable', retriable: true });\n }\n\n if (res.status === 403) {\n throw new ExecutorError({ code: 'credential_denied', message: `Credential '${name}' pull was denied (grant invalid, expired, or already used)`, retriable: false, status: 403 });\n }\n if (res.status >= 500) {\n if (attempt === 0) continue; // transient → retry once\n throw new ExecutorError({ code: 'credential_unavailable', message: `Credential pull failed (status ${res.status})`, retriable: true, status: res.status });\n }\n if (!res.ok) {\n throw new ExecutorError({ code: 'credential_fetch_failed', message: `Credential pull failed (status ${res.status})`, retriable: false, status: res.status });\n }\n\n const parsed = (await res.json().catch(() => null)) as { data?: { name?: string; content_type?: string; value?: string } } | null;\n const data = parsed?.data;\n if (!data || typeof data.value !== 'string') {\n throw new ExecutorError({ code: 'credential_malformed', message: 'Credential pull returned an unexpected body', retriable: false });\n }\n return { name: data.name ?? name, contentType: data.content_type ?? 'application/octet-stream', value: data.value };\n }\n\n // Unreachable: the loop returns or throws on every path.\n throw new ExecutorError({ code: 'credential_unavailable', message: 'Credential pull failed', retriable: true });\n}\n"]}
1
+ {"version":3,"sources":["../src/core/hmac.ts","../src/core/headers.ts","../src/core/canonical.ts","../src/core/verify.ts","../src/store/memory.ts","../src/errors/envelope.ts","../src/executor.ts","../src/adapters/fetch.ts","../src/adapters/node.ts","../src/core/sign.ts","../src/core/credential.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * HMAC-SHA256 over Web Crypto — pure, dependency-free, runs in any runtime with `crypto.subtle`\n * (Cloudflare Workers, Deno, Bun, browsers, and Node — see the package's declared `engines`).\n * Ported verbatim from the backend's implementation; the cross-package conformance vector\n * (test/conformance.test.ts) guards against drift.\n */\n\nexport async function signPayload(secret: string, payload: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(payload));\n return arrayBufferToHex(signature);\n}\n\n/** Constant-time-ish equality of two hex strings (length-checks first, then XOR-accumulate). */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let result = 0;\n for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return result === 0;\n}\n\nfunction arrayBufferToHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/** Case-insensitive header reader for a Fetch `Headers` or a plain object. A duplicate (array) value is\n * ambiguous and resolves to `undefined` (the signature/timestamp must be unambiguous). Shared by the\n * verify path and the dev-bypass path so they read headers identically. */\nexport type HeaderGetter = (name: string) => string | undefined;\n\nexport function headerGetter(headers: Headers | Record<string, string | string[] | undefined>): HeaderGetter {\n if (headers instanceof Headers) return (name) => headers.get(name) ?? undefined;\n const lower: Record<string, string | string[] | undefined> = {};\n for (const [k, v] of Object.entries(headers)) lower[k.toLowerCase()] = v;\n return (name) => {\n const v = lower[name];\n if (Array.isArray(v)) return v.length === 1 ? v[0] : undefined;\n return v;\n };\n}\n","/**\n * The \"Signing v1\" canonical — the exact string Data HMAC-signs for every request it sends\n * (trigger webhooks + signed proxied calls). Ported verbatim from the backend's implementation and\n * kept byte-identical to it by the cross-package conformance vector (test/conformance.test.ts).\n *\n * canonical = [\"v1\", id, String(unixTimestamp), method, path, body].join(\"\\n\")\n *\n * SECURITY INVARIANT: newline-joined; the only data-influenced field, `body`, is LAST, and every\n * other field is provably newline-free (the \"v1\" literal, an integer, a uuid/hex id, an HTTP method\n * token, and a URL pathname+search). The signature binds method + path so it can't be replayed as a\n * different request.\n */\n\nexport const SIGNING_SCHEME = 'v1';\n\nexport interface SignedParts {\n /** Stable message id — `X-Data-Id` (= delivery id on webhooks, idempotency key on proxied writes). */\n id: string;\n /** `X-Data-Timestamp`, unix seconds (integer). */\n timestamp: number;\n /** HTTP method (`POST` for trigger webhooks). */\n method: string;\n /** Request path: `new URL(receiverUrl).pathname + search`. */\n path: string;\n /** Exact raw request body string (`\"\"` when none). */\n body: string;\n}\n\n/** Build the canonical signing string. Component order and the `v1` prefix are part of the wire. */\nexport function buildCanonical(parts: SignedParts): string {\n return [SIGNING_SCHEME, parts.id, String(parts.timestamp), parts.method, parts.path, parts.body].join('\\n');\n}\n\n/** Current unix time in whole seconds (Standard Webhooks convention). `nowMs` override for tests. */\nexport function unixSeconds(nowMs?: number): number {\n return Math.floor((nowMs ?? Date.now()) / 1000);\n}\n","/**\n * Verify a \"Signing v1\" request: HMAC over the canonical, plus timestamp-skew enforcement\n * (the receiver's job — Data only emits). All inputs are the raw request bits the adapter extracts.\n */\n\nimport { signPayload, timingSafeEqual } from './hmac.js';\nimport { headerGetter } from './headers.js';\nimport { SIGNING_SCHEME, buildCanonical, unixSeconds, type SignedParts } from './canonical.js';\n\n/** Lowercased header names Data emits. */\nexport const DATA_HEADERS = {\n signature: 'x-data-signature',\n timestamp: 'x-data-timestamp',\n id: 'x-data-id',\n idempotencyKey: 'x-data-idempotency-key',\n deliveryId: 'x-data-delivery-id',\n // Executor credential pull: advertised on a proxied dispatch to a source declaring executor_secrets.\n credentialUrl: 'x-data-credential-url',\n credentialToken: 'x-data-credential-token',\n credentialNames: 'x-data-credential-names',\n} as const;\n\n/**\n * Max `scheme,hex` candidates we'll HMAC-check from one `X-Data-Signature` header. Data emits one\n * (two during a future key rotation); the cap stops a hostile header with thousands of entries from\n * forcing thousands of HMACs per request.\n */\nexport const MAX_SIG_ENTRIES = 4;\n\n/**\n * Verify a signature header value (`X-Data-Signature`) against the canonical of `parts`. The header\n * is a space-delimited list of `scheme,hex` entries; returns true if any `v1` entry matches (constant-\n * time compare). Malformed/empty/unknown-scheme entries are skipped, never throw. Candidates beyond\n * `MAX_SIG_ENTRIES` are ignored. Does NOT check timestamp skew (see {@link verify}).\n */\nexport async function verifySignatureHeader(secret: string, parts: SignedParts, header: string): Promise<boolean> {\n const canonical = buildCanonical(parts);\n let expected: string | undefined; // loop-invariant HMAC — computed at most once, lazily\n let checked = 0;\n let matched = false;\n for (const entry of header.split(' ')) {\n const comma = entry.indexOf(',');\n if (comma <= 0) continue; // no scheme/hex separator (e.g. \"v1\" or \"\")\n if (entry.slice(0, comma) !== SIGNING_SCHEME) continue;\n const hex = entry.slice(comma + 1);\n if (hex.length === 0) continue;\n if (++checked > MAX_SIG_ENTRIES) break;\n if (expected === undefined) expected = await signPayload(secret, canonical);\n if (timingSafeEqual(expected, hex)) matched = true;\n }\n return matched;\n}\n\nexport type VerifyResult =\n | { ok: true; id: string; timestamp: number; idempotencyKey?: string; deliveryId?: string; body: string }\n | { ok: false; reason: 'missing_signature' | 'invalid_signature' | 'stale_timestamp' | 'malformed' };\n\nexport interface VerifyInput {\n /** The HMAC signing secret shared with Data (the trigger/source signing secret). */\n secret: string;\n /** Request headers — a Fetch `Headers` or a plain object (case-insensitive lookup either way). */\n headers: Headers | Record<string, string | string[] | undefined>;\n /** HTTP method of the incoming request. */\n method: string;\n /** Request path the signature was computed over: `pathname + search` (see README path-matching). */\n path: string;\n /** RAW request body bytes as a string — NEVER a re-stringified parsed object. */\n rawBody: string;\n /** Allowed clock skew in seconds (both directions). A finite value is clamped to ≥1 (never 0);\n * unset / NaN / Infinity fall back to the 300s default (a bad value can't disable the window). */\n toleranceSec?: number;\n /** Override \"now\" in ms (tests). */\n now?: number;\n}\n\n/**\n * Verify an inbound Data request end to end: signature present + valid, and timestamp within the\n * skew window. Returns a discriminated result; never throws on bad input.\n */\nexport async function verify(input: VerifyInput): Promise<VerifyResult> {\n const get = headerGetter(input.headers);\n const signature = get(DATA_HEADERS.signature);\n if (!signature) return { ok: false, reason: 'missing_signature' };\n\n const tsRaw = get(DATA_HEADERS.timestamp);\n const id = get(DATA_HEADERS.id);\n if (!tsRaw || !id) return { ok: false, reason: 'malformed' };\n if (!/^\\d+$/.test(tsRaw)) return { ok: false, reason: 'malformed' };\n const timestamp = Number(tsRaw);\n if (!Number.isFinite(timestamp)) return { ok: false, reason: 'malformed' };\n\n // Non-finite (unset / NaN / Infinity, e.g. `Number(process.env.X)` with X missing) → the 300s\n // default, so a misconfigured tolerance can never silently disable the replay window. A finite value\n // is clamped to ≥1 (never 0). To accept a wider window, pass an explicit large finite number.\n const t = input.toleranceSec;\n const tolerance = Number.isFinite(t) ? Math.max(1, Math.floor(t as number)) : 300;\n const now = unixSeconds(input.now);\n if (Math.abs(now - timestamp) > tolerance) return { ok: false, reason: 'stale_timestamp' };\n\n const parts: SignedParts = { id, timestamp, method: input.method, path: input.path, body: input.rawBody };\n const valid = await verifySignatureHeader(input.secret, parts, signature);\n if (!valid) return { ok: false, reason: 'invalid_signature' };\n\n return {\n ok: true,\n id,\n timestamp,\n idempotencyKey: get(DATA_HEADERS.idempotencyKey),\n deliveryId: get(DATA_HEADERS.deliveryId),\n body: input.rawBody,\n };\n}\n","import type { ClaimResult, IdempotencyStore, StoredResult } from './types.js';\n\ninterface Entry {\n state: 'pending' | 'done';\n result?: StoredResult;\n expiresAt: number; // epoch ms\n}\n\n/**\n * In-memory idempotency store (per-process). Single-threaded JS gives the atomic first-wins claim for\n * free. An accessed key expires lazily; since every delivery has a unique key, completed entries are\n * otherwise dropped by a periodic full sweep (every {@link SWEEP_EVERY} writes) — so the map is bounded\n * by the deliveries within one TTL window (default 24h), not unbounded. For multiple instances, or to\n * cap memory tightly, implement {@link IdempotencyStore} over a shared backend instead.\n */\nconst DEFAULT_SWEEP_EVERY = 1000;\n\nexport class MemoryStore implements IdempotencyStore {\n private readonly map = new Map<string, Entry>();\n private readonly nowMs: () => number;\n private readonly sweepEvery: number;\n private writes = 0;\n\n constructor(opts?: { now?: () => number; sweepEvery?: number }) {\n this.nowMs = opts?.now ?? (() => Date.now());\n this.sweepEvery = opts?.sweepEvery ?? DEFAULT_SWEEP_EVERY;\n }\n\n /** Number of live entries currently held (after lazily ignoring nothing — counts raw map size). */\n get size(): number {\n return this.map.size;\n }\n\n private live(key: string): Entry | undefined {\n const e = this.map.get(key);\n if (!e) return undefined;\n if (e.expiresAt <= this.nowMs()) {\n this.map.delete(key);\n return undefined;\n }\n return e;\n }\n\n /** Amortized O(1)/write: every SWEEP_EVERY writes, drop all expired entries so the map can't grow forever. */\n private maybeSweep(): void {\n if (++this.writes < this.sweepEvery) return;\n this.writes = 0;\n const now = this.nowMs();\n for (const [k, e] of this.map) if (e.expiresAt <= now) this.map.delete(k);\n }\n\n async claim(key: string, ttlSec: number): Promise<ClaimResult> {\n const e = this.live(key);\n if (!e) {\n this.map.set(key, { state: 'pending', expiresAt: this.nowMs() + ttlSec * 1000 });\n this.maybeSweep();\n return { state: 'fresh' };\n }\n if (e.state === 'done' && e.result) return { state: 'done', result: e.result };\n return { state: 'in_progress' };\n }\n\n async complete(key: string, result: StoredResult, ttlSec: number): Promise<void> {\n this.map.set(key, { state: 'done', result, expiresAt: this.nowMs() + ttlSec * 1000 });\n this.maybeSweep();\n }\n\n async fail(key: string): Promise<void> {\n this.map.delete(key);\n }\n}\n","/**\n * The normalized error envelope an executor returns. The HTTP STATUS is the real contract a sender's\n * retry logic reads (2xx ack, 4xx permanent/no-retry, 5xx & 429 retry); the JSON body is for humans.\n */\nexport interface ErrorEnvelope {\n status: number;\n code: string;\n message: string;\n retriable: boolean;\n}\n\n/**\n * Throw this from a handler to fail with a specific code/retriability. `retriable` decides the status\n * family ({@link toHttpStatus}) so the sender retries (5xx/429) or drops (4xx) correctly.\n */\nexport class ExecutorError extends Error {\n readonly code: string;\n readonly retriable: boolean;\n readonly httpStatus?: number;\n\n constructor(args: { code: string; message: string; retriable: boolean; status?: number }) {\n super(args.message);\n this.name = 'ExecutorError';\n this.code = args.code;\n this.retriable = args.retriable;\n this.httpStatus = args.status;\n }\n\n toEnvelope(): ErrorEnvelope {\n return { status: toHttpStatus(this), code: this.code, message: this.message, retriable: this.retriable };\n }\n}\n\n/**\n * Map an error to an HTTP status. An explicit `status` is honored when it agrees with `retriable`\n * (5xx/429 for retriable, 4xx for permanent); otherwise the family default is used: retriable → 503,\n * permanent → 400.\n */\nexport function toHttpStatus(e: { retriable: boolean; httpStatus?: number }): number {\n const s = e.httpStatus;\n if (e.retriable) {\n return s !== undefined && (s === 429 || (s >= 500 && s <= 599)) ? s : 503;\n }\n return s !== undefined && s >= 400 && s <= 499 ? s : 400;\n}\n\n/** Serialize an envelope to a JSON body string. */\nexport function jsonResponseBody(env: ErrorEnvelope): string {\n return JSON.stringify(env);\n}\n","import { verify, DATA_HEADERS, type VerifyResult } from './core/verify.js';\nimport { headerGetter } from './core/headers.js';\nimport { MemoryStore } from './store/memory.js';\nimport type { IdempotencyStore, StoredResult } from './store/types.js';\nimport { ExecutorError, toHttpStatus, jsonResponseBody, type ErrorEnvelope } from './errors/envelope.js';\n\n/** Framework-neutral request the adapters hand to the executor. `rawBody` is the RAW bytes as a string. */\nexport interface NormalizedRequest {\n method: string;\n path: string;\n headers: Headers | Record<string, string | string[] | undefined>;\n rawBody: string;\n}\n\nexport interface NormalizedResponse {\n status: number;\n body: string;\n contentType: string;\n}\n\n/** Verified request context passed to your handler. Never contains the signing secret. */\nexport interface ExecutorContext {\n id: string;\n idempotencyKey?: string;\n deliveryId?: string;\n timestamp: number;\n body: string;\n}\n\n/** Your business logic. Return a `StoredResult` (cached for idempotent replays) or nothing (→ 204). */\nexport type ExecutorHandler = (ctx: ExecutorContext) => Promise<StoredResult | void> | StoredResult | void;\n\nexport interface CreateExecutorOptions {\n /** HMAC signing secret shared with Data. */\n secret: string;\n handler: ExecutorHandler;\n /** Idempotency store. Default: a per-process {@link MemoryStore}. */\n store?: IdempotencyStore;\n /** Clock-skew tolerance (seconds, both directions). Default 300; clamped to ≥1. */\n toleranceSec?: number;\n /** Skip signature/skew verification (LOCAL DEV ONLY). Dedup still applies if headers are present. */\n devBypass?: boolean;\n /** Idempotency record TTL (seconds). Default 86400 (24h). */\n idempotencyTtlSec?: number;\n /** Reports unexpected handler errors. MUST NOT log secrets/PII — it never receives the secret. */\n onError?: (err: unknown, ctx?: ExecutorContext) => void;\n /**\n * Reproduce the path Data SIGNED when a reverse proxy rewrites it. Must return the exact\n * `pathname+search` Data used (see README path-matching), not necessarily the incoming path.\n */\n pathOverride?: (req: NormalizedRequest) => string;\n}\n\nexport interface Executor {\n handle(req: NormalizedRequest): Promise<NormalizedResponse>;\n}\n\nconst JSON_CT = 'application/json';\n\nfunction errorResponse(env: ErrorEnvelope): NormalizedResponse {\n return { status: env.status, body: jsonResponseBody(env), contentType: JSON_CT };\n}\n\nfunction reasonResponse(reason: Exclude<VerifyResult, { ok: true }>['reason']): NormalizedResponse {\n // missing/invalid → 401 (unauthenticated); stale → 403; malformed → 400.\n const status = reason === 'malformed' ? 400 : reason === 'stale_timestamp' ? 403 : 401;\n return errorResponse({ status, code: reason.toUpperCase(), message: `signature ${reason}`, retriable: false });\n}\n\n/** Read the dedupe context from headers without verifying (dev-bypass path). */\nfunction bypassContext(req: NormalizedRequest): ExecutorContext | null {\n const get = headerGetter(req.headers);\n const id = get(DATA_HEADERS.id);\n const tsRaw = get(DATA_HEADERS.timestamp);\n return {\n id: id ?? '',\n idempotencyKey: get(DATA_HEADERS.idempotencyKey),\n deliveryId: get(DATA_HEADERS.deliveryId),\n timestamp: tsRaw && /^\\d+$/.test(tsRaw) ? Number(tsRaw) : 0,\n body: req.rawBody,\n };\n}\n\nexport function createExecutor(opts: CreateExecutorOptions): Executor {\n const store = opts.store ?? new MemoryStore();\n const ttl = opts.idempotencyTtlSec ?? 86_400;\n\n async function run(ctx: ExecutorContext): Promise<NormalizedResponse> {\n // Dedupe key: the logical idempotency key (proxy writes) or the stable message id (webhooks).\n // Absent both (e.g. dev-bypass with no signed headers) → run without dedupe.\n const key = ctx.idempotencyKey || ctx.id;\n if (!key) return await invoke(ctx);\n\n const claim = await store.claim(key, ttl);\n if (claim.state === 'done') {\n return { status: claim.result.status, body: claim.result.body, contentType: claim.result.contentType ?? JSON_CT };\n }\n if (claim.state === 'in_progress') {\n // A concurrent attempt holds this key — tell the sender to retry. Use a retriable (5xx) status,\n // not 409, so it matches `retriable: true` and the sender's retry convention.\n return errorResponse({ status: 503, code: 'IN_PROGRESS', message: 'duplicate delivery in progress', retriable: true });\n }\n try {\n const res = await invoke(ctx);\n await store.complete(key, { status: res.status, body: res.body, contentType: res.contentType }, ttl);\n return res;\n } catch (err) {\n const env = toEnvelope(err);\n const res = errorResponse(env);\n if (env.retriable) {\n await store.fail(key); // retriable: release the key so the retry re-runs (don't cache failures)\n } else {\n // permanent: cache the 4xx so a duplicate delivery replays it instead of re-running.\n await store.complete(key, { status: res.status, body: res.body, contentType: res.contentType }, ttl);\n }\n if (!(err instanceof ExecutorError)) opts.onError?.(err, ctx);\n return res;\n }\n }\n\n async function invoke(ctx: ExecutorContext): Promise<NormalizedResponse> {\n const result = await opts.handler(ctx);\n if (!result) return { status: 204, body: '', contentType: JSON_CT };\n return { status: result.status, body: result.body, contentType: result.contentType ?? JSON_CT };\n }\n\n return {\n async handle(req: NormalizedRequest): Promise<NormalizedResponse> {\n const path = opts.pathOverride ? opts.pathOverride(req) : req.path;\n\n if (opts.devBypass) {\n const ctx = bypassContext(req);\n return ctx ? await run(ctx) : reasonResponse('malformed');\n }\n\n const result = await verify({\n secret: opts.secret,\n headers: req.headers,\n method: req.method,\n path,\n rawBody: req.rawBody,\n toleranceSec: opts.toleranceSec,\n });\n if (!result.ok) return reasonResponse(result.reason);\n\n return await run({\n id: result.id,\n idempotencyKey: result.idempotencyKey,\n deliveryId: result.deliveryId,\n timestamp: result.timestamp,\n body: result.body,\n });\n },\n };\n}\n\nfunction toEnvelope(err: unknown): ErrorEnvelope {\n if (err instanceof ExecutorError) return err.toEnvelope();\n // Unknown error → retriable 500 so the sender retries; message is generic (no secret/PII).\n return { status: toHttpStatus({ retriable: true }), code: 'INTERNAL', message: 'handler error', retriable: true };\n}\n","import type { Executor } from '../executor.js';\n\n/**\n * Wrap an {@link Executor} as a Fetch-API handler `(Request) => Promise<Response>` — works on Hono,\n * Next.js route handlers, Cloudflare Workers, Deno, and Bun.\n *\n * Reads the RAW body via `req.text()` BEFORE any parsing (verifying a re-stringified parsed body would\n * fail). The signed `path` is `new URL(req.url).pathname + search`; if a reverse proxy rewrites the\n * path, pass `pathOverride` to `createExecutor`.\n */\nexport function toFetchHandler(executor: Executor): (req: Request) => Promise<Response> {\n return async (req: Request): Promise<Response> => {\n const rawBody = await req.text();\n const url = new URL(req.url);\n const res = await executor.handle({\n method: req.method,\n path: url.pathname + url.search,\n headers: req.headers,\n rawBody,\n });\n // 204/205/304 are null-body statuses — passing any body (even '') throws in spec-compliant\n // runtimes, so send `null` when the body is empty.\n return new Response(res.body || null, { status: res.status, headers: { 'content-type': res.contentType } });\n };\n}\n","import type { Executor } from '../executor.js';\n\n/**\n * Minimal structural shapes of Node's `http.IncomingMessage` / `http.ServerResponse` (and Express's\n * `req`/`res`). Declared structurally so the package imports no Node built-ins at runtime.\n */\nexport interface NodeReq extends AsyncIterable<unknown> {\n method?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n}\nexport interface NodeRes {\n writeHead(status: number, headers?: Record<string, string>): unknown;\n end(chunk?: string): unknown;\n}\n\n/**\n * Wrap an {@link Executor} as a Node/Express handler `(req, res) => Promise<void>`.\n *\n * IMPORTANT: this reads the RAW request stream itself, so it MUST run BEFORE any body parser\n * (`express.json()`, `body-parser`, …). If a parser has already consumed the stream, the raw bytes are\n * gone and verification fails. Mount this on your executor route before such middleware.\n *\n * The signed `path` comes from `req.url` (already `pathname+search`); use `pathOverride` for proxies.\n */\nexport function toNodeHandler(executor: Executor): (req: NodeReq, res: NodeRes) => Promise<void> {\n return async (req: NodeReq, res: NodeRes): Promise<void> => {\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer));\n }\n const rawBody = Buffer.concat(chunks).toString('utf8');\n\n const out = await executor.handle({\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: req.headers,\n rawBody,\n });\n res.writeHead(out.status, { 'content-type': out.contentType });\n res.end(out.body);\n };\n}\n","/**\n * Sign a request TO Data — the inverse of the executor's verify side. Use this when an executor POSTs\n * a result back to an inbound webhook on a base (the async write-back leg): the inbound webhook ingest\n * verifies a Signing v1 signature, and this produces it from the inbound webhook's shared secret.\n *\n * Returns the three headers to attach (`X-Data-Signature`, `X-Data-Timestamp`, `X-Data-Id`). The\n * canonical is byte-identical to the verify side (the cross-package conformance vector guards drift).\n */\n\nimport { buildCanonical, SIGNING_SCHEME, unixSeconds } from './canonical.js';\nimport { signPayload } from './hmac.js';\n\nexport interface SignRequestInput {\n /** HMAC shared secret for the target inbound webhook. */\n secret: string;\n /** HTTP method of the request being signed (e.g. 'POST'). */\n method: string;\n /** Full target URL — its `pathname + search` is bound into the signature. */\n url: string;\n /** Raw request body string (default `''`). MUST be the exact bytes sent. */\n body?: string;\n /** Message id → `X-Data-Id`, bound into the signature. Default: a generated UUID. */\n id?: string;\n /** Unix seconds → `X-Data-Timestamp`. Default: now. */\n timestamp?: number;\n}\n\nexport interface SignedHeaders {\n 'X-Data-Signature': string;\n 'X-Data-Timestamp': string;\n 'X-Data-Id': string;\n}\n\n/** Sign a request to Data and return the headers to attach. */\nexport async function signRequest(input: SignRequestInput): Promise<SignedHeaders> {\n const id = input.id ?? crypto.randomUUID();\n const timestamp = input.timestamp ?? unixSeconds();\n const body = input.body ?? '';\n const { pathname, search } = new URL(input.url);\n const hex = await signPayload(input.secret, buildCanonical({ id, timestamp, method: input.method, path: pathname + search, body }));\n return {\n 'X-Data-Signature': `${SIGNING_SCHEME},${hex}`,\n 'X-Data-Timestamp': String(timestamp),\n 'X-Data-Id': id,\n };\n}\n","/**\n * Executor credential pull — the client side of Data's scoped, single-use binary-credential delivery.\n *\n * When a source declares `executor_secrets`, every proxied dispatch to the executor carries\n * `X-Data-Credential-Url`/`-Token`/`-Names`. The executor calls `fetchExecutorCredential` with the\n * INBOUND request to pull a declared credential by name (e.g. an mTLS client cert it must present to a\n * downstream upstream). The grant is single-use per name and short-lived, so fetch each credential ONCE\n * per invocation and reuse the value — a re-pull of the same name fails (the grant is already claimed).\n *\n * The returned `value` is delivered verbatim (binary credentials are stored base64-encoded); decode it\n * per `contentType` (e.g. base64-decode an `application/x-pkcs12` bundle). This helper never decodes.\n *\n * SECURITY: call this ONLY from a handler whose inbound request the SDK has already verified (e.g. inside\n * `createExecutor`'s handler). The grant headers are NOT part of the request's HMAC signature, so anything\n * that can rewrite headers in front of your executor (a TLS-terminating proxy/gateway) could swap the\n * callback URL. Pass `allowedOrigins` (your Data origin, e.g. `['https://data.wayai.pro']`) to reject a\n * rewritten URL — recommended whenever your executor sits behind such an intermediary.\n */\n\nimport { headerGetter } from './headers.js';\nimport { DATA_HEADERS } from './verify.js';\nimport { ExecutorError } from '../errors/envelope.js';\n\n/** A pulled credential. `value` is delivered verbatim (base64 for binary); decode per `contentType`. */\nexport interface ExecutorCredential {\n name: string;\n contentType: string;\n value: string;\n}\n\nexport interface FetchCredentialOptions {\n /** Override the fetch implementation (defaults to the global `fetch`). */\n fetchImpl?: typeof fetch;\n /**\n * If set (and non-empty), the callback URL's origin must be one of these — rejects a URL rewritten in\n * transit (the grant headers are NOT covered by the request signature). Pass your Data origin(s),\n * e.g. `['https://data.wayai.pro']`. Recommended when the executor sits behind a TLS-terminating proxy.\n *\n * Fails closed on an exact origin match: if the Data origin changes and this list still names only\n * the old one, every pull throws `credential_untrusted_url` (never retried). List every origin your\n * deployment may send from, and add a new one BEFORE it starts arriving.\n */\n allowedOrigins?: string[];\n}\n\n/** Anything carrying the inbound request headers: a NormalizedRequest, a Fetch `Request`, or raw headers. */\ntype HeaderSource =\n | Headers\n | Record<string, string | string[] | undefined>\n | { headers: Headers | Record<string, string | string[] | undefined> };\n\nfunction resolveHeaders(source: HeaderSource): Headers | Record<string, string | string[] | undefined> {\n if (source instanceof Headers) return source;\n // A NormalizedRequest / Fetch Request carries headers under `.headers` (a Headers or a record); a bare\n // headers record does not (its `.headers`, if any, would be a string value, not an object) — so guard\n // on the shape rather than `'headers' in source`, which a record with a \"headers\" key would spoof.\n const nested = (source as { headers?: unknown }).headers;\n if (nested instanceof Headers) return nested;\n if (nested && typeof nested === 'object') return nested as Record<string, string | string[] | undefined>;\n return source as Record<string, string | string[] | undefined>;\n}\n\n/**\n * Pull a declared credential by name from Data, using the grant advertised on the inbound dispatch.\n * Throws `ExecutorError` if the request carries no grant, the name isn't in the grant, or the pull is\n * denied/fails. A 403 (grant invalid/expired/already-used) is never retried; a transient 5xx/network\n * error is retried once.\n */\nexport async function fetchExecutorCredential(\n source: HeaderSource,\n name: string,\n opts: FetchCredentialOptions = {},\n): Promise<ExecutorCredential> {\n const get = headerGetter(resolveHeaders(source));\n const url = get(DATA_HEADERS.credentialUrl);\n const token = get(DATA_HEADERS.credentialToken);\n const namesHeader = get(DATA_HEADERS.credentialNames);\n if (!url || !token || namesHeader === undefined) {\n throw new ExecutorError({ code: 'no_credential_grant', message: 'Request carries no executor credential grant', retriable: false });\n }\n // The callback URL is transport-trusted, not signature-bound — pin it when an allowlist is supplied so a\n // rewritten URL can't redirect the single-use grant token to an attacker host.\n if (opts.allowedOrigins && opts.allowedOrigins.length > 0) {\n let origin: string;\n try { origin = new URL(url).origin; } catch { throw new ExecutorError({ code: 'credential_bad_url', message: 'Credential callback URL is not a valid URL', retriable: false }); }\n // Normalize each allowlist entry through URL.origin too, so a caller's trailing slash / explicit\n // default port (e.g. 'https://data.wayai.pro/' or ':443') still matches. A malformed entry keeps its\n // raw form and simply never matches (it can't grant), rather than breaking the whole check.\n const allowed = opts.allowedOrigins.map((o) => { try { return new URL(o).origin; } catch { return o; } });\n if (!allowed.includes(origin)) {\n throw new ExecutorError({ code: 'credential_untrusted_url', message: `Credential callback URL origin '${origin}' is not in allowedOrigins`, retriable: false });\n }\n }\n const names = namesHeader.split(',').map((n) => n.trim()).filter(Boolean);\n if (!names.includes(name)) {\n throw new ExecutorError({ code: 'undeclared_credential', message: `Credential '${name}' is not in the grant (declared: ${names.join(', ') || 'none'})`, retriable: false });\n }\n\n const doFetch = opts.fetchImpl ?? fetch;\n const body = JSON.stringify({ token, name });\n\n // Up to 2 attempts: retry once on a transient 5xx/network error, never on a 403 (single-use — a retry\n // would just hit `already_redeemed`). A 5xx that landed AFTER the grant was claimed surfaces as a clean\n // 403 on retry, which is the correct terminal outcome (the credential is consumed; Data mints a fresh\n // grant on the next dispatch).\n for (let attempt = 0; attempt < 2; attempt++) {\n let res: Response;\n try {\n // `redirect: 'manual'` so a 3xx from the (pinned) URL can't re-POST the token-bearing body to\n // another host — a redirect surfaces as a non-ok status → credential_fetch_failed below.\n res = await doFetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body, redirect: 'manual' });\n } catch {\n if (attempt === 0) continue; // network error → retry once\n throw new ExecutorError({ code: 'credential_unreachable', message: 'Credential pull is unreachable', retriable: true });\n }\n\n if (res.status === 403) {\n throw new ExecutorError({ code: 'credential_denied', message: `Credential '${name}' pull was denied (grant invalid, expired, or already used)`, retriable: false, status: 403 });\n }\n if (res.status >= 500) {\n if (attempt === 0) continue; // transient → retry once\n throw new ExecutorError({ code: 'credential_unavailable', message: `Credential pull failed (status ${res.status})`, retriable: true, status: res.status });\n }\n if (!res.ok) {\n throw new ExecutorError({ code: 'credential_fetch_failed', message: `Credential pull failed (status ${res.status})`, retriable: false, status: res.status });\n }\n\n const parsed = (await res.json().catch(() => null)) as { data?: { name?: string; content_type?: string; value?: string } } | null;\n const data = parsed?.data;\n if (!data || typeof data.value !== 'string') {\n throw new ExecutorError({ code: 'credential_malformed', message: 'Credential pull returned an unexpected body', retriable: false });\n }\n return { name: data.name ?? name, contentType: data.content_type ?? 'application/octet-stream', value: data.value };\n }\n\n // Unreachable: the loop returns or throws on every path.\n throw new ExecutorError({ code: 'credential_unavailable', message: 'Credential pull failed', retriable: true });\n}\n"]}
package/package.json CHANGED
@@ -1,18 +1,31 @@
1
1
  {
2
2
  "name": "@wayai/executor-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Receive and verify WayAI Data's HMAC-signed, idempotency-keyed webhooks and proxied calls — the executor side of the WayAI Data executor contract.",
5
- "keywords": ["wayai", "data", "webhook", "hmac", "signature", "idempotency", "executor", "verify"],
5
+ "keywords": [
6
+ "wayai",
7
+ "data",
8
+ "webhook",
9
+ "hmac",
10
+ "signature",
11
+ "idempotency",
12
+ "executor",
13
+ "verify"
14
+ ],
6
15
  "license": "MIT",
7
- "publishConfig": { "access": "public" },
8
- "homepage": "https://github.com/wayai-pro/rekor/tree/main/sdk#readme",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "homepage": "https://wayai.pro/docs/data/executors",
9
20
  "repository": {
10
21
  "type": "git",
11
- "url": "git+https://github.com/wayai-pro/rekor.git",
12
- "directory": "sdk"
22
+ "url": "git+https://github.com/wayai-pro/wayai.git",
23
+ "directory": "apps/rekor/sdk"
13
24
  },
14
25
  "type": "module",
15
- "engines": { "node": ">=24.0.0" },
26
+ "engines": {
27
+ "node": ">=24.0.0"
28
+ },
16
29
  "sideEffects": false,
17
30
  "main": "./dist/index.cjs",
18
31
  "module": "./dist/index.js",
@@ -28,17 +41,19 @@
28
41
  "default": "./dist/index.js"
29
42
  }
30
43
  },
31
- "files": ["dist"],
32
- "scripts": {
33
- "build": "tsup",
34
- "typecheck": "tsc --noEmit",
35
- "test": "vitest run",
36
- "dev": "tsup --watch"
37
- },
44
+ "files": [
45
+ "dist"
46
+ ],
38
47
  "devDependencies": {
39
48
  "@types/node": "^26.2.0",
40
49
  "tsup": "^8.4.0",
41
50
  "typescript": "^5.8.2",
42
51
  "vitest": "^4.1.10"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "typecheck": "tsc --noEmit",
56
+ "test": "vitest run",
57
+ "dev": "tsup --watch"
43
58
  }
44
- }
59
+ }