Release Updates

Changelog

Canonical release notes generated from Sphyr release metadata artifacts.

v0.24.0

2026-05-19

**Security Depth — MCP Coverage and Launch Hardening (Phases 112–119).** AI-agent threat transparency, injection pattern expansion, SDK-level tool schema drift detection, supply chain hardening, and credits system billing correctness. | Change | Detail | | :----- | :------ | | **AI-agent threat coverage matrix (Phase 112)** | `### AI-Agent Threat Coverage` section added to `SECURITY.md` with a public three-column table: 3 COVERED, 3 PARTIAL, 3 OUT OF SCOPE classes with explicit revisit conditions. | | **Injection pattern expansion (Phase 113)** | `INJECTION_PATTERNS` expanded from 5 to 9: `dan_persona`, `authority_override`, `continuation_marker`, `base64_decode_execute`. U+2028/U+2029 leading-newline bypass closed. Property tests extended for Unicode/NFKC/determinism. | | **Tool schema drift detection — Python SDK (Phase 114)** | SHA-256 baseline per MCP server origin, per-tool `change_type` granularity, `SphyrDriftError` in BLOCK mode. Wired into httpx sync and async transport wrappers. Re-exported from `sphyr_sdk` and `sdk/agent_guard_utils.py`. | | **Tool schema drift detection — TypeScript SDK (Phase 114)** | Byte-exact parity with Python (Unicode code-point sort + Web Crypto SHA-256). Wired into `patchedFetch` and `patchedAdapter`. Re-exported from `@sphyr/sdk` and `sdk/agent_guard_utils.js`. | | **Tool drift gateway feature flag (Phase 114)** | New D1 column `key_flags.tool_drift_mode` (`'LOG'` default, `'BLOCK'` opt-in via admin). `audit_sess` returns `tool_drift_mode` in response body when non-default; SDKs cache at session start. Migration: `0024_tool_drift_mode.sql`. | | **Cross-SDK parity fixture (Phase 114)** | `sdk/python/tests/fixtures/tool_drift_parity_fixture.json` — shared source of truth consumed by both Python and TypeScript test suites; either SDK diverging its canonical JSON or hashing fails both suites simultaneously. | | **Supply chain hardening — CI `--ignore-scripts` (Phase 116)** | All 20 `npm ci` invocations across 9 CI workflow files now include `--ignore-scripts`, preventing postinstall script execution during dependency installation on CI infrastructure. | | **Supply chain hardening — CycloneDX SBOMs (Phase 116)** | Four CycloneDX 1.x JSON SBOMs committed to repo root: `sbom.json` (gateway, 36 components), `sbom-sdk-ts.json`, `sbom-cli.json`, `sbom-python.json` (12 components). `sbom.yml` workflow automates regeneration on every push to `main`. | | **Adversarial injection corpus (Phase 116)** | `tests/lib/adversarial-corpus.test.ts` — regression tests covering all 10 injection pattern groups using real-world jailbreak samples from JailbreakBench, PromptInject, and HackerOne. | | **TypeScript SDK — dedicated Node.js test suite (Phase 117)** | `sdk/ts/vitest.config.ts` uses a Node forks pool, cleanly isolating SDK tests from the Miniflare/Workers environment. 8 integration tests migrated from the Workers pool. | | **Credits — atomic Stripe webhook batches (Phase 118)** | `handleCheckoutCompleted` and `handlePaymentIntentSucceeded` merged from split two-await writes into a single `env.DB.batch([purchase, balance, ledger])` — no partial state possible if D1 fails mid-handler. `insertStripeTopupPurchase` deleted; inline batch replaces it. `paymentIntentId ?? null` preserves the UNIQUE index semantics. | | **Credits — `creditRefunded` idempotency guard (Phase 118)** | `creditRefunded: boolean` added to `GatewayContext`. Guard in `refundCredits` prevents double-refund when the handler retries after a transient D1 error. | | **Credits — `stripe_events.status` semantic fix (Phase 119)** | Refund, dispute, and failed-payment handlers now write `'processed'` instead of `'credited'`. D1 migration `0025_stripe_event_status_processed.sql` extends the CHECK constraint. `'credited'` is now reserved for events that actually apply credits (checkout, subscription renewal). | | **Credits — refund rollback on failure (Phase 119)** | `refundCredits` wraps `adjustCredits` in try/catch; resets `gctx.creditRefunded = false` on failure so the pipeline-level catch can attempt recovery. `guard_net.ts` pipeline catch calls `refundCredits` as best-effort when the pipeline throws before a refund was applied. | | **Credits — null `payment_intent` guard (Phase 119)** | `handleChargeRefunded` now skips the `credit_purchases` UPDATE when `charge.payment_intent` is null (direct charges), preventing a silent zero-row match that diverged the ledger from `credit_purchases`. Balance decrement and ledger insert run unconditionally. | | **Credits — `refundKeySpend` conditional on success (Phase 119)** | `refundKeySpend` is now inside the `if (refund.success)` block. Failure path emits `log.error('refundCredits.adjust_failed')` so operationally invisible mismatches surface in logs. | | **Credits — `insertAdminGrantPurchase` expiry guard (Phase 119)** | Throws if `computedExpiresAt <= Date.now()`, preventing silently-expired grants from a stale `purchasedAt` input. | ---

v0.23.0

2026-05-17

**Sphyr Trace Launch + Security CI Hardening — Phases 108–111.** Product entitlement system, console Trace UI with session timeline, marketing Trace launch, and CI security gates. | Change | Detail | | :----- | :------ | | **Sphyr Trace — product entitlement** | Phase 108: `PATCH /api/admin/keys/:key_id/products` endpoint grants or revokes product access per key. Body: `{ product: "gateway" \| "trace" \| "box" \| "state", enabled: boolean }`. Writes a tamper-evident `product_toggle` entry to `admin_audit_log` and busts the live KV session so the change propagates immediately. CSV normalization (trim, deduplicate, canonical join) on every write prevents string drift. `products_enabled` now defaults to `"gateway,trace"` for all new keys — Trace is on by default. | | **Sphyr Trace — console session timeline** | Phase 109: Console gains a Trace page at `/keys/:keyId/trace`. The key dashboard row shows a `Trace →` link when expanded. `GET /v1/console/keys/:keyId/trace/sessions` returns up to 50 sessions (grouped by session hash, with request count, block count, max risk score, start/last-seen timestamps). `GET /v1/console/keys/:keyId/trace/sessions/:sessionHash` returns up to 500 request entries for a session with method, category, outcome, risk score, rationale, and trace ID. Both endpoints return `PRODUCT_NOT_ENABLED` (403) when the Trace product is not enabled for the key. | | **Sphyr Trace — marketing launch** | Phase 110: Marketing site updated with Trace observability content: home page intro section, 9th feature panel in the features grid, and a Session observability section in the MCP docs page. | | **Semgrep SAST CI gate** | Phase 111: `sast.yml` runs Semgrep CE on every PR and push to `main` using rulesets `p/nodejs`, `p/typescript`, and `p/owasp-top-ten`. Fails-closed on any finding (`--error`). SARIF report uploaded as a 30-day artifact. SHA-pinned image (`semgrep/semgrep@sha256:7cad2bc2d1…`). | | **TruffleHog secret-scan CI gates** | Phase 111: `secret-scan.yml` gates every PR with TruffleHog `--only-verified` (currently-valid live credentials only). `secret-scan-nightly.yml` runs at 05:00 UTC on full history with `--results verified,unknown`, uploading findings as a non-blocking 30-day artifact. Both workflows use a SHA-pinned action (v3.95.3). | | **CRLF header injection hardening** | Phase 111: Forwarded header values are now stripped of `\r` and `\n` in both directions (inbound agent headers and upstream response headers). Agent-supplied header keys containing CRLF characters are rejected with `HEADER_INVALID`. | | **Quarantine tag attribute escaping** | Phase 111: The `QUARANTINE` injection-detection wrapper now escapes the quarantine tag through all attribute/case variants, closing bypass paths where attribute mutations could evade the tag scanner. | | **MCP tool-description capture** | Phase 111: `scanRegisteredToolDescriptions` interceptor wired into both MCP server factories (stateless and session-based). Tool descriptions are captured at registration time for downstream scanning of prompt-injection patterns. | ---

v0.22.4-security

Breaking

2026-05-16

**Security backlog closure — INFO findings (I-06 through I-10, R2-L-04).** | Change | Detail | | :----- | :------ | | **I-07: DELETE request bodies now forwarded (breaking)** | `DELETE` requests with a body now forward the body to the upstream server per RFC 9110 §9.3.5. Previously the body was silently dropped. Agents that send `DELETE` requests with bodies will now have those bodies forwarded; agents relying on the silent-drop behaviour must be updated. | ---

v0.22.3

2026-05-14

### Added - **Webhook event delivery** — User-registered webhook endpoints now receive real-time notifications for security events. Five events are supported: `HONEYTOKEN` (key compromised and suspended), `ENTROPY_BLOCKED` (exfiltration scanner triggered), `KEY_PAUSED` (key suspended by admin emergency stop or circuit breaker), `KEY_BUDGET_EXCEEDED` (per-key lifetime spending cap reached), and `LOW_BALANCE` (account balance dropped below 200 credits). Delivery is at-least-once with 5 automatic retries via Cloudflare Queues. Each delivery is HMAC-SHA256 signed with the endpoint's unique signing secret and includes an `X-Sphyr-Event` header for event routing. Permanently failing endpoints are marked `failing` in the console after all retries are exhausted. `GLOBAL_PAUSE` has been removed from the subscribable event list — agents receive the `GLOBAL_PAUSE` error code directly from the gateway during active incidents. ---

v0.22.2

2026-05-14

**Security hardening — pre-launch audit (continued).** Twenty-five MEDIUM findings resolved across admin, console, CLI/SDK, crypto, schema, and documentation. | Change | Detail | | :----- | :------ | | **M-01: Admin login 503 when CF-Connecting-IP absent** | Admin login endpoint now returns HTTP 503 when the `CF-Connecting-IP` header is missing, failing-closed rather than silently binding to `undefined` IP. Prevents silent session binding to unvalidated IP in edge cases where proxy headers are misconfigured. | | **M-20: Hard-fail 503 when ADMIN_GUARD absent** | Admin auth middleware now explicitly throws when `ADMIN_GUARD` Durable Object binding is undefined, returning HTTP 503. Removes silent degradation path that would allow unauthenticated access if the binding is accidentally omitted from `wrangler.toml`. | | **M-22: Origin-header CSRF check on state-changing routes** | State-changing admin routes (`/admin/customers/:id`, `/admin/customers/:id/override`, `/admin/kill-switch`) now validate the `Origin` header against the expected admin domain, adding an additional layer of CSRF defense beyond SameSite=Strict cookies. | | **M-18: Admin run-job uses 60s rate-limit window** | `/admin/run-job` rate limit migrated from 1-second window (1 req/sec) to 60-second sliding window (100 requests per 60 seconds), aligning with other admin routes and reducing false-positive rate-limit rejections during batch operations. | | **M-19: Remove dead IP-bypass on run-job** | Removed unreachable code path in `api-entry.ts` that attempted to bypass rate limiting based on IP. This bypass was shadowed by the higher-level `ADMIN_GUARD` RPC call and is now explicitly removed to reduce attack surface. | | **M-05: Per-user rate limits on console read endpoints** | Console read-only endpoints (`GET /v1/console/keys`, `GET /v1/console/customers`, etc.) now enforce a per-user rate limit of 60 requests per minute. Prevents information disclosure attacks via high-volume enumeration or scraping. | | **M-10: Session token hash storage in admin grants** | `admin_grants.admin_id` now stores the SHA-256 hash of the session token (not the token itself). Sessions validated via constant-time comparison with the stored hash. Reduces session-token exposure risk if the KV bucket is compromised. | | **M-04: Device flow credentials encrypted in KV** | Device flow temporary credentials (stored at `gn_device:{user_code}`) are now encrypted with AES-256-GCM before storage in KV. Authorization codes and session tokens are no longer readable from a raw KV snapshot. | | **M-03: Console SPA proxy uses exact host match** | Console SPA proxy (`/admin/spa`) now validates the request host against the exact `ADMIN_DOMAIN` rather than allowing any `.workers.dev` subdomain. Prevents subdomain takeover exploitation of the proxy route. | | **M-16: CLI generic error to stdout, real error to stderr** | Fatal CLI errors now emit a generic `"Authentication failed"` message to stdout and log the detailed error to stderr. Prevents sensitive implementation details (stack traces, internal function names) from leaking to stdout or automation logs. | | **M-14: Retry-After cap at 60 seconds** | CLI and Python SDK retry policies now cap the `Retry-After` header at 60 seconds maximum. Prevents malicious servers from returning extreme backoff values (e.g., 1 year) that would hang the CLI or block SDK operations indefinitely. | | **M-15: Remove tenacity runtime dependency from Python SDK** | Python SDK no longer depends on the `tenacity` library for retry logic. Retry behavior reimplemented inline using standard library, reducing transitive dependencies and associated CVE surface. | | **M-21: Validate Stripe checkout URL hostname** | Stripe checkout session redirect URL now validates that the hostname is `checkout.stripe.com` before the redirect. Prevents open-redirect attacks if Stripe URL parsing changes or a local config is misconfigured. | | **M-12: Constant-time comparison for session token coherence check** | Session token coherence validation now uses `constantTimeEqual()` instead of `===` operator, preventing timing side-channels that could leak token prefixes. Hardens against attackers measuring response timing to infer valid session prefixes. | | **M-13: Enforce minLength 32 for ADMIN_KEY_ID** | `ADMIN_KEY_ID` Worker secret is now validated to be exactly 32 characters at startup. Prevents deployment with insufficient entropy, which could allow brute-force attacks on the admin session token. | | **M-08: AuditLogDO serializes hash chain** | New `AuditLogDO` Durable Object serializes append-only audit hash chain operations, closing TOCTOU race where concurrent audit writes could produce non-linearizable log sequences. Audit entries are now cryptographically sequenced via rolling SHA-256 hash chain. | | **M-07: CHECK constraints on 8 enum columns** | D1 schema now includes `CHECK` constraints on 8 enum/status columns: `grant_type`, `resource_type`, `verification_status`, `status` (3×), `type`, and `reason`. Prevents invalid enum values from entering the database via SQL injection or ORM bypass. | | **M-09: user_id_hash NOT NULL constraint** | `request_logs.user_id_hash` column now has a `NOT NULL` constraint, enforced via table recreation migration. Ensures all audit-log entries have a valid user identifier for forensics and prevents NULL-injection attacks. | | **M-11: Composite index on credit_ledger** | Added composite index `credit_ledger(user_id, created_at DESC)` to optimize credit reconciliation queries. Reduces query time from O(n) table scan to O(log n) index seek, hardening against time-based DoS on reconciler paths. | | **M-06: SQL injection safety comments** | Added inline comments documenting why dynamic `IN (...)` clauses in `src/lib/ae-sql.ts` are safe (values are pre-validated integers, not user input). Reduces future maintenance risk of accidental SQL injection. | | **M-23: Meta CSP fallback in HTML entry points** | Admin SPA and console HTML entry points now include a meta CSP tag as a fallback if HTTP header delivery fails: `<meta http-equiv="Content-Security-Policy" content="default-src 'none'">`. Ensures CSP policy always applies even if middleware is misconfigured. | | **M-25: NAT gateway caveat in session IP binding docs** | Session IP binding documentation now includes a caveat: IP binding is not suitable for deployments behind shared serverless NAT gateways (AWS Lambda, Google Cloud Functions, etc.) where multiple unrelated users may share the same egress IP. Admin operators should disable IP binding in such environments or enforce IP ranges. | ---

v0.22.4

2026-05-14

**Security hardening — Round 2 architecture concerns.** | Change | Detail | | :----- | :------ | | **Fix 1: Per-IP rate limit on OAuth new-user creation** | OAuth callback now enforces two-tier per-IP rate limits (3 new accounts/hour, 20/day) on the new-user branch only. Existing-user logins are unaffected. Null-IP requests share a single fallback bucket. Redirects to `/login?error=rate_limited` on block. Mitigates casual signup-bonus abuse ($0.20/account) without requiring CAPTCHA at launch. | | **Fix 2: Transient D1 error classification in circuit breaker** | `handleCreditDbError` now classifies D1 errors as transient (network/timeout patterns) or persistent (constraint violations, schema errors). Transient errors skip the circuit-breaker increment, preventing legitimate users from being auto-paused during regional Cloudflare D1 incidents. Persistent errors increment as before; auto-pause behavior is unchanged. | | **Fix 3: D1-verify `client_reference_id` in Stripe webhook handler** | `resolveUserId` now verifies `client_reference_id` against the `users` table (`status NOT IN ('scrubbed', 'banned')`) before trusting it. Prevents log poisoning via `stripe_events.user_id` if the Stripe webhook secret is compromised. GDPR-scrubbed and banned users no longer receive credits; paused users still receive credits (purchase honored). | ---

v0.22.1

Breaking

2026-05-14

**Security hardening — pre-launch audit.** Eight security findings resolved across auth, crypto, rate limiting, webhooks, and billing. | Change | Detail | | :----- | :------ | | **H-07: Loopback CLI auth deprecated** | The loopback OAuth flow stored `api_key` and `hmac_secret` in URL query parameters, exposing credentials to browser history (RFC 8252 §8.2 violation). Device authorization (RFC 8628) is now the default. **Migration:** Run `sphyr init` — you will see a device code prompt instead of an automatic browser redirect. Headless/CI users are unaffected. | | **H-09: Admin session now HttpOnly cookie** | Admin SPA authentication migrated from `sessionStorage` bearer token to `HttpOnly; Secure; SameSite=Strict` cookie. Admin session tokens are no longer accessible to JavaScript (eliminates XSS session-theft risk). | | **H-03: HMAC secrets encrypted at rest** | Per-key HMAC signing secrets in `console_keys.hmac_secret` and webhook signing secrets are now encrypted with AES-256-GCM before storage in D1. A D1 database dump alone no longer exposes customer signing secrets. Requires `HMAC_ENCRYPTION_KEY` Worker secret at deploy time. | | **H-04: Webhook signing secret delivered on creation** | `POST /v1/console/webhooks` now returns the signing secret once in the response body (Stripe show-once pattern). `POST /v1/console/webhooks/:id/rotate` added for secret rotation. The `X-Sphyr-Signature` feature was previously inoperable (secret was generated but never exposed to users). | | **H-02: Stripe metadata trust hardening** | Stripe customers are now pre-created at checkout session start, ensuring `users.stripe_id` is always set before any `payment_intent` event arrives. Webhook handlers resolve the beneficiary via `customer → users.stripe_id` D1 lookup only; `metadata.user_id` removed from all authorization paths (OWASP ASVS V4.2.1). | | **H-01: Rate limiter fail-closed on critical paths** | `checkRateLimit` and `checkWindowRateLimit` now accept a `failClosed` parameter. 14 security-critical call sites (admin auth, MCP entry, key minting, device flow, billing) will return `{ allowed: false }` when the Durable Object layer is unavailable rather than failing open (OWASP ASVS V4.1.5). | | **H-08: Admin auth bypass closed** | `src/routes/admin.ts` middleware replaced `path.endsWith('/login')` with a `BYPASS_PATHS` `Set.has()` exact-match check, eliminating a latent bypass class where any future route ending in `/login` would silently skip authentication (OWASP ASVS V4.1.1). | | **M-17: DO test-only methods gated in production** | `_resetForTest()` and 7 other `_*ForTest` methods on `SessionGuardDO` and `AdminGuardDO` now throw when `NODE_ENV` is `production` or `staging`, preventing any Worker in the binding scope from wiping rate-limit and replay-cache state outside test environments (OWASP ASVS V14.2.2). | ---

v0.22.0

2026-05-13

**Open-Source Prep, Repo Organization, Device Flow & Structured Errors — Phases 104–107.** 51/51 requirements satisfied. Public orphan branch, compliance artifacts, tsconfig inheritance consolidation, RFC 8628 device authorization, PII-safe logging expand/contract, and repo-wide structured error enforcement. | Change | Detail | | :---------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Open-source prep** | Phase 104: `Sphyr-internal` private backup repo created with full history and 8 tags mirrored. `TRADEMARKS.md`, `NOTICE`, and `DCO.md` compliance artifacts added to main. FSL trust-strip pill and footer link wired to `compliance.astro#source-available` on the marketing site. Public orphan branch cut with 543 files matching D-06 allowlist — `.planning/`, `ROADMAP.md`, `CLAUDE.md`, `benchmarks/`, and `docs/superpowers/` excluded. | | **Repo organization** | Phase 105: `tsconfig.base.json` and `tsconfig.node.base.json` added at repo root; all four workspaces (`root`, `admin-spa`, `apps/console`, `sdk/ts`) extend the base, eliminating 5× duplicated compiler options. `tests/lib/ae-sql.test.ts` moved from `src/lib/` to its canonical path and registered in the Node pool — git history preserved via `--follow`. Five inline `SessionGuardDO` stub definitions consolidated into `tests/helpers/session-guard-stub.mts`; two redundant cold-start configs merged into `vitest.cold-start-db-state.config.mts`. | | **PII-safe logging** | Phase 106: `log.ts` `emit()` applies `scrubPii()` to every user-supplied string field before serialization. `user_id_hash` expand step: migration `0014` adds the column alongside `user_id`; `writeLog()` dual-writes both fields with `PHASE-107-CLEANUP` markers; all `log.*` call sites in billing, webhook, and reconciler paths pass `user_id_hash` — raw `user_id` absent from the log stream. | | **RFC 8628 device flow** | Phase 106: `sphyr init --device` CLI command — prints `user_code` + `verification_uri`, polls `/v1/device_auth/token` until approved, writes `api_key` + `hmac_secret` to IDE config. `/v1/console/device/authorize` and `/v1/console/device/approve` routes added. Branch B unauthenticated device approval page with GitHub OAuth round-trip (`/device?code=XXXX-XXXX` → OAuth → return). Per-IP rate limit on `/v1/device_auth/token` (Durable Object). | | **Structured error refactor** | Phase 107: `SphyrError` class (`src/lib/server-error.ts`) with `code` + `context` own fields. `LogExtras.error` typed as `SphyrError` — passing a plain string is now a TS compile error. `emit()` serializes `SphyrError` as `{code, context}` before `scrubEntry()` runs. All 16 `throw new Error()` sites across 13 files migrated to `throw new SphyrError()`. ESLint `no-restricted-syntax` gate blocks `throw new Error(...)` where the argument is a template literal or string concatenation in `src/**/*.ts` (bare variable arguments are not blocked — data-flow analysis is outside ESLint scope). Expand/contract CONTRACT step: migration `0015` drops `user_id` from `request_logs`; all dual-match OR queries simplified to `WHERE user_id_hash = ?`; `PHASE-107-CLEANUP` markers removed. | ---

v0.21.0

2026-05-08

**TOCTOU & Durable Object Hardening — Phases 97–103.** 12/12 requirements satisfied (phases 97–100) plus test infrastructure resilience, drift detection, and timing-oracle / lock-safety hardening (phases 101–103). | Change | Detail | | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **TOCTOU race audit** | Phase 97: Complete race inventory across the guard_net pipeline. All races dispositioned as accept, fix, or defer. Ghost-session × credit-deduction cross-threat analysis documented. Formal backlog table prevents races from resurfacing as open findings. | | **Circuit breaker & fail-safe DO atomicity** | Phase 98: `checkAndIncrementCircuitBreaker` and `checkAndIncrementFailSafe` migrated from KV get+put to `SessionGuardDO` RPC calls. Concurrent Worker isolates can no longer both read stale KV state during a D1 outage. Fail-safe free-pass ceiling (5/hr) is now enforced atomically. Expired counter rows purged via existing DO alarm cycle. | | **Concurrent test coverage** | Phase 99: `Promise.all` concurrent tests confirm CB and FS counters are race-free. `phaseCredits` integration tests cover circuit-breaker trip, auto-pause threshold, and fail-safe ceiling with DO stubs. | | **Per-IP rate limiting** | Phase 100: `gn_ip:{ip}` shard added to `SessionGuardDO` in `phaseRateLimit()`. Multi-key attackers can no longer bypass per-key RPS limits by distributing concurrent requests from the same IP. `IP_RATE_LIMITED` Analytics Engine counter added. | | **Test infrastructure resilience** | Phase 101: `deleteAlarm()` added to `_resetForTest()` in `SessionGuardDO` and `AdminGuardDO` — prevents alarm carry-over between tests. Namespace-isolation test hardened with explicit `SHARED_BUCKET` semantics. | | **Implementation drift detection** | Phase 102: `AdminGuardDO` stub hash file and `refresh-stubs` script added. CI `check-admin-guard-stub-hash` job blocks merges when the stub diverges from the live implementation. Zero-tolerance coverage drift detection script (`check-coverage-drift`) added as a standalone CI job. | | **Timing oracle elimination** | Phase 103: `audit_sess` jitter replaced with constant-time response padding (`T1-01`) — removes the timing side-channel that allowed session-existence inference. `phaseHmac` HMAC key import inlined to bypass the module-level `_hmacKeyCache` (`CR-03`). `normalizedSig` used in the hex pre-filter instead of raw sig to prevent casing bypass (`CR-02`). | | **Lock safety & SDK hardening** | Phase 103: `invalidateAndRefresh()` method added to the Python SDK async client to fix the `SESS_INVALID` concurrent race (`CR-04`). `MAX_RETRY_AFTER_MS` cap added to both TypeScript and Python retry policies — prevents unbounded backoff when the server returns an extreme `retry_after_ms`. Python `_parse_result` guarded against `content=[None]` `AttributeError` (`CR-01`). | ---

v0.20.0

2026-04-30

**Admin & Console Launch Audit, Code Quality & SDK Instrumentation — Phases 83–96.** Marketing site UI polish, admin API test coverage, admin cold-start failure tests, admin dashboard accessibility hardening, console surface fixes, axe-core CI scan, code hygiene, console OAuth / CLI auth, testing infrastructure audit and remediation, coverage gap closure, and Python SDK auto-instrumentation scaffolding. | Change | Detail | | :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Marketing site UI polish** | Phase 83: `pricing.astro` fully migrated to Sphyr design token system. `prefers-reduced-motion` guard added to trust-arrow hover transform. Schema.org structured data and copy accuracy fixes applied. | | **Admin API test coverage** | Phase 84: Auth guard assertions added for all 5 admin route groups (ATEST-01). Coverage added for Customers, Threats, Forensics, Allowlists, Overview, and Settings read paths. Kill-switch idempotency test added. Static asset security headers regression test added (`KNOWN_ASSET` hash). | | **Admin cold-start failure tests** | Phase 85: Four dedicated vitest cold-start configs (ACOLD-01..04): entropy gate, missing Unkey key, missing honeytoken domain, LOG_SALT mismatch. `test:cold-start` npm script and `test.yml` CI job added. All four confirmed passing at runtime (2026-05-01). | | **Admin bug fixes & accessibility** | Phase 86: Error states added to `SuggestedBansSection`, `HoneytokenSection`, and `OverviewCharts`. `TriageQueue` context sync fixed — triage badge updates immediately after all 5 mutation actions (ban, dismiss, clear, resume, revoke). WCAG AA: `aria-label` on date inputs (AA11Y-01), triage badge aria-label ("N threats pending", AA11Y-02), `aria-pressed` on events/sessions toggle (AA11Y-03), `aria-live="polite"` on all three key panels (AA11Y-04). | | **Console surface fixes & test coverage** | Phase 87: All `/v1/*` error responses now include 5-field envelope (`code`, `message`, `retryable`, `request_id`, `docs_url`). `app.notFound()` registered — unknown `/v1/*` URLs return JSON 404 instead of Cloudflare HTML. Five `data-testid` attributes added for autonomous agent key-lifecycle automation (CFIX-03). `aria-busy` on KeysTable loading container (CFIX-04). `retry_after_ms` in console route 429 responses (CFIX-05). `CreateKeyModal` and `RevokeKeyModal` component tests added. | | **Admin axe-core CI scan** | Phase 88: Playwright axe-core WCAG 2.1 AA scan added for admin SPA — 5 pages (login, dashboard, customers, forensics, triage), 0 violations. `admin-axe` CI job green in production (2026-05-01). `test-spa` CI job added to gate admin-spa jsdom and console jsdom tests on every push. | | **Code hygiene** | Phase 89: Stale `.js` test helpers (`fetch-mock.js`, `hmac.js`) deleted. Dead code removed from `guard_net.ts` and `usage_summary.ts`. | | **Console OAuth / CLI auth** | Phase 90: `CliAuthPage` at `console.sphyr.io/cli-auth`. OAuth port-threading via KV state (`cli:{port}` value survives the GitHub OAuth round-trip). Loopback credential delivery to `127.0.0.1:{port}` — CLI receives `api_key` + `hmac_secret` without clipboard paste. | | **Testing infrastructure audit** | Phase 91: 135-row test inventory matrix across all 4 workspaces (api pool, admin pool, admin-spa, console). 9-dimension audit covering organization, coverage, integrity, and agent-readiness. Verification scripts added. | | **Test audit remediation** | Phases 92–94: Stale `.js` test artifact deleted; `check-no-stale-js-test-artifacts` and `check-no-shared-extracthandler` CI gates added. Unkey v2 URL mock drift fixed in 3 test files; property test coverage added for idempotency. 8 corrected Unkey mock interceptors in `tests/ready.test.ts`; DI-02 and DI-03 failures resolved. | | **Coverage gap closure** | Phase 95: `tests/lib/alert.test.ts` added (3 cases: happy path, 500 error, network error). `src/lib/analytics.ts` lifted from excluded to measured — 97.61% statement coverage, 96.66% branch coverage. | | **Python SDK instrumentation scaffold** | Phase 96: `sdk/python/sphyr_sdk/_instrument.py` created with `_build_exclusion_check` (RFC1918 IP exclusion regex) and private patch/unpatch helpers (`_patch_requests`, `_unpatch_requests`, `_patch_httpx`, `_unpatch_httpx`). `requests>=2.32` added as optional dep. TDD xfail stubs cover instrumentation cases; coverage floor set to 80%. \_(Note: public `instrument\__`/`aiohttp`/`urllib`names described in earlier drafts were not implemented — private`_patch__` API shipped instead.)\_ | ---

v0.19.0

2026-04-28

**Marketing Integrity & Launch Readiness — Phases 77–82.** 32/32 requirements satisfied. Marketing copy accuracy pass, console onboarding with per-user HMAC secrets, WCAG AA / Observatory audit, signed denial-response system, and the operations launch checklist. | Change | Detail | | :---------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Marketing copy accuracy** | Pricing, features, and docs pages audited for false claims. JSON-LD structured data and `llms.txt` added. SEO and accessibility improvements across marketing site. | | **Per-user HMAC secrets** | `console_keys.hmac_secret` column (migration 0012) — each API key now has its own HMAC secret, eliminating the shared `HMAC_SECRET` worker secret. Console key-creation flow generates and returns the per-key secret on first issue. | | **WCAG AA + Observatory hardening** | Console SPA and marketing site pass Mozilla Observatory and WCAG AA. CSP tightened, HSTS added, accessibility fixes applied across all surfaces. | | **Signed denial responses** | Gateway signs `BLOCKED` responses with an Ed25519 key. Integrators can cryptographically verify a denial was issued by Sphyr. New endpoints: `GET /.well-known/sphyr-signing-key.json`, `POST /v1/verify-denial`. TypeScript and Python SDKs expose `DenialProof` / `denial_proof`. | | **Operations launch checklist** | `LAUNCH-CHECKLIST.md` authored — step-by-step first-deploy sequence covering infra, secrets, migrations, smoke tests, and human UAT sign-off. | ---

v0.18.0

2026-04-27

**Developer Experience & Agent Onboarding — Phases 68–76.** 19/19 requirements satisfied. SDK error taxonomy, MCP proxy in TypeScript and Python, onboarding docs, marketing site content, and proxy tech-debt cleanup. | Change | Detail | | :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **SDK error taxonomy** | `SphyrError` typed exception hierarchy with `SphyrAuthError`, `SphyrCreditError`, `SphyrRateLimitError`, `SphyrBlockedError` in both TypeScript and Python SDKs. `RETRYABLE_CODES` set for automatic retry logic. | | **MCP proxy — TypeScript** | `sphyr-proxy` bin in `@sphyr/sdk` — STDIO↔HTTP bridge for Claude Desktop and other MCP clients. Session management, HMAC signing, and error surfacing built in. | | **MCP proxy — Python** | `python -m sphyr_sdk.proxy` equivalent for Python MCP clients. Same session/signing contract as the TypeScript proxy. | | **Onboarding docs** | `/docs` site on sphyr.io with integration guides, MCP setup, and the agent `cat` field default (`api-call`). SDK quick-start in README. | | **Marketing site content** | Pricing page, features page, and MCP landing page published. | | **Proxy tech-debt cleanup** | Phase 76: scroll-spy fixes, SIGTERM timer leak, `waitForResponse` drain, SESS_INVALID binary test, dead `SphyrAuthError` import removed. | ---

v0.17.0

2026-04-25

**Surface Isolation & SDK Hardening — Phases 64–67.** 15/15 requirements satisfied. Worker surface split into api/admin/console, test infrastructure stabilisation, and SDK error taxonomy groundwork. | Change | Detail | | :------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Worker surface split** | `wrangler.toml` deleted and replaced with `wrangler.api.toml` (public gateway), `wrangler.admin.toml` (admin Worker), and Cloudflare Pages for the console SPA. Each surface has independent secrets, routes, and DO bindings. `AdminGuardDO` isolated to the admin Worker. | | **Marketing cleanup** | Phase 64: stale claims, broken links, and outdated pricing copy removed from the marketing site. | | **Test infrastructure stabilisation** | Phase 66: vitest configs split by surface (api / admin / console). `PIPELINE_LATENCY` binding added to `wrangler.admin.toml`. CI cold-runner timeout tuned. | | **SDK error taxonomy groundwork** | Phase 67: `SphyrError` base class and error code constants established in both TypeScript (`sdk/ts/`) and Python (`sdk/python/sphyr_sdk/`) SDKs. | ---

v0.16.0

2026-04-23

**Performance, Observability & Agent Ergonomics — Phases 59–63.** SessionGuardDO consolidation, Server-Timing latency observability, agent-native response headers, typed TypeScript and Python SDKs, and public discovery/trust endpoints. | Change | Detail | | :----------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **SessionGuardDO consolidation** | Phase 59: `RateLimiterDO` and `ReplayCacheDO` merged into a single `SessionGuardDO`. HMAC verification and the DO claim RPC now run concurrently (`Promise.all`), eliminating one serialized round-trip from the hot path. All call sites migrated; legacy DO classes deleted. | | **Production latency observability** | Phase 60: `Server-Timing` response header exposes per-phase and total pipeline latency on every gateway response. Analytics Engine receives phase timing doubles keyed by `PHASE_NAMES` for p50/p95/p99 dashboarding. Cached admin latency metrics endpoint added. | | **Agent-native response contract** | Phase 61: `X-Credits-Remaining`, `X-Quota-Remaining`, and `X-Request-ID` response headers added via `gatewayHeaders` middleware. `retry_after_ms` wired into 5 `makeErr` sites with named retry-duration constants. Idempotency middleware (schema + check/store helpers) added to 3 mutation routes; hourly prune job added to prevent unbounded table growth. | | **Typed SDK — TypeScript + Python** | Phase 62: `@sphyr/sdk` TypeScript SDK with namespaced call surface, bounded retry policy honoring server-supplied `retry_after_ms`, and write-path auto idempotency-key generation. Python SDK (`sphyr-sdk`) with strict Pydantic models, equivalent retry/idempotency, and shared parity fixture contract. Both SDKs ship with timeout contract tests. | | **Discovery + trust layer** | Phase 63: `/.well-known/sphyr-config` endpoint exposes machine-readable SDK metadata and release info. Public status page at `status.sphyr.io` (dedicated Astro workspace) with resilient poller, latency trend chart, and degraded-mode fallback semantics. Changelog RSS and JSON feed endpoints added. All new endpoints hardened with security headers and binding machine contract policy. | ---

v0.15.0

2026-04-20

**Marketing Polish & DCO Enforcement — Phases 55–58.** Sphyr design system landing page, LLM-friendly discovery endpoints, marketing site security hardening, and self-contained DCO enforcement. | Change | Detail | | :------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Landing page redesign** | Phase 55: Full rewrite of `apps/marketing/src/pages/index.astro` to the Sphyr hi-fi design — dark-first, Sphyr Lime accent, Geist typography. Eight sections: Nav, Hero, Proof Strip, Differentiation Grid, How It Works, Pricing, Final CTA, Footer. Content verified against `pricing.astro` and `docs.astro` as sources of truth. | | **Agentic discovery** | Phase 56: `public/llms.txt` (llmstxt.org spec — structured index of all 6 pages and machine-readable endpoints). `/docs.md` and `/llms-full.txt` Astro endpoints share a single `llms-prose.ts` content module. JSON-LD `SoftwareApplication` + `Offer` structured data on landing and pricing pages so AI assistants can access clean, machine-readable Sphyr content without parsing styled HTML. | | **Marketing site security hardening** | Phase 57: `apps/marketing/public/_headers` defines per-path CSP, HSTS (`max-age=63072000; includeSubDomains; preload`), `X-Frame-Options: DENY`, and `X-Content-Type-Options: nosniff`. Material Symbols font self-hosted to satisfy `font-src` CSP. `marketing-ci.yml` npm audit gate added to the build pipeline. | | **DCO enforcement via GitHub Actions** | Phase 58: Third-party `dcoapp` GitHub App replaced with a self-contained GitHub Actions workflow using the `tim-actions` three-step DCO pattern. All workflow action refs SHA-pinned. `dependabot.yml` added to keep action pins current. Zero external app install, no third-party trust boundary. | ---

v0.14.0

**Best-Practices Hardening — Phases 47–54.** Pre-launch security, observability, reliability, and code-organization hardening pass. 21/21 requirements satisfied across 8 phases and 22 plans. 221 files changed, 30,602 insertions, 4,064 deletions. | Change | Detail | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **CORS + CSRF hardening** | CORS allowlist pinned to exact `CONSOLE_PAGES_URL` origin — no `*.pages.dev` wildcard. Origin header validation added to all three console mutation endpoints (key create, key revoke, topup). SEC-01/04 closed. | | **Console rate limiting** | Hourly rate limits added to console state-changing routes: 10 key operations and 5 topup requests per `user_id`. SEC-03 closed. | | **Error message sanitization** | Raw `(err as Error).message` strings no longer forwarded to API clients in admin routes. SEC-05 closed. | | **Structured JSON logging** | New `src/lib/log.ts` helper replaces all ad-hoc `console.*` calls across `src/routes/`, `src/lib/`, and `src/jobs/`. OBS-01 closed. | | **Request ID tracing** | Hono middleware mints a server-side UUIDv4 `request_id` per request, threaded through all log entries, error responses, and Durable Object calls including `AdminGuardDO`. OBS-02 closed. | | **ReplayCacheDO wired** | `02-hmac.ts` now uses `ReplayCacheDO.claim()` atomic RPC for replay detection instead of KV get/check/put. Closes the TOCTOU race documented since v0.11.0. REL-01 closed. | | **AdminGuardDO implemented** | `AdminGuardDO.checkAndIncrement()` implemented with full brute-force counter logic and `request_id` logging. Replaces KV-backed admin auth failure counters. REL-02 closed. | | **Error envelope unification** | 54 ad-hoc `{error: '...'}` response objects in `admin-api.ts` replaced with canonical `errorResponse(code, message)`. API-01 closed. | | **Analytics Engine** | Cloudflare Analytics Engine wired for 5 gateway outcome counters: `ALLOWED`, `BLOCKED`, `HONEYTOKEN`, `QUOTA_EXCEEDED`, `HMAC_INVALID`, and `INBOUND_BLOCKED` paths. OBS-03 closed. | | **`GET /ready` health endpoint** | New liveness endpoint checks D1, KV, and Unkey reachability and reports which dependency is down. OBS-04 closed. | | **Audit log hardening** | IP address captured on every mutating admin action. 8 previously-unlogged mutation routes now write audit entries. SHA-256 `prev_hash` tamper-evidence chain added to `admin_audit_log`. AUDIT-01–03 closed. | | **Code organization refactor** | `src/lib/db.ts` split into 5 domain modules under `src/lib/db/`. `src/routes/admin-api.ts` split into 10 Hono sub-routers under `src/routes/admin/`. `src/mcp/tools/admin_suite.ts` split into 4 per-domain files + thin orchestrator. Time-duration magic numbers centralized in `src/lib/constants.ts`. 30 inline `crypto.randomUUID()` calls replaced with `generateId()`. Shared MCP test helper factory extracted to `tests/helpers/mcp.ts`. REF-01–06 closed. | ---

v0.13.0

**Platform Test Audit & Hardening — Phases 37–46.** Comprehensive test coverage build-out, CI enforcement, and test quality hardening. | Change | Detail | | :-------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Coverage infrastructure** | `@vitest/coverage-istanbul` configured with 80% line/branch/function/statement thresholds. Istanbul provider required for `@cloudflare/vitest-pool-workers` (V8 raises `ERR_METHOD_NOT_IMPLEMENTED`). Dead code (`validate-secrets.ts`) removed to prevent metric inflation. | | **Test infrastructure hardening** | Durable Object shards reset inside `clearKvForKey()` fixture to prevent rate-limiter state bleed between tests. Per-test `fetchMock.activate()`/`deactivate()` lifecycle enforced. Contract test added for the fetch-mock helper. | | **Guard net pipeline tests** | Exact-boundary clock-skew tests at `±TIMEOUTS.CLOCK_SKEW_WINDOW_SEC` (300 s). DNS rebinding and cross-session HMAC rejection confirmed. Entropy DO shard (`rl:ent:{keyId}`) reset between tests to prevent spurious auto-pause carry-over. | | **Billing & webhook tests** | HTTP-layer integration tests for `checkout.session.completed`, `payment_intent.succeeded` Auto-Topup, idempotency guard (duplicate `event_id`), unattributable charge handling, and Stripe signature replay guard. | | **Admin API & console tests** | Audit log entry coverage for all mutating admin actions. OAuth single-use enforcement test (CONS-01). Admin session expiry test (ADMIN-02). | | **Test quality sweep** | `@security` tags added to all security-relevant tests across 7 files. `assertNoPendingInterceptors()` drain hygiene enforced after every test. `check-security-tags` CI job added — blocks untagged security tests from merging. | | **TEST-STRATEGY.md** | Authoritative test strategy document covering tagging conventions, coverage gates, isolation contracts, and the security-test enforcement perimeter. | | **CI infrastructure** | ESLint `no-unused-vars` enabled with `argsIgnorePattern: '^_'` for Hono handler params. `lint-typecheck` CI job green across all route and lib files. | ---

v0.11.0

**Code Quality & Pre-Launch Hygiene — Phases 23–30.** Security hardening, code review, Nyquist validation, and Durable Object migration for the rate limiter. | Change | Detail | | :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Pre-launch audit fixes** | Addressed all MUST-FIX and SHOULD-FIX items from the pre-launch audit: DNS error message sanitization (MUST-03), Stripe sentinel validation tightening (MUST-04), stale test file rename (`angus_up.test.ts` → `agent_guard_up.test.ts`), `dcoapp/app@latest` pin (SHOULD-07). | | **ReplayCacheDO implementation** | Implemented `ReplayCacheDO` Durable Object class (`src/durable-objects/replay-cache.ts`) with atomic `claim()` RPC. Class is bound in `wrangler.toml` and ready for gateway integration. KV-backed replay detection remains active in `02-hmac.ts` pending cutover. | | **RateLimiterDO migration** | Migrated all rate-limiting paths from KV sliding-window (TOCTOU-01) to `RateLimiterDO` Durable Object fixed-window counters. Affects per-key quota (`04-rate-limit.ts`), per-IP MCP gate (`index.ts:314`), per-IP admin MCP gate (`index.ts:386`), `audit_sess` rate gate, and `autoPauseOnRepeatedViolation`. Closes the KV TOCTOU race documented since v0.7.3. `orchestrateKeyResume` updated to clear DO shards on key resume. | | **AdminGuardDO stub** | Registered `AdminGuardDO` Durable Object binding in `wrangler.toml` and `types.ts`. Implementation deferred — `admin-auth.ts` brute-force counters remain KV-backed with compensating controls (entropy gate, IP allowlist). | | **Version, copyright, SDK rename** | Unified version to `0.11.0` across `package.json`, `wrangler.toml`, `worker-configuration.d.ts`, and `admin-spa/package.json`. Copyright updated from "Angus-B2A Contributors" to "Sphyr". SDK files renamed from `sphyr_utils.*` to `agent_guard_utils.*` and all classes renamed to `AgentGuardClient`, `AsyncAgentGuardClient`, `MockAgentGuardClient`, `AgentGuardRequestsAdapter`, `AgentGuardHTTPXTransport`, `AgentGuardSyncHTTPXTransport`. | | **Documentation accuracy audit** | Corrected false DO migration claim in v0.8.0 CHANGELOG entry. Added missing v0.10.0 and v0.11.0 CHANGELOG entries. Fixed OPERATIONS.md brute-force limit (3→5 for API/Bearer channel). Updated ROADMAP.md DO status. Fixed SECURITY.md TOCTOU status (rate limiter resolved, replay cache and admin auth remain). Updated AGENTS.md admin tool list. Updated PERFORMANCE.md rate limiting I/O description. Fixed TODO.md stale items. | ---

v0.10.0

**Performance & Accuracy — Phases 13–22.** Migrated to a pure single-wallet credit model, audited and optimized the hot request path, and established SDK latency baselines. | Change | Detail | | :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Stripe Meter Events migration** | Replaced legacy `usage_records` API with Stripe Meter Events API. Removed subscription/metered billing remnants. | | **Pure Service Credit Architecture (Phase 14)** | Consolidated to a single-wallet credit model. Dropped `stripe_subscription_id` from `users` table (migration `0008`). Removed all tier management functions (`getUserTier`, `setUserTier`). Removed `STRIPE_PRO_PRICE_ID` and `STRIPE_OVERAGE_PRICE_ID`. Cleaned `audit_sess` and `user_bal` of multi-tier logic. Rearchitected `/pricing` and `/features` pages to 2-tier "Free" / "Pay-As-You-Go" marketing layout. | | **Marketing pages** | Built out `getsphyr.com` pages: `/pricing`, `/features`, `/compliance`, `/legal` with server-rendered HTML, CSP-compliant inline styles, and GDPR/AUP content. | | **Pipeline benchmarking** | Established phase-by-phase latency budget. Total gateway tax: ~40–95ms (happy path). Documented I/O ordering rationale in `docs/PERFORMANCE.md`. | | **D1/KV query efficiency audit** | Added `rows_read` regression gates to critical D1 queries. Added D1KV-04 KV write failure mode test for `checkRateLimit`. Introduced `getSystemConfigMulti()` batch helper to reduce D1 round-trips. | | **guard_net optimization** | Wrapped rate limiter KV put in fail-open try/catch (FINDING-04). Introduced `getSystemConfigMulti` for batch config reads. | | **Security detection accuracy** | Shannon entropy scanner improvements: tightened thresholds, extended JWT recursion depth. Billing accuracy fixes: response body byte counting via `TextEncoder` (non-ASCII fix), `checkAndDeductKeySpend` return value now gating response scan spend. | | **Admin SPA performance** | Bundle optimization and lazy-loading for the React admin dashboard. | | **SDK latency baseline** | Established JS SDK p50/p95 latency benchmarks via Vitest (`tests/sdk/`). Regression gates added. | ---

v0.9.0

**Pure Service Credit Architecture (Phases 13 & 14)** - Transitioning away from legacy subscriptions to a strict, floor-guarded service credit model with atomic Auto-Topup. - **Database Schema:** Dropped `stripe_subscription_id` from the `users` table via migration `0008`. - **Code Purge:** Completely removed legacy tier management functions (`getUserTier`, `setUserTier`), subscription mode in checkout, and all associated overage configurations (`STRIPE_PRO_PRICE_ID`, `STRIPE_OVERAGE_PRICE_ID`). - **MCP Tooling:** Cleaned `audit_sess` and `user_bal` by purging all multi-tier logic, enforcing a pure credit balance response. - **Frontend / UX:** Rearchitected `/pricing` and `/features` into a 2-tier "Free" and "Pay-As-You-Go" marketing layout, removing references to dual-wallet setups. ---

v0.8.0

**Gateway Atomicity, Admin Hardening, and Pre-launch Stabilization.** A massive 280+ commit milestone resolving remaining TOCTOU structural vulnerabilities and securing the administrative plane. | Change | Detail | | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------ | | **Admin Rate Limiting Channel Isolation** | Hardened the `/admin` rate limiting implementation by cordoning off automated `cron` triggers into their own isolated bucket (channel parameter: `dashboard` | `mcp` | `cron`), preventing GitHub Actions bursts from consuming the per-IP failure budget and locking operators out of the dashboard. | | **Identity Firewall Finalization** | Handled all remaining pre-launch readiness audit tasks, optimizing the system for fully autonomous agent workflows, strict error bubbling, and deterministic control. | | **Project Cleanup** | Aggressive removal of obsolete configuration (`.windsurf`), environment consolidation, and typescript compilation/bridge fixes ensuring a pristine zero-emission (`tsc --noEmit`) state. | ---

v0.7.8

Shannon Pentest Hardening — Lane A (A0–A9). Security-posture hardening pass from the Shannon pentest report. No schema changes. Wire change: error envelope normalized to `{code, message, request_id?}` (inline `{error:}` objects removed and grep-gated). **Totals: 468 automated tests across 39 files.** | Change | Detail | | :---------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **A0: crypto.ts working-tree restore** | `src/lib/crypto.ts` was inadvertently modified by a stray editor save during the Shannon review session. File restored to last-committed state; RCA documented in commit notes to prevent recurrence. | | **A1: `/v1/checkout/*` middleware gap** | The checkout route family was reachable without IP-allowlist or key-entropy checks. Both `adminIpAllowlistMiddleware` and `adminKeyEntropyMiddleware` are now applied to `/v1/checkout/*`, matching the protection level of `/admin/*`. | | **A2: `errorResponse()` canonical envelope** | New `src/lib/errors.ts` exports `errorResponse(code, message, requestId?)`. `app.onError()` registered as global error handler. 25+ inline `{error: ...}` response objects migrated to `errorResponse()`. A grep gate in CI now blocks any new `{error:` envelope from being introduced. | | **A3: Stripe signature header parse hardening** | `verifyStripeSignature` wrapped the raw `Stripe-Signature` header parsing in a try/catch. Malformed headers (truncated, non-UTF-8) now return `false` instead of throwing an unhandled exception. | | **A4: Zod schemas on HTTP routes** | Input validation added to HTTP route handlers. `checkout` `user_id` parameter capped at 256 characters with Zod; other HTTP-facing params validated at route entry. | | **A5: Stripe event runtime validation** | Stripe webhook events are now validated against a Zod schema post-signature-verification, pre-handler dispatch. Malformed event payloads that pass signature checks are now rejected before reaching handler logic. | | **A6: Global security headers middleware** | New `src/lib/security-headers.ts` registers a Hono middleware that sets on every response: `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload`, `Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()`, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: same-origin`, `Content-Security-Policy: default-src 'none'`. Route-specific CSPs (admin dashboard, legal pages) are preserved via a header-presence check — the default CSP is only written if no CSP is already set. | | **A7: Admin cookie IP binding** | `checkAdminSession(stored, ip)` in `src/lib/admin-auth.ts` validates admin cookie sessions against the caller's IP. Sessions are stored as `{"ip": "<ip>"}` JSON. Legacy bare `'valid'` cookie strings are rejected; operators must re-authenticate after upgrade. | | **A8: Cold-start guard consolidation** | `runColdStartChecks(c, format)` in `src/index.ts` is now the single call site for all cold-start validation (config + salt integrity). `/admin/mcp` previously skipped `verifySaltIntegrity`; this drift bug is fixed — both `/mcp` and `/admin/mcp` now run the full check via the shared helper. | | **A9: `getClientIp()` canonical helper** | New `src/lib/client-ip.ts` exports `getClientIp(c)`. Returns `string \| null` — never the string `'unknown'`. 12+ call sites across the codebase migrated; all null-path callers updated to handle `null` explicitly. | ---

v0.7.7

MVP Launch Hardening — production config validation, Stripe charge attribution, session eviction safety, D1 migration runner, and full test coverage for the admin suite. Schema change: new `pending_overage` table. No wire protocol changes. **Totals: 434 automated tests across 34 files.** | Change | Detail | | :----------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Config validation module (CFG-01..CFG-05)** | New `src/lib/validate-config.ts` checks 5 required runtime items at cold-start: `honeytoken_domain`, `ADMIN_KEY_ID` entropy (≥32 bytes), `LOG_SALT`, `HMAC_SECRET`, and `STRIPE_RESTRICTED_KEY`. Missing or sentinel-value items emit a structured error and halt request processing. `wrangler.toml` now ships with operator-readable sentinel placeholders for all required vars. | | **Honeytoken cold-start guard** | `src/index.ts` calls `validateConfig()` before accepting any traffic. `CONFIG_MISSING` is returned on every request until all required values are set. Pre-launch operator provisioning checklist added to `OPERATIONS.md`. | | **`resolveCustomerFromCharge` (BILL-01)** | Charge refunds and disputes previously had no reliable way to map a Stripe charge to a Sphyr user. `resolveCustomerFromCharge` now calls the live Stripe API (`/v1/charges/:id`) and extracts the `customer` ID for lookup. Affects `charge.refunded` and `charge.dispute.created` webhook handlers. | | **Session eviction SAFE_FAILURE (REL-01)** | The 4-step KV eviction sequence in `audit_sess.ts` can leave a ghost session window on partial failure. The failure path now emits a structured `SAFE_FAILURE`-labelled log entry with the partial-eviction state. `OPERATIONS.md` runbook documents the ghost session lifecycle and explains why the window is non-exploitable. | | **D1 migration runner (REL-02)** | New `scripts/migrate.ts`: TypeScript migration runner with SHA-256 checksums per migration file, baseline auto-seeding from `schema.sql`, dry-run mode (`--dry-run`), and an interactive confirmation gate for production runs (`--env production`). Prevents silent re-runs of already-applied migrations. | | **Admin suite test coverage (TEST-01)** | 41 tests for all 11 admin MCP tools: `admin_get_customer_context`, `admin_recommend_action`, `admin_resume_key`, `admin_list_paused`, `admin_stats`, `admin_ban_domain`, `admin_unban_domain`, `admin_reconcile_ledger`, `admin_check_stale_events`, `admin_set_response_limit`, `admin_run_job`. | | **Preflight no-proxy/no-credit tests (TEST-02)** | Integration tests confirm `preflight: true` runs all validation checks but skips upstream fetch and credit deduction. Covers PREFLIGHT_OK, HMAC failure, SSRF block, and entropy block paths. | | **GDPR scrub endpoint tests (TEST-03)** | Tests covering `DELETE /gdpr/scrub`: 401 on missing auth, 200 + scrub confirmation on valid user, 404 on unknown user. | | **Admin dashboard route tests (TEST-04)** | Tests covering `/admin` dashboard: 401 on missing/invalid `ADMIN_KEY_ID`, 200 + HTML response on valid auth. | | **Constants renamed from dollars to credits** | `SIGNUP_BONUS` → `SIGNUP_BONUS_CREDITS = 1_000`; `PRO_MONTHLY_CREDIT` → `PRO_MONTHLY_CREDITS = 50_000`. Callers now multiply by `CREDIT_BASE` to get the dollar value passed to `adjustCredits`, making the unit of each constant unambiguous. `PRO_OVERAGE_CAP_CREDITS = 100_000` added (~$10 overage cap). | | **Overage recovery path** | New `pending_overage` table buffers overage records that fail to reach Stripe. `reportOverageUsage` now accepts an optional `pendingId` and marks the row reported on success. `10-credits.ts` and `12-response.ts` write to `pending_overage` before calling Stripe. New `runOverageReconciliation` cron (every 5 minutes) retries unreported rows older than 5 minutes. | | **Negative balance cap** | When a Pro user's overage exceeds `PRO_OVERAGE_CAP_CREDITS * CREDIT_BASE` ($10), the offending key is auto-paused via existing `pauseKey` infra. Overage is still reported to Stripe even when pausing — the credit was already consumed. | | **Downgrade balance clamp** | `handleSubscriptionDeleted` and the `canceled/unpaid` branch of `handleSubscriptionUpserted` now clamp `credit_balances.balance` to `MAX(balance, 0)` after downgrading to free tier. Overage was already billed on the final invoice; carrying a negative balance into free tier would permanently block the user. | | **`setUserTier` DRY fix** | Extended to accept optional `meteredItemId`. When `tier='free'`, always NULLs both `stripe_subscription_id` and `stripe_metered_item_id`. `handleSubscriptionUpserted` now calls `setUserTier` instead of inline SQL, eliminating the duplicate tier-change code path. | | **Null metered item warning** | `handleSubscriptionUpserted` now emits a `console.warn` when `STRIPE_OVERAGE_PRICE_ID` is not found among the subscription items. Without this warning, misconfigured subscriptions silently disable overage billing. | | **Stripe config note** | `wrangler.toml` `STRIPE_OVERAGE_PRICE_ID` comments now document the required Stripe Dashboard settings: `aggregate_usage=sum`, `billing_scheme=per_unit`. | | **Ledger write comment** | `10-credits.ts` now includes a comment at the `insertCreditLedger` waitUntil call explaining why the non-atomic write is an accepted risk (source of truth is `credit_balances`; drift is detectable by the `runLedgerReconciliation` cron). | | **Test updates** | `db.test.ts` derives the signup bonus assertion from `SIGNUP_BONUS_CREDITS * CREDIT_BASE` so it stays correct if the constants change. `webhook.test.ts` adds a derivation comment for the `100_000` microcent literal. | | **Cron jobs migrated to GitHub Actions** | Cloudflare Workers free plan enforces a 5-cron-trigger account limit (dev + production together consumed the quota). All scheduled work now runs via GitHub Actions (`.github/workflows/crons.yml`): `*/5 * * * *` triggers shadow-scan, stale-stripe, and overage-recon; `0 0 * * 1` triggers weekly-hardening. A new authenticated `POST /admin/run-job` endpoint dispatches each job by name. The `scheduled()` export is kept in `src/index.ts` for emergency use with `wrangler dev --test-scheduled`. Requires two GitHub secrets: `WORKER_URL` and `ADMIN_KEY_ID`. | | **SDK class rename (rebrand)** | All SDK classes renamed from `Sphyr`-prefixed to `AgentGuard`-prefixed as part of the Angus → Sphyr Agent Guard rebrand: `SphyrClient` → `AgentGuardClient`, `AsyncSphyrClient` → `AsyncAgentGuardClient`, `MockSphyrClient` → `MockAgentGuardClient` (JS and Python), `SphyrRequestsAdapter` → `AgentGuardRequestsAdapter`, `SphyrHTTPXTransport` → `AgentGuardHTTPXTransport`, `SphyrSyncHTTPXTransport` → `AgentGuardSyncHTTPXTransport`. CLI package renamed from `@sphyr/mcp-cli` to `@sphyr/agent-guard-cli`; env var renamed from `SPHYR_MCP_URL` to `AGENT_GUARD_MCP_URL`. The v0.6.0 changelog entries describe the classes as they shipped under their original names. | ---

v0.7.6

Stripe event coverage, test coverage expansion, and prelaunch documentation fixes. No schema changes, no wire protocol changes. | Change | Detail | | :------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`charge.refunded` webhook handler** | New handler in `src/routes/webhook.ts` reverses credits when a Stripe refund is processed. Uses `amount_refunded` (cumulative cents) converted via `centsToCreditUnits`. Records ledger entry with reason `refund`. If user has insufficient balance, marks the event as `failed` for admin reconciliation — does not block the refund acknowledgment to Stripe. | | **`charge.dispute.created` webhook handler** | New handler pauses the user account (`users.status → 'paused'`) when a chargeback/dispute is opened. Records the event and updates the `stripe_events` row with the resolved `user_id`. Conservative approach: account is paused pending admin review. Does not auto-deduct credits (Stripe handles the financial reversal). | | **Stripe types extended** | `StripeCharge` and `StripeDispute` interfaces added to `src/lib/stripe.ts` for the new webhook handlers. | | **`sphyr_up` test suite** | New `tests/mcp/sphyr_up.test.ts` — 5 tests covering valid session (ACTIVE), invalid session, IP mismatch, global pause (DEGRADED status + whitelist), and key pause (KEY_PAUSED). | | **`user_bal` test suite** | New `tests/mcp/user_bal.test.ts` — 7 tests covering balance retrieval, low balance warning threshold, zero balance, invalid session, IP mismatch, key pause, and global pause. | | **Webhook handler test suite** | New `tests/routes/webhook.test.ts` — 12 tests covering checkout credit flow, idempotency guard, user bootstrap on first payment, refund credit deduction, insufficient balance refund, dispute account flagging, Stripe signature verification (valid, expired, tampered, missing header), and `centsToCreditUnits` conversion. | | **Global pause/resume integration tests** | New `tests/mcp/global_pause_resume.test.ts` — 3 tests covering full pause→DEGRADED→resume→ACTIVE cycle and key-level pause blocking `sphyr_up`. | | **`SECURITY.md` placeholder removed** | Removed hardcoded `security@yourdomain.com` reference; now points only to the `SECURITY_EMAIL` environment variable. | ---

v0.7.5

Pipeline phase reorder, entropy auto-learning, logging hygiene, prelaunch audit fixes, and documentation updates. No schema changes, no wire protocol changes. SDK wire change: `usage_summary` now requires `ts` parameter (introduced in v0.7.2, SDKs now comply). | Change | Detail | | :------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **HMAC replay bypass via hex case variation (SECURITY)** | `phase 02-hmac.ts` and `usage_summary.ts` now normalize signatures to lowercase before constructing replay cache keys. Previously `abc123` and `ABC123` produced different cache keys but identical HMAC bytes, allowing replay bypass. `verifyHmac` regex tightened from `/^[0-9a-f]+$/i` to `/^[0-9a-f]+$/` — mixed-case signatures are now rejected outright. | | **SDK `usage_summary` signature fixed (BUG)** | JS and Python SDKs now include `ts` in the HMAC payload (`"usage_summary:{sess}:{ts}"`) and send it as a parameter, matching the server expectation introduced in v0.7.2. Previously both SDKs signed `"usage_summary:{sess}"` without `ts`, causing every call to fail with `SIG_INVALID`. | | **Version string consistency** | `wrangler.toml`, `README.md`, and `worker-configuration.d.ts` updated to `0.7.5` (were `0.7.1`/`0.7.2`). | | **Session validation DRY extraction** | `validateSession()` and `checkIpBinding()` extracted to `src/mcp/tools/shared.ts`. `sphyr_up.ts`, `user_bal.ts`, and `usage_summary.ts` refactored to use shared helpers (15-line block removed from each). | | **`suggested_bans` UPSERT accumulation fix (BUG)** | `hardening.ts` UPSERT now uses `block_count = suggested_bans.block_count + excluded.block_count` instead of overwriting with the new value. Previously, re-offending domains had their count reset instead of accumulated. | | **Response body credit billing accuracy** | `phase 12-response.ts` now uses `new TextEncoder().encode(responseText).length` (byte count) instead of `responseText.length` (UTF-16 code units). Non-ASCII responses were previously under-billed. | | **IPv6 private address normalization** | `isPrivateIp()` in `dns.ts` now expands IPv6 addresses via `expandIpv6()` before prefix matching. Malformed IPv6 fails closed. | | **`getSystemConfig()` negative cache** | Null D1 results are now cached with a `__NULL__` sentinel (60s TTL) to prevent repeated D1 lookups for missing config keys under load. | | **Legal pages HTML escaping** | All env var interpolations in `legal.ts` are now wrapped with `esc()` for defense-in-depth XSS prevention. | | **HTTPS rejection test fixed** | `guard_net.test.ts` HTTPS test now actually uses `http://` and asserts `HTTPS_REQUIRED` (was a no-op). | | **ReDoS benchmark timing assertion** | `entropy_bench.test.ts` now asserts `< 1000ms` on the ReDoS test (was log-only). | | **Broken `tools/` scripts removed** | `tools/simulate_steg.py` and `tools/remediate_sim.py` deleted — used pre-v0.5.0 signing format and stale secrets. | | **Optional `STRIPE_WEBHOOK_SECRET`** | `validate-secrets.ts` no longer enforces `STRIPE_WEBHOOK_SECRET` on application startup. This allows deploying the gateway before configuring a Stripe endpoint. The webhook route itself continues to assert the secret's presence on every incoming event. | | **Preview/production environment split** | `wrangler.toml` now has an `[env.production]` section with placeholder D1/KV bindings. `deploy` targets preview; `deploy:prod` targets production. | | **CI workflow** | `.github/workflows/e2e-chaos-suite.yml` replaced with a two-job workflow: `lint-typecheck` (lint + tsc + format:check) and `test` (vitest). Removed unused Toxiproxy setup. | | **`.gitignore` hygiene** | Added `__pycache__/`, `*.pyc`, `*.pyo`, `coverage/`, and `worker-configuration.d.ts`. Removed tracked `__pycache__` files. | | **Auto-learn entropy FP aggregation (weekly cron)** | `src/jobs/hardening.ts` now aggregates `DRY_RUN_BLOCKED` entropy hits by prefix and upserts them into `suggested_allows` (Phase 5). This automatically learns and flags common safe structural patterns for admin review, drastically reducing false positives. | | **Entropy logging prefix** | `src/lib/entropy.ts` now includes `tokenPrefix` (first 16 chars) in violation logs to support the new auto-learn aggregation feature. | | **Inbound safety allowlisting** | `src/lib/inbound-safety.ts` `scanInboundBody` now supports an `allowlistPrefixes` array, allowing the gateway to ignore known false-positive structural patterns. | | **Logging hygiene (ID slicing)** | Internal record IDs (`key_id`, `event_id`) in `guard_net` and `hardening.ts` console logs are now sliced to their first 12 characters. Prevents sensitive identifier exposure in standard platform logs. | | **Honeytoken phase moved before DNS pinning** | `pipeline.ts` now runs `phaseHoneytoken` before `phaseDnsPinning`. Canary/decoy domains are intentionally NXDOMAIN — checking them before DNS avoids a spurious DNS resolution failure on honeytoken URLs. | | **Honeytoken test DNS mocks removed** | Four `guard_net` honeytoken tests no longer register `mockDoh()` interceptors. With the new phase order, DNS pinning is never reached when honeytoken triggers, so the unconsumed interceptors caused `assertNoPendingInterceptors` failures in `afterEach`. | | **Retention documentation accuracy** | Updated `README.md` to explicitly state that the weekly execution of the PII purge cron introduces up to 6 days of lag on the 30-day retention window (effective window up to 36 days). | ---

v0.7.4

Financial and billing integrity fixes. No schema changes, no wire protocol changes. | Change | Detail | | :--------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Response scan spend-cap bypass fixed (BUG)** | `phase 12-response.ts` now captures the return value of `checkAndDeductKeySpend`. Previously the return value was discarded, allowing response scan costs to bypass a key's per-key spend cap and charge the owner pool unchecked. The fix gates the `adjustCredits` / `insertCreditLedger` calls behind `responseSpendAllowed`; when the cap is exceeded the response scan cost is zeroed and the `credits_used` log entry reflects 0 for the response portion. | | **Ledger reconciliation (weekly cron)** | `runLedgerReconciliation()` added to `src/jobs/hardening.ts`. Compares `credit_balances.balance` against `SUM(credit_ledger.delta)` per user and dispatches a `LEDGER_DRIFT` alert via the existing webhook channel for any discrepancy > 0.0001 credits. Runs as Phase 4 of `runWeeklyHardening` (after domain auto-expiry, before report assembly). Returns a typed `ReconciliationReport`. | | **Stale Stripe event detection (5-min cron)** | `runStaleStripeEventCheck()` added to `src/jobs/hardening.ts`. Queries for `stripe_events` rows stuck in `pending` status for > 10 minutes — the signature of a crash between `adjustCredits` and `updateStripeEventStatus` — and dispatches a `STALE_STRIPE_EVENTS` alert. Wired into the 5-minute scheduled cron in `src/index.ts` alongside `runShadowTokenScan`. | | **`admin_reconcile_ledger` tool** | New read-only MCP admin tool. Calls `runLedgerReconciliation` on demand and returns the full drift report. No auto-correction applied. | | **`admin_check_stale_events` tool** | New read-only MCP admin tool. Calls `runStaleStripeEventCheck` on demand and returns the stale event list for manual investigation. | | **`SecurityAlertEvent` extended** | `LEDGER_DRIFT` and `STALE_STRIPE_EVENTS` added to the `SecurityAlertEvent` union type in `src/lib/alert.ts`. | ---

v0.7.3

Infrastructure & resource limit audit. No schema changes, no wire protocol changes. | Change | Detail | | :---------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`usage_model = "standard"` in wrangler.toml** | Explicit CPU billing model declared. Removes ambiguity about the per-invocation CPU ceiling (Bundled = 10ms hard limit; Standard = metered, no ceiling). Ensures the entropy scanner and HMAC key imports cannot trigger a 500 on Bundled plan limits on max-size payloads. | | **CryptoKey module-level cache** | `crypto.ts` now caches `CryptoKey` objects in a module-level `Map<string, CryptoKey>`. `importKey()` runs at most once per `(secret, usage)` pair per isolate lifetime. Saves ~1-4ms CPU on warm isolates (2-3 redundant imports eliminated per `guard_net` request). | | **Entropy single-pass optimization** | `entropy.ts` shadow-zone accumulation merged into the sliding-window loop, eliminating the second full token iteration. For tokens shorter than the window size, `shannonEntropy()` is now called once and reused for both the per-token check and shadow accumulation (previously called twice). Saves ~3-10ms CPU on max-size payloads. | | **Admin global counter TOCTOU documented** | `admin-auth.ts` global fail counter race acknowledged with `@security` block documenting why it is non-exploitable (entropy gate, IP allowlist, per-IP counter) and that Durable Objects is the correct atomic fix (ROADMAP Phase 2). | | **Sess-Bust concurrent race documented** | `audit_sess.ts` 4-step KV eviction sequence race documented with `@security` block covering the ghost session window, Phase 03 coherence-check mitigation, TTL expiry, and DO fix path. | | **PERFORMANCE.md updated** | CryptoKey caching section updated from "evaluated, not implemented" to "implemented (v0.7.3)". New Existing Optimizations entries §6 (CryptoKey cache) and §7 (single-pass entropy scanner). | ---

v0.7.2

Security audit remediation (round 2). No schema changes — all behavioral changes are backward-compatible within the existing wire protocol except `usage_summary`, which now requires a `ts` parameter. | Change | Detail | | :------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`usage_summary` replay protection** | `ts` (Unix seconds) is now a required parameter. The signature payload changes from `"usage_summary:<sess_token>"` to `"usage_summary:<sess_token>:<ts>"`. Signatures are rejected outside a ±5-minute clock-skew window and cached in KV (`replay_usage:{sig}`, 600s TTL) to prevent verbatim replay. Response includes crypto jitter (50–200ms) to harden timing analysis. | | **HMAC secret entropy gate** | `validate-secrets.ts` now rejects startup if `HMAC_SECRET` encodes to fewer than 32 bytes. Failure is logged and `valid: false` is returned before any request is handled. | | **Honeytoken subdomain bypass fixed** | Phase 9 now checks `hostname.endsWith('.' + honeyDomain)` in addition to exact match, closing a bypass via arbitrary subdomains of the canary domain. | | **SSRF IPv6 bracket notation** | `SSRF_HOSTNAME_RE` patterns updated to match both bare (`::1`) and WHATWG bracket form (`[::1]`) for loopback, unique-local (`fc00::/7`), and link-local (`fe80::/10`) addresses. | | **Unicode normalization extended** | Inbound-safety scrub regex extended to cover 30+ additional invisible, deprecated, and format Unicode code points (U+2061–U+2064, U+206A–U+206F, U+2800, U+3164, U+FFA0, etc.). | | **`emergency_stop` user_id preservation** | Pause path now resolves owner via KV→D1→null cascade (`key_sess:{keyId}` → `sess:{token}` → `user_id`, with D1 `request_logs` fallback) before calling `pauseKey`. `paused_keys.user_id` is `null` only for dormant keys with no resolvable owner (SQLite allows NULL in FK columns). | | **`emergency_stop` PARTIAL_RESUME surfaced** | Resume path now checks `orchestrateKeyResume` result. If the Unkey re-enable call fails, returns `{ status: "PARTIAL_RESUME", code: "UNKEY_SYNC_FAILED", isError: true }` with operator instructions. Previously this failure was silent. | | **Unkey API response validation** | `verifyUnkeyKey` now validates the raw Unkey API response against a Zod schema before use. Malformed or unexpected responses return `{ valid: false }` rather than propagating undefined field access. | | **DNS cache key privacy** | `resolveViaDoH` now accepts `logSalt` and stores DNS results under `dns:{saltedHash(hostname)}` instead of `dns:{hostname}`. Prevents KV key enumeration from leaking plaintext hostnames. | | **`audit_sess` cache key IP leak fixed** | `unkey_v:` cache key no longer includes `:{ip}` suffix. The IP was redundant (session binding enforces IP coherence) and caused cache misses on IP rotation, leaking IP change patterns into KV key space. | | **Shadow token scan threshold** | Hardening job threshold tightened from `COUNT(*) > 2` to `COUNT(*) >= 1` — a single duplicate session token is now flagged. | ---

v0.7.1

Multi-product architecture preparation. No behavioral changes to the gateway — schema and SDK only. | Change | Detail | | :-------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`request_logs.product` column** | New `TEXT NOT NULL DEFAULT 'gateway'` column enables unified telemetry across future products. `guard_net` writeLog now explicitly passes `product: 'gateway'`. All existing rows default to `'gateway'`. | | **`key_flags.products_enabled` column** | New `TEXT NOT NULL DEFAULT 'gateway'` column for per-key product entitlements (comma-separated). `getKeyFlags()` returns it; `audit_sess` caches it in the KV session. Not enforced yet — all current keys have `gateway` access. | | **`admin_stats` product filter** | Optional `product` parameter filters the 24-hour stats query by product. Omit for aggregate across all products. | | **SDK namespaced exports** | JS: `sphyr.gateway.Client`, `sphyr.gateway.MockClient`, `sphyr.gateway.createFetchAdapter`. Python: `gateway.Client`, `gateway.AsyncClient`, `gateway.MockClient`, `gateway.AsyncMockClient`, `gateway.RequestsAdapter`, `gateway.SyncHTTPXTransport`, `gateway.HTTPXTransport`. Flat exports remain as aliases. | | **ROADMAP Phase 5** | Documented platform expansion strategy: Trace → Box → State, gated on gateway PMF. | | **AGENTS.md** | Added multi-product architecture notes for contributors. | ---

v0.7.0

Final pre-launch security and operational readiness audit remediation. | Change | Detail | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`audit_sess` Hardening** | Added strong Zod schema validation to `audit_sess` input and strictly separated parameter mapping from core logic. Ensures all incoming telemetry bindings (IP, context) are validated. | | **D1 Schema Alignment** | Clarified D1's role strictly as an aggregation and telemetry store. Dropped redundant identity columns (`owner_id`, `name`) previously mirrored from Unkey, reinforcing Zero-Trust where Unkey remains the single source of truth. | | **KV Parameter Unification** | Moved the `honeytoken` domain parameter entirely from D1 to KV `system_config`. Speeds up handshake by eliminating a D1 latency hop and unifies all security parameters into sub-5ms KV reads. | | **Admin Tools Refactoring** | Removed `admin_key` parameter logic from internal admin suite schema definitions as authorization is explicitly handled beforehand at the `/admin/mcp` routing boundary. Fixed duplicate evaluations. | | **IPv6 Admin Allowlist** | Upgraded `src/lib/ip-allowlist.ts` to fully support IPv6 CIDR parsing and prefix matching for `ADMIN_ALLOWED_IPS`, securing the administrative routing layer. | | **Inbound Payload Safety** | Added NFKC Unicode normalization to prompt injection defenses to thwart visually identical character bypasses and bounded regex scrubbing via IPv6 parser simplifications to prevent ReDoS. | | **Stripe Double-D1 Protection** | Validated `recordStripeEvent` deduplication via `INSERT OR IGNORE` transactions for Stripe webhooks to ensure idempotent billing even across concurrent process panics. | | **System Resiliency** | Implemented `parseInt` NaN fallbacks for configurable timers, wrapped DNS KV latency writes in fault-tolerant catch blocks, and added 1MB memory constraints to PII scrubbing utilities. | | **Test Harness Hardening** | Fixed cascade failure in `fetchMock.assertNoPendingInterceptors` — now drains pending queue before throwing (nock/MSW/undici pattern). Added `unkey_v:` and `key_sess:` to `clearKvForKey` in test fixtures to eliminate auth cache and session-index bleed between tests. Refactored `audit_sess.test.ts` to explicit per-test mock registration, ensuring hermetic test isolation. | | **Type System Integrity** | Restored strict `Cloudflare.Env` bindings across the workspace by linking `worker-configuration.d.ts` in TSConfig and extracting runtime bindings into a discrete `src/env-runtime-secrets.d.ts` declaration merge. Removed deprecated `cloudflare:test` bridge patterns. | | **Log Rationale Sanitization** | Upgraded `guard_net` `writeLog` to use `sanitizeRationale()` instead of `scrubPii()` for the rationale field. Adds control-character stripping, CSV/log-formula injection prevention, and a 200-char hard cap. Category field (`cat`) is unchanged — Zod's `^[a-zA-Z0-9\-_. ]+$` regex pre-blocks injection at schema validation. |

v0.6.0

Developer ergonomics and agent experience (AX) hardening. | Change | Detail | | :---------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Semantic error messages** | All `guard_net` failure paths now return a structured `remediation` object with explicit LLM-friendly `description` strings following a "Context / Instruction" format. Agents can read why a call failed and exactly what to do next without hallucinating retry strategies. | | **New ActionCodes** | `FIX_URL` (malformed URL, wrong scheme, non-ASCII path), `FIX_HEADERS` (non-ASCII keys, null-byte values, case-collision duplicates), `ABORT` (non-retryable policy blocks: SSRF, banned domain, oversized inbound body, prompt injection), `ABORT_TASK` (critical security events requiring task halt: honeytoken, key suspended, budget exhausted). | | **`preflight` flag on `guard_net`** | New per-request `preflight: boolean` parameter runs the full validation pipeline (HMAC, session, URL, SSRF, entropy, DNS, honeytoken) without executing the upstream fetch or deducting credits. Returns `{status: "PREFLIGHT_OK", simulated_cost}` on success. Distinct from the key-level `dry_run` audit mode. | | **`PREFLIGHT_OK` outcome** | Added to `RequestOutcome` type in `src/types.ts`. Logged with zero credits. | | **JS `createFetchAdapter(client)`** | Returns a `fetch`-compatible function that routes all calls through `guardNet()`. Drop-in for OpenAI SDK, LangChain, and any library accepting a custom `fetch`. | | **JS `MockSphyrClient`** | Test double overriding `_callTool()` — exercises signing and session-management paths in CI with no network I/O. Returns deterministic fixtures. | | **Python `SphyrRequestsAdapter`** | `requests.adapters.BaseAdapter` subclass. Mount on a `requests.Session` to proxy all calls through `guard_net`. | | **Python `SphyrHTTPXTransport`** | `httpx.AsyncBaseTransport` subclass for `httpx.AsyncClient`. | | **Python `SphyrSyncHTTPXTransport`** | `httpx.BaseTransport` subclass for `httpx.Client` (sync). Completes the httpx surface. | | **Python `MockSphyrClient` / `AsyncMockSphyrClient`** | Test doubles overriding `_call_tool()`. Shared `_MOCK_FIXTURES` dict ensures sync and async return identical data. | | **MCP CLI Bridge (`packages/cli`)** | New `@sphyr/mcp-cli` package. STDIO↔HTTP bridge for Claude Desktop integration — forwards JSON-RPC from stdin to the Worker `/mcp` endpoint. Configure via `SPHYR_MCP_URL` env var. | | **npm workspaces** | `package.json` now declares `"workspaces": ["packages/*"]`, linking `packages/cli` for local development and publishing. | | **Admin temporal telemetry** | `trace_id` and `sess_token_hash` added to the Forensic Feed SQL queries and HTML table. Operators can now scan down the `sess_hash` column to reconstruct agent "chains of thought" chronologically. | ---

v0.5.5

Security hardening: header integrity and SSRF normalization verification. | Change | Detail | | :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Dangerous proxy headers stripped** | `guard_net.ts` now strips `X-HTTP-Method-Override`, `X-Forwarded-Host`, `X-Original-URL`, and 7 other proxy override headers before forwarding to upstream. Previously only hop-by-hop and `CF-*` headers were blocked — an attacker could smuggle a `DELETE` via `X-HTTP-Method-Override` while the audit trail recorded a signed `POST`. New `DANGEROUS_PROXY_HEADERS` set added alongside `HOP_BY_HOP_HEADERS`. | | **SSRF normalization verification tests** | Added tests confirming WHATWG URL parser normalizes octal (`0177.0.0.1`), hex (`0x7f000001`), and IPv6-mapped (`::ffff:127.0.0.1`, `::ffff:a00:1`) IP encodings before they reach the `SSRF_HOSTNAME_RE` pre-filter. These are verification tests — no new runtime logic was needed. | ---

v0.5.4

Pre-MVP cleanup: remove misleading `risk_score` stub from `audit_sess` response and add admin IP allowlist. | Change | Detail | | :-------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`risk_score` removed from `audit_sess` response** | The session-level `risk_score` field was always `0` (placeholder for Phase 2 IP/geo/velocity scoring). Exposing an always-zero value in the public API contract erodes consumer trust. The field is retained internally in `SessionData` for future use. `README.md` response example updated. Regression assertion added to `tests/mcp/audit_sess.test.ts`. | | **Admin IP allowlist** | New `src/lib/ip-allowlist.ts` module (`ipToNum`, `parseCIDR`, `isIpAllowed`, `loadAllowlist`) restricts `/admin/*` and `/v1/users/*` to operator-configured IPv4 addresses or CIDR ranges. Configuration is stored in `system_config` (`admin_allowed_ips`) and managed via a new "IP Allowlist" card in the `/admin` dashboard (`POST /admin/ip-allowlist`). Empty allowlist = open access (backwards compatible). Recovery path: set `ADMIN_ALLOWED_IPS` env var via `wrangler secret put` — always takes effect without dashboard access, preventing self-lockout. IPv6 not supported for MVP; IPv6 clients are denied when an allowlist is active. | | **`ADMIN_ALLOWED_IPS` env var** | Added to `Env` interface in `src/types.ts`. Documented in `wrangler.toml` comments as the break-glass recovery mechanism. | | **Schema seed** | Added `('admin_allowed_ips', '', ...)` to `schema.sql` seed block. | | **Middleware ordering** | IP allowlist check runs before `adminKeyEntropyMiddleware` — fail fast at the network layer before any credential processing. | | **Tests** | 20 new tests in `tests/lib/ip-allowlist.test.ts` covering all pure functions and D1 loading. `tests/helpers/db.ts` `cleanData()` resets `admin_allowed_ips` to `''` between tests. | ---

v0.5.3

Post-audit corrections: auth bug fix, schema seeding, and documentation accuracy. | Change | Detail | | :---------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`emergency_stop` auth bug fix** | `emergency_stop.ts` was returning `auth.response` on failed authentication — a property that does not exist on `AuthResult`. The tool silently returned `undefined` instead of an error. Fixed to use `authResultToMcpError(auth)`, matching the pattern used by all other admin MCP tools (`admin_suite.ts`). | | **`auto_pause_shadow_sessions` schema seeding** | The v0.5.2 CHANGELOG claimed this default was seeded in `schema.sql`. It was not — only a code fallback existed. Added `('auto_pause_shadow_sessions', 'false', ...)` to the `INSERT OR IGNORE` block. Fresh deployments now get an explicit config row; existing deployments are unaffected. | | **OPERATIONS.md KV key prefix correction** | The KV Schema table listed `admin_stop_fail:{ip}` — the original prefix from before the v0.3.1 auth extraction. Corrected to `admin_fail:{ip}` and `admin_fail:__global__` to match the actual code in `admin-auth.ts`. | | **Admin lockout recovery documented** | Added "Admin Lockout Recovery" section to `OPERATIONS.md` with the break-glass `wrangler kv key delete` procedure for clearing the global admin fail counter during an active attack. | | **Broken comment fixed** | `guard_net.ts` line 18: `/ KV-backed` → `// KV-backed`. | ---

v0.5.2

Pre-v1 hardening: global admin brute-force limit, shadow session auto-pause, and ADMIN_KEY_ID entropy enforcement. | Change | Detail | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Global admin rate limiter** | `checkAdminAuth` now tracks total failed attempts across all IPs in `admin_stop_fail:__global__` (KV, 15-min TTL, threshold 10). An attacker rotating through N IPs previously got 3×N guesses; now the global budget caps total cross-IP attempts. Per-IP limit (3) still applies. On success, only the per-IP counter is cleared — the global counter expires via TTL to prevent a known-key reset. | | **Shadow session auto-pause** | `runShadowTokenScan` (5-min cron) now checks `auto_pause_shadow_sessions` in `system_config`. When `'true'`, each detected key is paused in D1+KV (`reason: shadow_session_detected`, `paused_by: system_cron`) immediately rather than waiting for admin response. Defaults to alert-only (`false`). Enable only after validating alert signal quality to avoid false positives from NAT/reconnection. | | **ADMIN_KEY_ID entropy gate** | `/admin/mcp` now validates `ADMIN_KEY_ID` byte length ≥ 32 on first cold-start request. Misconfigured deploys return HTTP 500 `CONFIG_ERROR` before any auth processing, preventing weak-key deployments from silently accepting traffic. | | **Production debug log removed** | Unconditional `console.log` in `inbound-safety.ts` (leaked body length and content-type on every request) removed. | ---

v0.5.1

Security hardening: honeytoken enforcement and safety metadata signing. | Change | Detail | | :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Honeytoken hard-fail** | `guard_net` now returns `CONFIG_MISSING` (fatal, non-self-healable) if `honeytoken_domain` is not set in `system_config`. Previously a `console.warn` + silent skip — code now matches the "required" language in `OPERATIONS.md` step 6. | | **Safety metadata signing** | `guard_net` responses include `safety_sig`: an HMAC-SHA256 of the `safety` object keyed with the session nonce. Agents can verify the signature using `SphyrClient.verify_safety_sig()` (Python) or `sphyr.verifySafetySig()` (JS). | | **`signHmac` exported** | `src/lib/crypto.ts` exports `signHmac` for use by `guard_net`. | | **SDK verification helpers** | `verify_safety_sig(safety, sig, nonce)` added to `SphyrClient` and `AsyncSphyrClient` in Python; `verifySafetySig(safety, sig, nonce)` added to `SphyrClient` in JavaScript. | | **Docs** | `AGENT_INTEGRATION.md` §4 (CONFIG_MISSING remediation), §6 (safety_sig verification); `README.md` (Operational Status table, Core Security Features, Remediation table). | | **Tests** | Gate 4b: CONFIG_MISSING when honeytoken_domain missing. Safety sig: present, verifiable, changes with content, absent on errors. | ---

v0.5.0

Audit remediation: clock skew, agent response hygiene, inbound safety default, spend visibility, and documentation. | Change | Detail | | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Clock skew window** | `CLOCK_SKEW_WINDOW_SEC` aligned to **300** seconds to match documented ±5 minute acceptance and replay cache TTL. Replay-cache comments corrected (5-minute window; KV TTL = `max(60, skew)`). | | **Agent response fields** | Removed `dns_pinned_ip` and `dns_cache_hit` from successful `guard_net` JSON (infra and cache state are operator-side only). Added **`spend_limit_active`** so agents see whether a per-key spend cap is configured. | | **Inbound safety default** | Default **`inbound_safety_mode` is `BLOCK`** (DB default, `getKeyFlags` fallback, session omit-when-default, `guard_net` fallback). Opt-in **`REPORT`** unchanged for calibration. | | **Uncapped key warning** | `audit_sess` logs a console warning when `spend_limit_credits` is NULL. | | **Admin dashboard** | **Key flags** table lists recent `key_flags` rows with an **Uncapped** badge when spend limit is NULL. | | **Docs** | `AGENT_INTEGRATION.md` §6, `OPERATIONS.md` (spend limits, admin access note), `ROADMAP.md` Phase 2 items, `entropy.ts` limitation note on steganographic bypass. | ---

v0.4.0

Security hardening and autonomous remediation. | Change | Detail | | :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Structured remediation metadata** | `guard_net` and `audit_sess` error responses now include a `remediation` object (`{ action, description, parameters }`) in addition to `error` and `code`. Agents can read `action` as a machine-readable instruction and `description` as a plain-language explanation. Non-self-healable errors (e.g. `HONEYTOKEN`) omit `remediation` entirely. | | **Prefix canonicalization** | HMAC verification now normalises the canonical payload before comparison, closing a class of evasion via URL-encoding or whitespace variance in the signed string. | | **Dual-signature fallback** | `HMAC_SECRET_PREV` optional binding added. During key rotation, signatures verified against the previous secret are accepted and flagged for migration, preventing agent disruption during rolling rotations. | ---

v0.3.2

Penetration audit remediation. All six confirmed findings addressed. | Change | Detail | | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Sess-Bust atomicity (P0)** | Secondary KV index write (`key_sess:{keyId}`) changed from `ctx.waitUntil` to `await` with rollback on failure. Eliminates the ghost session race window where concurrent handshakes could produce sessions invisible to admin Sess-Bust. Old session eviction now wrapped in try/catch for forensic logging. | | **Header validation consolidation (P1)** | Non-ASCII header keys rejected per RFC 7230 (`\x21-\x7E` visible ASCII only). Null-byte, collision, and non-ASCII checks consolidated into a single pass. Dry-run mode uses collect-then-log pattern — one D1 write per check type with up to 5 collision keys in a single rationale. | | **Rate limit ordering (P1)** | Added explicit `@security` JSDoc documenting the load-bearing ordering: rate limit must be after HMAC (prevents counter-poisoning) and before D1 reads (prevents cost-asymmetry abuse). | | **Entropy fragment aggregation (P2)** | Cumulative entropy budget (256 bits) detects split-secret exfiltration across multiple tokens that individually pass the per-token threshold. Only fires if no individual token triggered the existing check. | | **Honeytoken D1 fallback (P2)** | Honeytoken handler now returns `HONEYTOKEN` and proceeds with Unkey disable even if D1 writes fail, preventing the social engineering vector where an incomplete forensic record could lead to key re-enablement. | | **Timing jitter (P3)** | `audit_sess` jitter widened from 50–100ms to 50–300ms using 2-byte CSPRNG. Raises sample count required for reliable timing discrimination. | ---

v0.3.1

Post-audit hardening pass. All seven findings from the internal code quality audit resolved. | Change | Detail | | :---------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Dry run if/else control flow** | `guard_net.ts` header validation loops used `break` after logging `DRY_RUN_BLOCKED`, falling through to the production `BLOCKED` path and making audit mode functionally identical to production mode. Refactored to `if/else` - audit mode now correctly allows requests through after logging violations. | | **logId uniqueness** | Requests producing multiple log writes (e.g. `DRY_RUN_BLOCKED` followed by `ALLOWED`) crashed the D1 worker with `UNIQUE constraint failed` on `request_logs.id`. Added a `logIndex` counter inside the `writeLog` closure: first write uses the bare `logId`, subsequent writes append `-{n}`. | | **Sess-Bust atomicity** | Old session deletion in `audit_sess` was deferred via `ctx.waitUntil`, leaving a race window where both the old and new sessions were simultaneously valid. Delete is now `await`ed inline before the new session is written, enforcing strict 1:1 concurrency semantics. | | **Admin auth DRY extraction** | Brute-force rate limiting and constant-time key comparison were duplicated verbatim between `admin_suite.ts` and `emergency_stop.ts`. Extracted to `checkAdminAuth()` in `src/lib/admin-auth.ts` and consumed universally. | | **Webhook error detection** | `dispatchSecurityAlert` only caught network-level errors; HTTP 4xx/5xx webhook responses were silently swallowed. Added `if (!res.ok) throw` inside the try block so all delivery failures emit a `console.error`. | | **`getSystemConfig` consolidation** | `audit_sess.ts` queryed `system_config` directly with an inline SQL string. Replaced with `isGloballyPaused(env.DB)` from `src/lib/db.ts`, which already wraps `getSystemConfig`. Eliminates the divergent query pattern. | | **Minor cleanup** | Removed duplicate `makeErr` import, removed shadow `logId` in mock path, added `console.warn` to previously empty catch blocks. | ---

v0.3.0

Dry run / audit mode, human-in-the-loop alerting, and resume deduplication. | Change | Detail | | :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Dry Run / Audit Mode** | New per-key `dry_run` flag (stored in `key_flags` table). When enabled, heuristic checks (header anomalies, IP mismatch, domain bans, entropy violations) log `DRY_RUN_BLOCKED` but allow the request through. Financial and auth checks are never bypassed. Flag is cached in the KV session at `audit_sess` handshake - zero added D1 I/O per `guard_net` request. | | **`DRY_RUN_BLOCKED` outcome type** | Added as a proper `RequestOutcome` union member. Queryable in `admin_stats`, exposed as a separate `dry_run_flagged` counter, and rendered in sky-blue in the dashboard. | | **Human-in-the-Loop Honeytoken Alerts** | `ALERT_WEBHOOK_URL` optional secret dispatches JSON `POST` payloads for `HONEYTOKEN`, `SSRF_HOSTNAME`, and `SSRF_DNS` events. Non-blocking via `ctx.waitUntil` - a slow webhook never delays the response. | | **Sess-Bust** | Secondary KV index `key_sess:{keyId}` written at `audit_sess` time. `admin_set_dry_run` (MCP) and `POST /keys/:keyId/dry-run` (HTTP) use it to patch the live session in-place, propagating flag changes without forced re-authentication. | | **Resume deduplication** | `orchestrateKeyResume()` extracted to `src/lib/admin.ts`. HTTP route and MCP tool now delegate to the same function, eliminating the ghost-block failure mode where a new KV prefix added in one caller but not the other caused resumed keys to immediately re-suspend. | | **`admin_set_dry_run` MCP tool** | New admin tool to enable/disable audit mode for any key. Includes Sess-Bust logic and reports whether the live session was patched. | ---

v0.2.0

Admin MCP suite, partial resume detection, and KV counter cleanup. | Change | Detail | | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Admin MCP Suite** | Three new tools: `admin_list_paused`, `admin_resume_key`, `admin_stats`. All share the existing `emergency_stop` lockout budget. | | **Partial resume detection** | `admin_resume_key` now checks the `enableUnkeyKey` return value. Returns `UNKEY_SYNC_FAILED` with `isError: true` if the Unkey API rejects the re-enable call, preventing silent split-brain state between D1 and the Unkey edge. | | **KV counter cleanup fix** | `admin_resume_key` now clears `rl:ent:{keyId}:*` (entropy auto-pause counter) and `rl:hmac:{keyId}:*` (HMAC failure counter) alongside the existing `rl:{keyId}:*` cleanup. Previously, a resumed key could be immediately re-suspended on its first new violation. | | **`getGhostId` utility** | Added to `src/lib/crypto.ts`. Derives a stable 128-bit opaque identifier for a key using `sha256('ghost:{keyId}:{LOG_SALT}').slice(0,32)`. Forward-compatible for future KV namespacing of security counters. | | **Auto-pause documentation** | Honeytoken, entropy auto-pause, and HMAC failure auto-pause behaviors are now documented with their conditions, actions, and clearance paths. | ---

v0.1.0

Initial hardening pass. Five adversarial vectors addressed from red-team audit. | Vector | Change | Standard | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------- | | **V1 — EDoS Circuit Breaker** | Replaced Isolate-Local `Map` with KV-backed counter. Auto-pauses keys at 10+ DB failures in 60s. | NIST SP 800-53 SC-5, OWASP Circuit Breaker | | **V2 — Header Canonicalization** | Rejects headers with null bytes (CWE-158) and Unicode keys that collide after lowercasing. | RFC 7230 §3.2, RFC 9113 §8.2 | | **V3 — Error Hardening** | Stripped entropy values and rate-limit counts from error responses. Auto-pauses keys after 3 entropy blocks in 60s. | NIST SP 800-53 SI-11, OWASP API7:2023 | | **V4 — Timing Mitigation** | Added 50–200ms random jitter to `usage_summary` responses. | OWASP Timing Attack Prevention | | **V5 — JWT Deep Scan** | Extended deep scan to all three JWT segments (header, payload, signature). Removed the `continue` that skipped sliding-window fallback. | CWE-116, NIST SP 800-53 SC-7 | | **SDK Sync** | Both JS and Python SDKs now include `headersHash` in the 4-part HMAC payload. | |