feat: TAP agent re-identification via Ed25519 keypair challenge-response#32
Conversation
…ain-and-offer-more
…d + raw ed25519 key format
… redirect to /login not /dashboard/login
…-level routes receive it
- Add POST /v1/agents/auth + /v1/agents/auth/verify endpoints for agent identity proofs - Add DELETE /v1/agents/:id endpoint with dashboard auth - Update POST /v1/agents/register/tap to patch existing agents instead of creating duplicates - Sync app_agents KV index on agent creation/deletion - Add isDashboardAuthedPath() to app-gate for DELETE /v1/agents/:id - Dashboard account page: Add Agent button with copyable registration prompt, delete buttons, re-identification instructions per TAP agent - CLAUDE.md: add re-identification section with working Node.js Bash script - .claude/commands/botcha-identify.md: slash command for TAP re-auth - wrangler.toml: add BOTCHA_BASE_URL var (https://botcha.ai)
🤖 BOTCHA Verification RequiredThis repository only accepts contributions from AI agents. To prove you're an agent, solve this challenge: Challenge ID: gh-1771814045-32 Numbers: [125574, 116779, 115846, 102304, 127645] Task: Compute SHA256 of each number, return the first 8 hex characters. Reply with this exact format: Time limit: 5 minutes from this comment. A human would need to manually run echo -n "123456" | sha256sum five times and copy-paste results. An AI agent just... computes it. Powered by BOTCHA (https://botcha.ai) — Prove you're a bot. Humans need not apply. |
🚀 Preview Deployed — PR #32Branch: Quick smoke testsBASE="https://botcha-pr-32.carrot-cart.workers.dev"
# Health check
curl "$BASE/health"
# Challenge flow
APP_ID=app_c4e8aade83ce32f0
curl "$BASE/v1/challenge?app_id=$APP_ID"
# New endpoints on this PR (check EPIC.md for specifics)
curl "$BASE/v1/" | jq .
Auto-deployed by preview.yml · View logs |
- Replace mismatched btn-delete/btn-rotate with single btn-action class (small, 11px, monospace, hover-only color) with .danger and .warn variants - + Add Agent button matches same size/weight - TAP indicator is now a consistent btn-action with green color - CLAUDE.md: add Lost Private Key recovery section - account.tsx: add rotate-key panel per agent with copyable recovery prompt - index.tsx: add step 12 explaining key recovery via app_secret
- Show explanation that app_secret was issued once at creation - Add 'Email recovery code' button that calls POST /v1/auth/recover - After code arrives, inline form to verify it and rotate the secret - Recovery only available when email is verified (shown conditionally)
…nt required After entering the recovery code, immediately POST rotate-secret with the new session cookie and show the new app_secret value in the UI with a copy button. Remove confusing 'ask your agent' instruction.
…efined - Pass btn explicitly instead of using event.target (not a parameter) - Extract copySecret() function instead of inline onclick string-building (was corrupting the script block with unbalanced quotes) - Write secret value via textContent not innerHTML to avoid quote escaping
- CLAUDE.md re-identification script: validate PRIV_RAW, reject sk_ keys with clear error message, strip tapk_ prefix before base64 decoding - CLAUDE.md key recovery script: output tapk_ prefix on new private key - Dashboard re-identify panel: label 'TAP private key (tapk_...)' and add amber warning note distinguishing it from app_secret (sk_...) - Registration prompt: instruct agent to prefix private key output with tapk_ and explicitly label it as different from app_secret
validateAppAccess only accepted Bearer JWTs, making app_secret-based key recovery impossible. Now rotateKeyRoute checks x-app-secret + app_id first (recovery path), falling back to Bearer JWT (normal path).
Agents can now re-identify using their existing ANTHROPIC_API_KEY, OPENAI_API_KEY etc — no separate tapk_ secret to manage or lose. - TAPAgent: add provider + provider_key_hash fields - POST /v1/agents/auth/provider: re-identify by hashing provider API key against stored SHA-256 hash — key never stored in plaintext - POST /v1/agents/register/tap: accept provider + api_key fields, hash on write - app-gate: add /v1/agents/auth/provider to open paths - Dashboard: TAP column shows provider name, re-identify panel shows provider flow (no tapk_ needed) vs keypair flow - CLAUDE.md: Option A = provider key (preferred), Option B = tapk_ fallback
…fication - POST /v1/oauth/device — issues device_code + user_code (brt_ prefix refresh tokens) - POST /v1/oauth/token — polling endpoint, returns brt_... refresh token on approval - POST /v1/oauth/approve — human approval/denial via dashboard session - POST /v1/oauth/revoke — revoke refresh token - GET /v1/oauth/lookup — public agent info for approval page UI - POST /v1/agents/auth/refresh — exchange brt_... refresh token for identity JWT - GET /device — human-facing approval page (requires dashboard auth) - Open paths wired in app-gate.ts - Dashboard account page: OAuth status column, revoke button, re-identify panel updated - CLAUDE.md: Option A (OAuth refresh token) added as primary re-identification method
…all discovery surfaces
- public/ai.txt: add /v1/agents/auth{,/verify,/provider,/refresh}, /v1/oauth/* endpoints, AGENT RE-IDENTIFICATION section, 3 feature lines
- static.ts: mirror same additions (embedded ai.txt + Feature list)
- doc/CLIENT-SDK.md: new ## Agent Re-identification section with curl examples for all 3 methods + key recovery
- README.md: add re-identification to feature bullet list, new ## section with endpoint table and curl example
…after approval - GET /v1/oauth/status — new endpoint, returns pending/approved/consumed - /device page polls status after approval, transitions to close-tab message - treat 'approved' same as 'consumed' so human sees confirmation immediately - post-approval shows copyable 'I approved... user code was: BOTCHA-XXXX' for agent handoff
|
🧹 Preview worker |
Summary
POST /v1/agents/auth+/v1/agents/auth/verify— new endpoints for TAP agent identity proofs. Agent gets a nonce, signs it with their Ed25519 private key, receives an identity JWT scoped to theiragent_id(not just their app). No new challenge solve required.DELETE /v1/agents/:id— new endpoint to remove an agent, cleans up both KV index keysPOST /v1/agents/register/tap— now patches an existing agent whenagent_idis provided, instead of creating a duplicateCLAUDE.md+.claude/commands/botcha-identify.md— documents the re-identification flow with a working Node.js script so future Claude sessions use Bash (not WebFetch) to sign the nonce in-processWhy
Previously, after registering as a BOTCHA agent, there was no way for an agent to prove in a subsequent session that it was the same agent — it would just solve a fresh anonymous challenge and get a generic token. The TAP keypair was registered but never used for re-auth. This wires it up end-to-end.