Sealed responses, docs overhaul, and org name fixes#6
Merged
Conversation
Phase 1 of sealed responses. Adds per-agent asymmetric encryption for secret values using X25519 key exchange with XSalsa20-Poly1305 (NaCl box). New types: - SealKeyPair: X25519 key pair for sealed responses - SealedEnvelope: outer wire format with cleartext path/ref + encrypted payload - SealedPayload: inner encrypted payload binding path/ref/value/issued_at New functions: - GenerateSealKeyPair: random X25519 key pair via nacl/box - SealValue: ephemeral key pair encrypt, inner payload JSON, discard ephemeral priv - OpenSealedEnvelope: decrypt + verify version/algorithm + inner/outer path/ref match - LoadSealPrivateKey: file I/O with permission check (rejects group/other readable) - DeriveSealPublicKey: curve25519 scalar base mult - EncodeSealKey/DecodeSealKey: base64 round-trip with 32-byte size validation Security properties: - Version and algorithm validated before any crypto operations - Inner/outer path binding prevents relabeling attacks - Inner/outer ref binding is unconditional (prevents stripped-ref bypass) - Key file permissions enforced (must be 0600 or stricter) - Ephemeral private key zeroed after use 22 tests covering: round-trip, wrong-key, tampered ciphertext, malformed base64, path mismatch, ref mismatch, ref-stripped attack, wrong version, wrong algorithm, key encode/decode, key derivation, file I/O with permission checks, generation uniqueness. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 2 of sealed responses. Adds per-agent X25519 public seal key storage to the ACL system for sealed response encryption. Changes: - Add SealPublicKey field to Agent struct (omitempty for backward compat) - Add SetAgentSealKey(): validates base64 + 32-byte size before storing - Add GetAgentSealKey(): returns empty string (no error) if no key set - Fix UpdateAgent() to preserve SealPublicKey across agent updates (prevents forced agent re-registration from silently clearing seal keys) 10 new tests covering: set/get, agent-not-found, bad base64, wrong size, no-key default, disk persistence, old config backward compat, overwrite, and UpdateAgent preservation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3 of sealed responses. Adds server-side keypair management and
sealed envelope encryption for secret-returning API responses.
New endpoints:
- POST /v1/keypair: admin-only keypair generation with rotation support
(?force=true). Private key returned once, never stored server-side.
Audit actions: generate-keypair / rotate-keypair.
- GET /v1/agents/{name}/seal-key: admin-only public seal key retrieval.
Modified endpoints:
- GET /v1/secrets/{path}: when X-Phoenix-Seal-Key header is present and
validated against agent's registered key, returns sealed_value envelope
instead of plaintext value. Plaintext fallback unchanged.
- POST /v1/resolve: same seal header pattern. Returns sealed_values map
with per-ref envelope binding. dry_run unchanged (always returns
values with "ok").
Security properties:
- Seal header validated against registered key before any response
- Audit logging occurs after seal validation (no false "allowed" entries
for rejected seal headers)
- Cache-Control: no-store on all secret-returning responses (sealed,
plaintext GET, and plaintext resolve) except dry_run
- validateSealHeader() rejects: malformed header, unregistered agent,
key mismatch
12 new tests covering: keypair generation, rotation, non-admin rejection,
agent-not-found, seal key retrieval, sealed GET/resolve with decryption
verification, plaintext fallback, malformed/mismatched/unregistered seal
key rejection, dry_run unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 4 of sealed responses. Adds L9 attestation layer requiring sealed responses for protected paths, with policy-gated unseal for MCP. Policy changes: - Rule.RequireSealed: denies access when no validated seal key is presented - Rule.AllowUnseal: gate for MCP unseal tool (client-side enforcement) - RequestContext.SealKeyPresented: set only after full seal key validation (not raw header presence — prevents bogus headers from bypassing policy) - L9 evaluation block after L8b in Evaluate() - Legacy policy format supports both new fields API wiring: - Seal header validated once at top of handleGetSecret, before list/read branch, so both list mode and single-secret reads pass validated state into attestation - handleResolve passes validated state into per-ref attestation - attestFull() replaces inline header check with explicit bool parameter - Status handler shows sealed-required and unseal-allowed in policy summary 10 new tests: - Policy: deny/allow require_sealed, unmatched paths, allow_unseal backward compat, interaction with require_nonce, no-new-fields default, legacy format - API integration: require_sealed denies without key, allows with key Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ryption, and tests - Add keypair generate/show commands with secure key file storage (0600/0700) - Add loadSealKey/sealHeaders/decryptSealedValue shared helpers - Modify cmdGet, cmdResolve, cmdExec, cmdAgentSockResolve for sealed mode - Harden file permissions on overwrite (os.Chmod after WriteFile) - Assert ref presence in sealed resolve to prevent silent empty output - Strip PHOENIX_SEAL_KEY from child process environment in exec - Add 17 targeted CLI tests covering all sealed paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…olicy - MCP resolve/get return opaque PHOENIX_SEALED:<base64> tokens in sealed mode - Add phoenix_unseal tool (only registered when PHOENIX_SEAL_KEY is set) - Unseal queries server via GET /v1/policy/check for authoritative allow_unseal - Add /v1/policy/check API endpoint with auth, param validation, check routing - Dynamic tool list: 3 base tools + unseal only when sealed mode active - Sealed resolve errors explicitly on refs missing from both sealed_values and errors - 13 MCP sealed tests + 6 API policy check tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All three SDKs now transparently handle sealed responses when a seal private key is configured. Clients send X-Phoenix-Seal-Key header and auto-decrypt sealed_values envelopes using NaCl box (X25519+XSalsa20). - Go: SetSealKey, openSealedEnvelope, key derivation via x/crypto - Python: seal_key_path constructor param, optional PyNaCl dependency - TypeScript: setSealKey, optional tweetnacl dependency - All SDKs: ref-swap integrity check (map key must match envelope ref) - TypeScript: safe deferred seal key loading (no unhandled rejection) - Python tests skip gracefully when PyNaCl is absent - .gitignore: add node_modules/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 8 — sealed responses documentation and version bump: - New docs/sealed-responses.md: full guide covering key generation, configuration (CLI/MCP/SDKs), policy controls, wire format, migration - New docs/multi-agent-setup.md: multi-agent same-host example with per-agent key pairs, scoped ACLs, and policy configuration - Updated docs/api-reference-index.md: policy check endpoint, sealed response behavior for secret-returning endpoints - Updated docs/threat-model.md: sealed response threat analysis - Updated README.md: sealed responses feature, attestation level, env var, MCP security note, reality check update, docs links - Updated phoenix-skill/SKILL.md: PHOENIX_SEAL_KEY, keypair command - Version bump: 0.10.5 -> 1.0.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CLI writes only the private key file and prints the public key to stdout — it does not create a .seal.pub file. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three reviewer findings fixed: 1. Multi-agent setup docs had steps in wrong order — agent must exist before keypair generation. Swapped steps 1 and 2. 2. Verification docs told users to "look for sealed_values" in CLI output, but CLI decrypts locally and prints plaintext. Corrected the success signal description. 3. CLI and MCP batch sealed paths did not validate map key vs envelope ref, leaving a ref-swap attack vector. Added map-key == env.ref checks to all four sealed iteration paths (resolve, get, exec in CLI; resolve and get in MCP). Softened threat model claim to accurately describe the defense. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Five non-blocker patches from review: 1. TOCTOU fix: LoadSealPrivateKey now opens the file once, stats the fd, and reads from it — no window for symlink swap between check and read. 2. Seal key comparison: validateSealHeader compares decoded 32-byte keys instead of raw base64 strings, avoiding false rejects from equivalent encodings. 3. CLI usage: added keypair generate/show commands and PHOENIX_SEAL_KEY to help text so users can discover sealed mode. 4. Go SDK plaintext fallback: ResolveBatch with a seal key configured now falls back to plaintext values if the server doesn't return sealed_values, instead of returning an empty map. 5. TypeScript setSealKey: wraps fs errors in PhoenixError for API consistency, clears _sealKeyError on success so a recovered setSealKey(valid) after a bad env key works correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Baseline commit of README and new docs before editorial review. These are the GPT-generated drafts as-is, prior to cleanup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename to "Phoenix Secrets" throughout README and doc titles - Rewrite README: context-safe framing, model provider trust pitch, named competitors (Vault/SOPS/OpenClaw), sealed responses callout, agent-assisted setup, 1Password and MCP highlights, emergency access - Remove branding assets section and phoenix metaphor tagline - Fix "single binary" claim (two binaries, single codebase) - Mark Docker images as planned/unpublished in configuration.md - Deduplicate content across 8 docs (net -85 lines): getting-started, multi-agent-setup, migration guide, threat model - Add docs/lan-deployment.md: full multi-host deployment walkthrough with topology, cert distribution, troubleshooting, scaling notes - Promote LAN cert note from buried blockquote to linked section Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Editorial review, dedup pass, and LAN deployment guide. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GitHub refs: phoenixsec/phoenix -> phoenixsec-dev/phoenix - Docker Hub refs: phoenixsec/phoenix -> phoenixsecdev/phoenix - Install script REPO var updated - Python SDK pyproject.toml repo URL updated - CI badge commented out (no workflow file exists yet) - Go module path left unchanged (requires coordinated code change) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
require_sealed,allow_unseal), and relabeling attack preventionphoenixsec-dev/phoenix, Docker Hub refs tophoenixsecdev/phoenix, commented out dead CI badgeChanges
docs/lan-deployment.mdfor multi-host deploymentsTest plan
go test ./... -count=1passesphoenix keypair generate+ sealed resolve round-trip worksPHOENIX_SEALED:tokens🤖 Generated with Claude Code