Wire the encrypted secrets vault (ahead of a trade key) - #115
Closed
eaitbrahim wants to merge 1 commit into
Closed
Conversation
The vault (security/secrets.py: scrypt -> AES-GCM) was built in Phase 3 and sat UNUSED -- the CLI read credentials from a plaintext .env via config.load_secrets. Fine for the read-only key in use today; the wrong risk class for a TRADE-enabled key, which is what the supervised live test needs. This wires the vault the design already built. New keel/security/secret_source.py centralises WHERE credentials come from, with a deliberate precedence that is NOT a fallback chain: 1. the vault (secrets.enc) when it exists -- the home for a trade key; 2. .env only when no vault exists -- the pre-vault path, unchanged. ⛔ A vault present but UNLOCKABLE (no/wrong passphrase, tampered file) RAISES rather than silently reading .env. Falling through would let a stale or lower-privilege .env key stand in for the vault the operator deliberately created -- the exact downgrade the vault exists to prevent. Pinned by test_a_vault_that_cannot_be_unlocked_RAISES_never_reads_env. The passphrase comes from KEEL_VAULT_PASSPHRASE (headless) or a TTY prompt -- never from config or the DB. A vault present with no passphrase available fails closed with guidance. _build_broker now resolves through this layer. New CLI: vault init (import from .env, prompts twice, refuses to clobber without --force), vault status (reports present/unlocks WITHOUT printing values), vault rekey (re-encrypt under a new passphrase, secrets unchanged). Verified end to end against the real CLI: init from .env -> status locked without a passphrase -> unlocks via KEEL_VAULT_PASSPHRASE -> resolve_ secrets prefers the vault -> the .enc file contains NO plaintext key. Errors never contain the passphrase or a secret (tested). docs/operator-secrets.md documents the trade-key setup: seal, verify, THEN delete .env. The vault decides only where the credential comes from -- confirm-mode, the 15 rails and the dangerous-action gate still sit in front of every live order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Closing: superseded by a change of direction, not by a problem with the work. The local encrypted secrets vault is being removed rather than wired in. Credentials come from a git-ignored Worth recording: Reopen if we ever go back to a local vault. |
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.
Wires the encrypted vault ahead of a trade-enabled key. The next thing before real money.
Why
The vault (
security/secrets.py— scrypt → AES-GCM) was built in Phase 3 and sat unused: the CLI read credentials from a plaintext.env. Fine for the read-only key in use today; the wrong risk class for a trade-enabled key, which the supervised live test needs. This wires the vault the design already built.The resolution layer — precedence, not fallback
New
keel/security/secret_source.pycentralises where credentials come from:secrets.enc) when it exists — the home for a trade key..envonly when no vault exists — the pre-vault path, unchanged.⛔ A vault present but unlockable — no passphrase, wrong passphrase, tampered file — RAISES rather than silently reading
.env. Falling through would let a stale or lower-privilege.envkey stand in for the vault the operator deliberately created — the exact downgrade the vault exists to prevent. Pinned by a test.The passphrase comes from
KEEL_VAULT_PASSPHRASE(headless) or a TTY prompt — never from config or the DB.CLI
keel vault init— import from.env, prompts twice, refuses to clobber without--force.keel vault status— reports present / unlocks without printing values.keel vault rekey— re-encrypt under a new passphrase; secrets unchanged._build_brokernow resolves through this layer.Verified end to end against the real CLI
init from
.env→statuslocked without a passphrase → unlocks viaKEEL_VAULT_PASSPHRASE→resolve_secretsprefers the vault → the.encfile contains no plaintext key. Errors never contain the passphrase or a secret (tested).What the vault does NOT do
It decides only where the credential comes from. Confirm-mode, the 15 rails, and the dangerous-action gate still sit in front of every live order.
docs/operator-secrets.mddocuments the trade-key setup: seal → verify → then delete.env.Verification
1273 tests pass (up from 1258), ruff clean.
🤖 Generated with Claude Code