feat: cross-repo cloud preview dispatch on PRs#7
Conversation
- Add "*" to Plane union type to support wildcard plane matching - Simplify ParsedScope.plane to just Plane (since "*" is now included) - Add "budget.exceeded", "budget.alert", and "scope.escalation_denied" to AuditAction type to match AUDIT_ACTIONS set in audit-query.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…failures Replace hardcoded NOW = 1_774_432_800 with int(time.time()) so token expiration tests remain valid regardless of when they run. Also increase the expired token offset from 1s to 60s to exceed the 30s clock skew leeway in the verifier. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 12 test files using node:test runner were incompatible with the vitest-based test infrastructure, causing "No test suite found" errors. This converts them to use vitest imports (test, onTestFinished) while keeping node:assert/strict for assertions. Also updates vitest.config.ts to include all test files and unifies the test script to use vitest run. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…auth consistency - Fix Uint8Array/BufferSource incompatibility in scope.ts and auth.ts by casting to ArrayBuffer for crypto.subtle.verify - Prevent scope escalation errors from returning 500 by catching matchScopeFn exceptions in authenticateAndAuthorize and handling non-RelayAuthError objects in scope middleware's jsonErrorResponse - Add bare wildcard "*" support in matchScope so tokens with scopes: ["*"] are correctly authorized - Implement JWKS endpoint (GET /.well-known/jwks.json) serving HMAC key in JWK format, registered in worker.ts - Add missing code field to auth error responses (missing_authorization, invalid_authorization, invalid_token, insufficient_scope) in auth.ts and propagate it in roles.ts for 401/403 response consistency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Triggers ephemeral CF Worker preview deploys in the cloud repo when PRs are opened/updated. Cleans up (deletes D1, KV, worker) on PR close. Each PR gets its own isolated D1 database with fresh migrations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| jwks.get("/jwks.json", (c) => { | ||
| const signingKey = c.env.SIGNING_KEY; | ||
| const keyId = c.env.SIGNING_KEY_ID; | ||
|
|
||
| // Encode the HMAC shared secret as a base64url JWK for symmetric key verification. | ||
| // This is used in dev/test mode where HMAC (HS256) signing is active. | ||
| const encodedKey = base64UrlEncode(new TextEncoder().encode(signingKey)); | ||
|
|
||
| const jwksResponse = { | ||
| keys: [ | ||
| { | ||
| kty: "oct", | ||
| use: "sig", | ||
| alg: "HS256", | ||
| kid: keyId, | ||
| k: encodedKey, | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| c.header("Cache-Control", CACHE_CONTROL_HEADER); | ||
| return c.json(jwksResponse, 200); |
There was a problem hiding this comment.
🔴 JWKS endpoint publicly exposes the HMAC shared secret, enabling arbitrary token forgery
The new jwks.ts route serves the HMAC SIGNING_KEY as a JWK oct key at /.well-known/jwks.json. Because all /.well-known/* paths bypass the auth middleware (packages/server/src/worker.ts:27), any unauthenticated request can retrieve the key material (the k field). Since HMAC (HS256) is a symmetric algorithm, possessing the key allows both signing and verification — an attacker can forge valid tokens with arbitrary claims (sub, org, scopes, etc.), bypassing all authentication and authorization. The comment says "dev/test mode" but no environment guard is applied; the route is unconditionally registered in production via app.route("/.well-known", jwks) at packages/server/src/worker.ts:100.
Prompt for agents
The JWKS endpoint at packages/server/src/routes/jwks.ts exposes the HMAC shared secret (SIGNING_KEY) publicly. For symmetric keys (kty: oct), the k field IS the secret key material. Anyone who reads /.well-known/jwks.json can forge arbitrary HS256 JWTs.
Options to fix:
1. Do NOT expose HMAC keys via JWKS at all. JWKS endpoints are designed for asymmetric keys (RSA, EC) where the public key can safely be shared. Remove the jwks route entirely, or migrate to asymmetric signing (RS256) and only expose the public key.
2. If this must exist for dev/test only, gate the route behind an environment check (e.g. c.env.NODE_ENV === 'development') and ensure it is never registered in production.
3. At minimum, require authentication on this endpoint so only authorized clients can access it — but even this is risky since the secret should not be shared at all.
Was this helpful? React with 👍 or 👎 to provide feedback.
Take main's version which avoids exposing symmetric key material in the JWKS endpoint to prevent token forgery. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
.github/workflows/cloud-preview.ymlthat triggers ephemeral CF Worker preview deploys in thecloudrepo when PRs are opened/updatedHow it works
Prerequisites
CLOUD_DISPATCH_TOKENsecret (GitHub App installation token withactions:writeonAgentWorkforce/cloud)Test plan
🤖 Generated with Claude Code