@pithy-sh/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -153,6 +153,16 @@ export interface SecretRegistryEntrySeam {
153
153
  * which it can do without executing the package.
154
154
  */
155
155
  readonly devValue?: string;
156
+ /**
157
+ * The Worker binding this value exists to reach, or absent when it reaches none — the constant half
158
+ * of *can this composition use this secret* (#541).
159
+ *
160
+ * Read here and never interpreted, exactly like {@link devValue}: the CLI joins it against a Worker's
161
+ * `declinedBindings` so a credential whose bucket the project declined stops being listed as
162
+ * outstanding work. `@pithy-sh/secrets` owns the concrete field; core needs it on the seam so a
163
+ * reporting surface can read a capability's declared slice off this contract.
164
+ */
165
+ readonly binding?: string;
156
166
  }
157
167
  /** A capability's secret-registry slice: secret name → {@link SecretRegistryEntrySeam}. */
158
168
  export type SecretRegistrySeam = Record<string, SecretRegistryEntrySeam>;
@@ -183,6 +193,34 @@ export type SecretRegistrySeam = Record<string, SecretRegistryEntrySeam>;
183
193
  * secret whose schema has no branches at all needs no declaration — every field is asked.
184
194
  */
185
195
  export type SecretBranchSeam = Record<string, readonly string[]>;
196
+ /**
197
+ * **Which of this capability's secrets this Worker's configuration cannot reach** — secret name → the
198
+ * reason, in an operator's words.
199
+ *
200
+ * The neighbor of {@link SecretBranchSeam}, one level up. That one says which *blocks* of a bundle
201
+ * apply when the bundle itself does; this says that a whole secret does not. `@pithy-sh/auth` declares
202
+ * one credential per OAuth provider, so a project running Google and GitHub has two secrets that no
203
+ * code path will ever read — and until #541 `pithy doctor` listed both as outstanding work under
204
+ * *"fine to leave until you need it"*, which is not true of a credential the configuration has refused.
205
+ *
206
+ * **Declared, never inferred**, for {@link SecretBranchSeam}'s reason: `auth-apple-credentials` and
207
+ * `providers.apple` are spelled alike today and nothing makes a secret name and a config key the same
208
+ * thing. The capability holds both halves at construction; a CLI matching them by substring would be
209
+ * right by luck.
210
+ *
211
+ * **Per composition, which is why it is here and not on the registry entry.** A registry entry is plain
212
+ * data shared by every Worker that declares the name; whether a provider is enabled is one Worker's
213
+ * configuration. The constant half of the same question — *which binding does this credential exist to
214
+ * reach* — is on the entry, as `binding`, because it does not change between compositions.
215
+ *
216
+ * **The reason reaches a terminal, so it is a sentence and not a code.** Name the configuration that
217
+ * decided it, the way an operator would have to edit it: `providers.apple is not enabled in
218
+ * pithy.config.ts`. It answers *what would I have to do to turn this on* as well as *why is this here*.
219
+ *
220
+ * Declaring nothing is the ordinary case and means every declared secret applies. It is descriptive
221
+ * only: nothing in the runtime reads it, and no command acts on it beyond reporting.
222
+ */
223
+ export type InapplicableSecretSeam = Record<string, string>;
186
224
  /**
187
225
  * The structural seam for one capability's token-profile slice — profile name → the scoped CF API
188
226
  * token that capability's code needs. Declared next to the code that uses it (alongside
@@ -313,6 +351,12 @@ export interface Capability<Databases extends DatabaseSpecMap = DatabaseSpecMap,
313
351
  * no `json` secret at all, declares nothing.
314
352
  */
315
353
  secretBranches?: SecretBranchSeam;
354
+ /**
355
+ * Which of this capability's secrets this Worker's configuration cannot reach, and why — see
356
+ * {@link InapplicableSecretSeam}. Additive and optional: a capability whose secrets all apply
357
+ * whenever it is composed declares nothing, which is the ordinary case.
358
+ */
359
+ inapplicableSecrets?: InapplicableSecretSeam;
316
360
  /**
317
361
  * The scoped CF API tokens this capability's code needs, as a token-profile slice (profile name →
318
362
  * {@link TokenProfileSeam}). Additive and optional, and declared next to {@link Capability.secretRegistry}
@@ -1 +1 @@
1
- {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../../src/capability/capability.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../../src/capability/capability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ import type { KitErrorCode } from "./payload";
4
+ /**
5
+ * The `params` a throw site **guarantees** for a kit error code — and therefore the only names a
6
+ * locale may write as a `{placeholder}` in that code's sentence.
7
+ *
8
+ * **Why a declaration exists at all.** `params` crosses the wire with `message` so a translating
9
+ * client can render its own wording for a code (CLAUDE.md §Errors), and `interpolate` leaves a
10
+ * placeholder nobody supplied written out as `{apiAnswer}`. So a locale writing a placeholder is
11
+ * making a claim about a *call site*, and a call is not a declaration — there was nothing in this
12
+ * repository for a catalog test to read, which is why `@pithy-sh/i18n` could only pin the absence of
13
+ * placeholders. This is that missing half: one table, in `core`, that both sides import.
14
+ *
15
+ * **Guaranteed means every throw, not most.** A name belongs here only when the code's throw sites
16
+ * pass it on **every** path, including the degraded one — a Cloudflare call that never reached
17
+ * Cloudflare still supplies `apiAnswer`, as the empty string. A param a throw site passes only
18
+ * sometimes is still useful to a client reading `payload.params` directly, and is still stripped from
19
+ * nothing; it simply may not be written into a sentence, because the sentence has to render on the
20
+ * path where it is missing.
21
+ *
22
+ * A code absent from this table guarantees nothing, which is the honest default and the state of all
23
+ * but one of them.
24
+ */
25
+ export declare const GUARANTEED_ERROR_PARAMS: Partial<Record<KitErrorCode, readonly string[]>>;
26
+ /** The names a locale may interpolate into `code`'s sentence. Empty when the code guarantees none. */
27
+ export declare function guaranteedErrorParams(code: string): readonly string[];
28
+ //# sourceMappingURL=messageParams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messageParams.d.ts","sourceRoot":"","sources":["../../src/error/messageParams.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,uBAAuB,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC,CAMpF,CAAC;AAEF,sGAAsG;AACtG,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAErE"}
@@ -0,0 +1,31 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+ //#region src/error/messageParams.ts
4
+ /**
5
+ * The `params` a throw site **guarantees** for a kit error code — and therefore the only names a
6
+ * locale may write as a `{placeholder}` in that code's sentence.
7
+ *
8
+ * **Why a declaration exists at all.** `params` crosses the wire with `message` so a translating
9
+ * client can render its own wording for a code (CLAUDE.md §Errors), and `interpolate` leaves a
10
+ * placeholder nobody supplied written out as `{apiAnswer}`. So a locale writing a placeholder is
11
+ * making a claim about a *call site*, and a call is not a declaration — there was nothing in this
12
+ * repository for a catalog test to read, which is why `@pithy-sh/i18n` could only pin the absence of
13
+ * placeholders. This is that missing half: one table, in `core`, that both sides import.
14
+ *
15
+ * **Guaranteed means every throw, not most.** A name belongs here only when the code's throw sites
16
+ * pass it on **every** path, including the degraded one — a Cloudflare call that never reached
17
+ * Cloudflare still supplies `apiAnswer`, as the empty string. A param a throw site passes only
18
+ * sometimes is still useful to a client reading `payload.params` directly, and is still stripped from
19
+ * nothing; it simply may not be written into a sentence, because the sentence has to render on the
20
+ * path where it is missing.
21
+ *
22
+ * A code absent from this table guarantees nothing, which is the honest default and the state of all
23
+ * but one of them.
24
+ */
25
+ const GUARANTEED_ERROR_PARAMS = { "cloudflare/request_failed": ["apiAnswer"] };
26
+ /** The names a locale may interpolate into `code`'s sentence. Empty when the code guarantees none. */
27
+ function guaranteedErrorParams(code) {
28
+ return GUARANTEED_ERROR_PARAMS[code] ?? [];
29
+ }
30
+ //#endregion
31
+ export { GUARANTEED_ERROR_PARAMS, guaranteedErrorParams };
@@ -3,11 +3,22 @@
3
3
  import type { ErrorPayload } from "./payload";
4
4
  import { PublicErrorPayload } from "./payload";
5
5
  /**
6
- * Render an error for the terminal: the public `message` becomes the problem line and the
7
- * optional `action` the action line — the two-line brand-voice shape `docs/CLI.md` §3.3 specifies.
8
- * This unifies the CLI's error output into the one `PithyError` family: the CLI catches a
9
- * `PithyError`, colorizes the first line via its `style.ts`, and prints this. `detail` is internal
10
- * and never rendered. The same payload that encodes to HTTP renders here — one error, two surfaces.
6
+ * Render an error for the terminal: the public `message` on top and the optional `action` last — the
7
+ * brand-voice shape `docs/CLI.md` §3.3 specifies. This unifies the CLI's error output into the one
8
+ * `PithyError` family: the CLI catches a `PithyError`, colorizes via its `style.ts`, and prints this.
9
+ * `detail` is internal and never rendered. The same payload that encodes to HTTP renders here — one
10
+ * error, two surfaces.
11
+ *
12
+ * **`message` is one line, and the newline this adds is the only one in the rendering.** #534 tried
13
+ * the other way — a problem line with an upstream's `errors[]` indented beneath it, which reads
14
+ * beautifully in a terminal and is unencodable everywhere else. The newline is a *field separator* in
15
+ * two places that matter more than the indent does. `../workflow/stepMessage` uses it to carry a
16
+ * terminal step's remedy across a durable boundary, so a multi-line `message` raised inside a Workflow
17
+ * step was declined whole and the operator got the platform's prose about durable execution instead of
18
+ * the refusal. And here, the action line is the one line after the message, so a `message` carrying a
19
+ * break puts upstream text where the remedy goes — a forged action line, from text a Worker we did not
20
+ * write can choose. Upstream words are marked with a word (`Cloudflare said:`), never with a line: a
21
+ * word survives a step record, a `--json` line and a browser.
11
22
  */
12
23
  export declare function renderTerminal(payload: ErrorPayload): string;
13
24
  /** An error as an operator's machine surface states it: the wire fields, plus the remedy. */
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/error/terminal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAE5D;AAED,6FAA6F;AAC7F,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,aAAa,CAGlE"}
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/error/terminal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAE5D;AAED,6FAA6F;AAC7F,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,aAAa,CAGlE"}
@@ -3,11 +3,22 @@
3
3
  import { PublicErrorPayload } from "./payload.js";
4
4
  //#region src/error/terminal.ts
5
5
  /**
6
- * Render an error for the terminal: the public `message` becomes the problem line and the
7
- * optional `action` the action line — the two-line brand-voice shape `docs/CLI.md` §3.3 specifies.
8
- * This unifies the CLI's error output into the one `PithyError` family: the CLI catches a
9
- * `PithyError`, colorizes the first line via its `style.ts`, and prints this. `detail` is internal
10
- * and never rendered. The same payload that encodes to HTTP renders here — one error, two surfaces.
6
+ * Render an error for the terminal: the public `message` on top and the optional `action` last — the
7
+ * brand-voice shape `docs/CLI.md` §3.3 specifies. This unifies the CLI's error output into the one
8
+ * `PithyError` family: the CLI catches a `PithyError`, colorizes via its `style.ts`, and prints this.
9
+ * `detail` is internal and never rendered. The same payload that encodes to HTTP renders here — one
10
+ * error, two surfaces.
11
+ *
12
+ * **`message` is one line, and the newline this adds is the only one in the rendering.** #534 tried
13
+ * the other way — a problem line with an upstream's `errors[]` indented beneath it, which reads
14
+ * beautifully in a terminal and is unencodable everywhere else. The newline is a *field separator* in
15
+ * two places that matter more than the indent does. `../workflow/stepMessage` uses it to carry a
16
+ * terminal step's remedy across a durable boundary, so a multi-line `message` raised inside a Workflow
17
+ * step was declined whole and the operator got the platform's prose about durable execution instead of
18
+ * the refusal. And here, the action line is the one line after the message, so a `message` carrying a
19
+ * break puts upstream text where the remedy goes — a forged action line, from text a Worker we did not
20
+ * write can choose. Upstream words are marked with a word (`Cloudflare said:`), never with a line: a
21
+ * word survives a step record, a `--json` line and a browser.
11
22
  */
12
23
  function renderTerminal(payload) {
13
24
  return payload.action ? `${payload.message}\n${payload.action}` : payload.message;
@@ -46,17 +46,20 @@ import type { PithyHonoEnv } from "../capability/capability";
46
46
  * ## What a refusal says
47
47
  *
48
48
  * `core/webhook_unverified`, 401, one code for every step a sender can fail — unreadable token, unknown key,
49
- * bad signature, wrong audience, expired, predicate false. The step goes in `detail`, which the HTTP codec
49
+ * a key that cannot verify an `RS256` signature, bad signature, wrong audience, expired, predicate false. A
50
+ * `kid` selects the key, and a `kid` is a value the sender chose, so *every* answer the key leads to is the
51
+ * sender's. The step goes in `detail`, which the HTTP codec
50
52
  * strips, so an operator reading a log learns which check refused it and the sender learns only that
51
53
  * something did. Values that decide something are named there (`kid`, `iss`, `aud`) because a misconfigured
52
54
  * audience is the realistic failure and an operator cannot fix what the log will not name — truncated by
53
55
  * {@link snippet}, and never the signature and never the whole token.
54
56
  *
55
- * Two failures are deliberately **not** the sender's. An endpoint with no issuers, no audience, no key
56
- * endpoint, or a clock that is not a clock is a configuration fault and takes `core/internal` — reporting it
57
- * as an unverified webhook sends an operator hunting a forger who is not there. And a key endpoint that
58
- * cannot be reached or answers nonsense is `core/upstream_failed` (502), because a 500 tells an operator to
59
- * read *our* logs and the thing that broke is the issuer's. Both still refuse the delivery: fail closed is
57
+ * Two failures are deliberately **not** the sender's, and the test for both is whether the sender could have
58
+ * chosen them. An endpoint with no issuers, no audience, no key endpoint, or a clock that is not a clock is a
59
+ * configuration fault and takes `core/internal` — reporting it as an unverified webhook sends an operator
60
+ * hunting a forger who is not there. And a key endpoint that cannot be reached or answers nonsense is
61
+ * `core/upstream_failed` (502), because a 500 tells an operator to read *our* logs and the thing that broke
62
+ * is the issuer's; no `kid` decides whether that happens. Both still refuse the delivery: fail closed is
60
63
  * about what happens to the request, not about whose code goes on it.
61
64
  *
62
65
  * ## What this proves, and what it does not
@@ -65,8 +68,29 @@ import type { PithyHonoEnv } from "../capability/capability";
65
68
  * never that it is *new*. A captured token replays until it expires. A handler that grants, charges, or
66
69
  * deletes needs its own uniqueness key.
67
70
  */
68
- /** How long a fetched key set is reused. Issuers rotate slowly, and an unknown `kid` forces a refresh anyway. */
71
+ /** How long a fetched key set is reused. Issuers rotate slowly, so an hour of it is one round trip an hour. */
69
72
  export declare const OIDC_JWKS_TTL_SECONDS = 3600;
73
+ /**
74
+ * **The most often the issuer's key endpoint is asked, whatever a token's `kid` claims.**
75
+ *
76
+ * The TTL above bounds the *hit* path and bounds nothing else. A `kid` the held set does not name is a miss,
77
+ * a miss is a reason to refresh — that is what makes rotation invisible — and `kid` arrives in a header
78
+ * nobody has verified yet. So the miss path was the whole amplifier: `Bearer <jwt with kid: random-uuid>` is
79
+ * exactly as cheap to mint as one naming a published key, and each one bought a round trip to the issuer,
80
+ * 1:1, from an anonymous caller. The cache answered only the forger who bothered to name a real `kid`.
81
+ *
82
+ * A window closes it, and it closes it for every `kid` at once because it is not about the `kid`: at most one
83
+ * ask per window per key endpoint, and a miss inside the window is refused on what we already hold rather
84
+ * than on a fresh look. A random `kid` therefore costs a lookup in memory, and the outbound rate is a
85
+ * constant an attacker cannot move.
86
+ *
87
+ * **A minute, and the number is a rotation delay.** It is how long a genuinely new signing key can be refused
88
+ * before we look again — an issuer publishes the key before the first token names it, and every one of them
89
+ * overlaps rotations by hours, so a minute is inside the overlap and a refusal inside it is a redelivery
90
+ * rather than a loss. Wider trades rotation latency for nothing much: one ask a minute is already too little
91
+ * traffic for any issuer to rate-limit.
92
+ */
93
+ export declare const OIDC_JWKS_MIN_REFRESH_SECONDS = 60;
70
94
  /** Tolerance on `exp`, `nbf` and `iat`. A minute covers clock drift; an hour would cover a replay. */
71
95
  export declare const OIDC_CLOCK_SKEW_SECONDS = 60;
72
96
  /**
@@ -151,20 +175,43 @@ export declare const oidcJwksFetch: OidcJwksFetch;
151
175
  * over KV or the Cache API and shares it across isolates.
152
176
  *
153
177
  * **Owning the store is not the same as having none.** {@link requireOidcWebhook} builds one per guard when a
154
- * route gives none, because the guard is the surface anonymous traffic arrives on: with no cache, a 49-byte
155
- * forged token naming a published `kid` buys a round trip to the issuer, 1:1, until the issuer rate-limits us
156
- * and the real deliveries 502 alongside the forgeries. {@link verifyOidcToken} called directly is the other
157
- * case and does fetch per call — its caller holds one token it has already decided to check, so there is no
158
- * flood to absorb and no cache to acquire by surprise.
178
+ * route gives none, because the guard is the surface anonymous traffic arrives on: with no store, a 49-byte
179
+ * forged token buys a round trip to the issuer, 1:1, until the issuer rate-limits us and the real deliveries
180
+ * 502 alongside the forgeries. {@link verifyOidcToken} called directly is the other case and does fetch per
181
+ * call — its caller holds one token it has already decided to check, so there is no flood to absorb and no
182
+ * cache to acquire by surprise.
183
+ *
184
+ * **Two facts, not one, and the second is the one that bounds an attacker.** `get`/`set` hold what the issuer
185
+ * published, which answers a `kid` we have seen. {@link claimRefresh} holds *when we last asked*, which is
186
+ * what answers a `kid` we have not — and that is the whole of the miss path, where an unverified header used
187
+ * to buy an outbound request per delivery. Both live in the store rather than in a closure so that a store
188
+ * shared across isolates (KV, the Cache API) shares the bound too: one ask per window for the deployment,
189
+ * not one per isolate.
159
190
  *
160
191
  * Public keys, so nothing here is what CLAUDE.md's secrets rule governs: the whole point of a JWKS endpoint
161
- * is that anyone may read it. What the cache buys is one round trip per TTL instead of one per delivery.
192
+ * is that anyone may read it. What the store buys is one round trip per TTL on the hit path and one per
193
+ * {@link OIDC_JWKS_MIN_REFRESH_SECONDS} on the miss path, instead of one per delivery on both.
162
194
  */
163
195
  export interface JwksCache {
164
196
  /** The key set held for `url`, or `undefined` when nothing usable is held. */
165
197
  get(url: string): Promise<readonly OidcJwk[] | undefined>;
166
198
  /** Hold a freshly fetched key set for `url` for `ttlSeconds`. */
167
199
  set(url: string, keys: readonly OidcJwk[], ttlSeconds: number): Promise<void>;
200
+ /**
201
+ * **May the issuer's endpoint be asked for `url` right now?** `true` grants the ask and records it for
202
+ * `seconds`; `false` says somebody already asked inside that window.
203
+ *
204
+ * One call rather than a read and a write, because the question and the claim are the same act: two
205
+ * deliveries that both read *nobody has asked* and then both ask are the flood this exists to stop. An
206
+ * implementation decides and records before it yields — {@link memoryJwksCache} does it synchronously
207
+ * inside the promise, so concurrent deliveries in one isolate produce exactly one ask. A store over a
208
+ * network is best-effort about that, and best-effort is still a bound.
209
+ *
210
+ * It is about the *ask*, never about the answer, which is what keeps a failing endpoint from being a
211
+ * second amplifier: a refresh that ends in `core/upstream_failed` has still spent the window, so a flood
212
+ * arriving while the issuer is down costs one attempt per window rather than one per delivery.
213
+ */
214
+ claimRefresh(url: string, seconds: number): Promise<boolean>;
168
215
  }
169
216
  /**
170
217
  * A key cache in memory, expiring on a clock the caller owns.
@@ -205,6 +252,10 @@ export interface OidcWebhookScheme {
205
252
  /**
206
253
  * How long a fetched key set is held. Defaults to {@link OIDC_JWKS_TTL_SECONDS} and must be a finite number
207
254
  * of seconds — a `NaN` is an entry that never expires, which pins a key the issuer has rotated out.
255
+ *
256
+ * Keep it above {@link OIDC_JWKS_MIN_REFRESH_SECONDS}. Below it, a set expires while the window that
257
+ * governs re-asking is still spent, and every delivery in the gap is answered `core/upstream_failed`
258
+ * because nothing is held and nothing may be asked. An hour against a minute is not close to that line.
208
259
  */
209
260
  jwksTtlSeconds?: number;
210
261
  /**
@@ -236,9 +287,11 @@ export interface OidcWebhookGuardOptions extends OidcWebhookScheme {
236
287
  * it has not checked yet.
237
288
  *
238
289
  * @throws {@link WebhookUnverifiedError} (401) when the sender did not prove itself — unreadable token,
239
- * unknown key, bad signature, wrong issuer or audience, expired, not yet valid, predicate false.
290
+ * unknown key, a key that cannot verify an `RS256` signature, bad signature, wrong issuer or audience,
291
+ * expired, not yet valid, predicate false.
240
292
  * @throws {@link InternalError} (500) when this endpoint is not configured to verify anything.
241
- * @throws {@link UpstreamError} (502) when the issuer's key endpoint could not be read.
293
+ * @throws {@link UpstreamError} (502) when the issuer's key endpoint could not be read — either because the
294
+ * ask failed, or because it was asked inside {@link OIDC_JWKS_MIN_REFRESH_SECONDS} and nothing is held.
242
295
  */
243
296
  export declare function verifyOidcToken(token: string, options: VerifyOidcTokenOptions): Promise<OidcClaims>;
244
297
  /**
@@ -252,21 +305,24 @@ export declare function verifyOidcToken(token: string, options: VerifyOidcTokenO
252
305
  * be unauthenticated work an anonymous POST could buy on the one route whose purpose is to refuse anonymous
253
306
  * callers.
254
307
  *
255
- * ## Why a cache is not optional here
308
+ * ## Why a store is not optional here
256
309
  *
257
310
  * A caller that presents *something* is past that first check, and the next thing the verifier needs is the
258
311
  * issuer's key set. So the guard builds a {@link memoryJwksCache} when the route gives none — once, when the
259
- * middleware is constructed, never per request, which is a cache that has never held anything. A `kid` is
260
- * published by definition, which makes a 49-byte token naming one the cheapest thing on the internet to send,
261
- * and with no cache each one is a round trip to the issuer, 1:1. That is a bandwidth multiplier aimed at
262
- * somebody else's endpoint, and once the issuer rate-limits us {@link fetchJwks} maps it to
263
- * `core/upstream_failed` for the genuine deliveries too — an anonymous caller taking the boundary down by
264
- * asking it politely. A route wanting a harder bound (shared across isolates, or rate-limiting) passes its
265
- * own {@link OidcWebhookScheme.jwksCache}, and this one is never built.
266
- *
267
- * A `kid` **nobody** publishes still costs one fetch per delivery, cache or no cache, because an unknown key
268
- * id is how rotation announces itself and declining to look is an outage. That bound is per delivery and
269
- * lives in {@link resolveKey}; the cache is what collapses the other flood, the one naming a key that exists.
312
+ * middleware is constructed, never per request, which is a store that has never held anything. A 49-byte
313
+ * token is the cheapest thing on the internet to send, and with no store each one is a round trip to the
314
+ * issuer, 1:1. That is a bandwidth multiplier aimed at somebody else's endpoint, and once the issuer
315
+ * rate-limits us {@link fetchJwks} maps it to `core/upstream_failed` for the genuine deliveries too — an
316
+ * anonymous caller taking the boundary down by asking it politely.
317
+ *
318
+ * **Both halves of the flood, not one.** A token naming a `kid` the issuer really publishes is answered out
319
+ * of what the store holds. A token naming a `kid` nobody has ever published is answered by
320
+ * {@link JwksCache.claimRefresh}: one ask per {@link OIDC_JWKS_MIN_REFRESH_SECONDS} per key endpoint, for
321
+ * every `kid` at once, so a `kid` an attacker invents costs a map lookup and no outbound request. The first
322
+ * round of this fix had only the first half, which meant the cache helped exactly against a forger who
323
+ * bothered to copy a real `kid` and not at all against one who did not. A route wanting the bound shared
324
+ * across isolates rather than held per isolate passes its own {@link OidcWebhookScheme.jwksCache} — over KV
325
+ * or the Cache API — and this one is never built.
270
326
  *
271
327
  * ## Why nothing is published to the request
272
328
  *
@@ -1 +1 @@
1
- {"version":3,"file":"oidcWebhook.d.ts","sourceRoot":"","sources":["../../src/http/oidcWebhook.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"oidcWebhook.d.ts","sourceRoot":"","sources":["../../src/http/oidcWebhook.ts"],"names":[],"mappings":"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"}