Skip to content

Sealed responses, docs overhaul, and org name fixes#6

Merged
tick10 merged 15 commits into
mainfrom
feature/sealed-responses
Mar 9, 2026
Merged

Sealed responses, docs overhaul, and org name fixes#6
tick10 merged 15 commits into
mainfrom
feature/sealed-responses

Conversation

@tick10

@tick10 tick10 commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Sealed responses: full implementation across CLI, MCP server, SDKs (Go/Python/TypeScript), with per-agent X25519 key pairs, policy controls (require_sealed, allow_unseal), and relabeling attack prevention
  • Docs overhaul: renamed to "Phoenix Secrets" throughout, rewrote README with context-safe framing and model provider trust pitch, deduplicated content across 8 docs, added LAN deployment guide
  • Org name fixes: corrected all GitHub refs to phoenixsec-dev/phoenix, Docker Hub refs to phoenixsecdev/phoenix, commented out dead CI badge

Changes

  • Sealed response wire format, key generation, CLI integration, MCP sealed tokens
  • SDK sealed mode for Go, Python, TypeScript
  • Ref-swap guards in CLI/MCP clients
  • Editorial review of all docs — dedup, accuracy, agent-readability
  • New docs/lan-deployment.md for multi-host deployments
  • Install script, pyproject.toml, release-runbook org name corrections

Test plan

  • go test ./... -count=1 passes
  • phoenix keypair generate + sealed resolve round-trip works
  • MCP sealed token flow produces PHOENIX_SEALED: tokens
  • Doc links resolve (no broken cross-references)
  • Install script points to correct GitHub org

🤖 Generated with Claude Code

Aaron.ClaudeContainer and others added 15 commits March 7, 2026 17:12
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>
@tick10
tick10 merged commit 80bd4ab into main Mar 9, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant