[rosary-54ad76] feat(vault): wire kek-source + per-caller rate bucket into Worker#22
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR wires previously-introduced vault hardening modules into the actual Worker + Durable Object runtime: KEK resolution is now driven by VAULT_KEK_SOURCE via the kek-source dispatcher, and every authenticated request is gated by a per-caller token bucket enforced inside the CredentialVault DO.
Changes:
- Add
VAULT_KEK_SOURCE-driven KEK resolution in the DO, with a legacyVAULT_KEK_SECRETfallback + one-time deprecation warning. - Add a DO RPC
consumeBudget(sub, costClass)and call it from the Worker before delegating to the handler, returning429+Retry-Afterwhen over budget. - Add DO-side wiring tests and update docs/config examples for KEK sources and rate budget behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
vault/src/worker.ts |
Adds KEK source resolution + legacy fallback in the DO, introduces per-caller rate bucket RPC, and charges budget in the Worker before handling requests. |
vault/src/__tests__/worker-do.test.ts |
New tests covering KEK source wiring (env/file/legacy) and per-caller bucket behavior (saturation/isolation/cost scaling). |
vault/wrangler.toml.example |
Documents VAULT_KEK_SOURCE configuration and legacy VAULT_KEK_SECRET deprecation. |
vault/README.md |
Documents KEK source schemes and the per-caller rate budget semantics/limits. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
jamestexas
added a commit
that referenced
this pull request
May 18, 2026
…y::collapsible_match)
The outer 'contains("..")' check was strictly redundant — the inner
's == ".."' check (the only one that returned) implies it. Collapse
the pair into a match guard. No behavior change; UDS paths containing
'..' as a substring (e.g. 'foo..bar.sock') are still permitted, only
exact '..' components reject.
CI for the whole repo couldn't pass while this lint was hot under
-D warnings. Unblocks PRs #20/#21/#22 once #23 merges and they rebase.
jamestexas
added a commit
that referenced
this pull request
May 18, 2026
* chore(deps): comprehensive workspace dep update sweep Workspace-wide dependency update sweep across root + action + worker. Clears all 6 pnpm audit advisories (was 2 high / 4 moderate). | Workspace | Package | From | To | |-----------|----------------------------------|--------------|--------------| | root | @vitest/coverage-v8 | ^4.1.2 | ^4.1.6 | | root | tsx | ^4.21.0 | ^4.22.2 | | root | zod | ^4.3.6 | ^4.4.3 | | root | pnpm.overrides undici | — | <6.24.0 → ^6.24.0 | | root | pnpm.overrides ws | — | <8.20.1 → ^8.20.1 | | action | @types/node | ^25.5.0 | ^25.9.0 | | action | esbuild | ^0.25.0 | ^0.25.12 | | action | typescript | ^5.8.0 | ^5.9.3 | | worker | @cloudflare/workers-types | ^4.20260329.1| ^4.20260518.1| | worker | @cloudflare/vitest-pool-workers | ^0.13.5 | 0.13.5 (PIN) | | worker | @playwright/test | ^1.59.1 | ^1.60.0 | | worker | @types/node | ^25.6.0 | ^25.9.0 | | worker | prettier | ^3.8.1 | ^3.8.3 | | worker | vitest | ^4.1.2 | ^4.1.6 | | worker | wrangler | ^4.78.0 | ^4.92.0 | | worker | zod | ^3.25.0 | ^4.4.3 | | worker | oslo | ^1.2.1 | REMOVED | Notable: - wrangler 4.78 → 4.92: aligns with notme.bot PR #2 baseline; Node 22 runtime - zod 3 → 4 in worker: only one file (gha-oidc.ts) uses zod; uses safeParse + .error.message which are stable across v3/v4. Aligns with root manifest (was already ^4.3.6 there). - oslo removed: deprecated meta-package; zero imports in src/. Worker already uses the successor @oslojs/crypto + @oslojs/encoding directly. - @cloudflare/vitest-pool-workers pinned to exact 0.13.5 (no caret): per rosary-8ae6ab, 0.13.5 has the CF API 10375 issue; we don't yet know if 0.14+ fixes it. Pin makes the constraint explicit. - pnpm.overrides force undici≥6.24.0 and ws≥8.20.1: clears all 5 undici advisories (transitive via @actions/http-client v2) and the ws advisory (transitive via miniflare). Avoids taking the @actions/* major bumps (4.0 is ESM-only — separate refactor). Deferred (need code change or evidence — separate beads): - @actions/core 1 → 3 / @actions/http-client 2 → 4: ESM-only migration; action is currently bundled via esbuild but the ESM-only constraint is a real refactor. Advisories handled via pnpm.overrides instead. - @peculiar/x509 1 → 2: security-sensitive cert API surface; cert-authority.ts + signing-authority.ts need careful review of v2's extension/generator API. - typescript 5 → 6 (action): major TS bump warrants its own pass across the workspace, not bundled in deps sweep. - esbuild 0.25 → 0.28 (action): 0.x bumps frequently change defaults; aligned worker is already at 0.28 — leaving action at 0.25 line for now to avoid bundling-flag drift. Test status: - worker: 425 passed | 6 todo (28 files) — matches baseline - task worker:check: typecheck + tests both green - task schema:check: green - action: pnpm build green, dist/index.js rebuilt and node --check clean - pnpm audit: 0 advisories (was 2 high / 4 moderate) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ci: trigger run after Actions re-enabled * fix(proxy): collapse redundant nested if in UDS path validator (clippy::collapsible_match) The outer 'contains("..")' check was strictly redundant — the inner 's == ".."' check (the only one that returned) implies it. Collapse the pair into a match guard. No behavior change; UDS paths containing '..' as a substring (e.g. 'foo..bar.sock') are still permitted, only exact '..' components reject. CI for the whole repo couldn't pass while this lint was hot under -D warnings. Unblocks PRs #20/#21/#22 once #23 merges and they rebase. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…ler rate bucket into Worker Brings the two cloister-hardened modules (introduced in #19) into actual use: - VAULT_KEK_SOURCE env var drives KEK resolution via the URL dispatcher (env://, file://, keychain://, http(s)://). The DO #getKEK delegates to buildKekSource() instead of consuming env.VAULT_KEK_SECRET directly. - RateBucket gates handleRequest with 429 + Retry-After on over-budget callers. Bucket state lives in DO memory (Map<sub, BucketState>), charged via a new consumeBudget() RPC method using the pure refill / tryConsume helpers from rate-bucket.ts. Cost class derived from method + path: PUT=write, DELETE/admin=read, everything else=proxy. - Legacy VAULT_KEK_SECRET path preserved with a one-time console.warn deprecation on first derive so existing deployments aren't broken. Identity resolution is now hoisted out of the handler-passed callback so the worker can charge the rate bucket BEFORE delegating — handler still receives the same `resolveIdentity` shape (no API break for tests). No existing vault-{adversarial,security,encryption,handler}.test.ts invariants change. New worker-do.test.ts covers env://, file://, legacy fallback (one-shot warn), missing config (throws), the 429 saturation path, per-caller isolation, and the read<write<proxy cost ordering. wrangler.toml.example + README updated with the KEK source choice matrix, kek-helper sidecar pointer (cloister ADR-0019), and the rate budget shape (capacity 100, refill 10/s, costs 1/3/5). Closes rosary-54ad76 (Phase A.1.6). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- wrangler.toml.example + README.md: trim KEK schemes to what buildKekSource() actually supports today (env, file, keychain, https). Helper-binary schemes (secret-tool/op/apple-password/keyring) deferred to follow-up bead rosary-ab33cb. - worker.ts: hoist route/method validation (preValidateRoute) before resolveIdentity() so a flood of garbage paths can't force JWT verify + DPoP replay RPC work. - worker.ts: cap buckets Map at 10_000 entries with LRU eviction (delete-then-set on access; evict Map.keys().next() on overflow) to bound DO memory growth against a stream of unique sub values. - worker-do.test.ts: new tests cover (a) preValidateRoute accepts known routes and rejects garbage with 400, (b) buckets Map evicts LRU when flooded with > BUCKET_CAP unique callers while preserving rate-limit correctness for active callers. 141 -> 145 tests passing.
ef8d5f6 to
0c9623e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase A.1.6 of the vault re-incorporation. Brings the two cloister-hardened modules (introduced in #19) into actual use — they were sitting in the tree unused, which the #19 reviewer flagged.
VAULT_KEK_SOURCEdrives KEK resolution via the URL dispatcher (env://,file://,keychain://,http(s)://). The DO's#getKEKdelegates tobuildKekSource(spec, env).resolve()instead of readingenv.VAULT_KEK_SECRETdirectly.Retry-Afterwhen the caller is over budget. State lives in DO memory (Map<sub, BucketState>), charged via a newconsumeBudget(sub, costClass)RPC method using the purerefillBucket/tryConsumehelpers fromrate-bucket.ts. Cost class derived from method + path:PUT=write,DELETE/admin endpoints=read, everything else=proxy.VAULT_KEK_SECRETpath preserved with a one-timeconsole.warndeprecation on first derive — keeps existing deployments working through the rollout.Identity resolution moved out of the handler-passed callback so the worker can charge the bucket BEFORE delegating. Handler API unchanged (still takes
resolveIdentity), so no existing test signatures break.Test plan
vault-adversarial,vault-security,encryption,vault,worker,kek-source,rate-bucket(134 tests, untouched)worker-do.test.tscovers:env://Xresolves toenv.X(full encrypt path)file://resolves via theKEK_DISKservice bindingVAULT_KEK_SOURCE→ legacyVAULT_KEK_SECRETwith exactly one deprecation warning per DO lifetimeno KEK source configuredconsumeBudgetsaturates after capacity / cost reqs and returnsretryAfterSec >= 1read < write < proxynpx tsc --noEmitclean viaworker/tsconfig.json(includesvault/src/**)Cross-references
KEK_HELPER)Out of scope
packages/schema-bridge/(touched by a different bead)pnpm-workspace.yaml, CI config, lockfiles (rosary-546e83 owns those)