fix(vault): replace XOR stream cipher with AES-256-GCM, remove hardcoded fallback key#279
fix(vault): replace XOR stream cipher with AES-256-GCM, remove hardcoded fallback key#279advikdivekar wants to merge 2 commits into
Conversation
…ded fallback key The credential vault previously used a custom XOR stream cipher that could be broken via crib-dragging attacks, and fell back to the hardcoded key "secuscan-dev-key" when SECUSCAN_VAULT_KEY was unset — meaning every default-config deployment shared the same encryption key. Changes: - vault.py: AES-256-GCM via cryptography.hazmat.primitives.ciphers.aead.AESGCM; fresh 12-byte nonce per encrypt(); 16-byte GCM auth tag provides integrity - config.py: resolved_vault_key raises RuntimeError when neither vault_key nor plugin_signature_key is configured; hardcoded "secuscan-dev-key" fallback removed - requirements.txt: add cryptography>=42.0.0 - .env.example: mark SECUSCAN_VAULT_KEY as required with generation instructions - conftest.py: set vault_key in test environment so existing tests pass - test_vault_failure_messages.py: update byte offsets to match AES-GCM blob layout - test_vault_security.py: 15 new tests covering GCM properties, nonce uniqueness, tamper detection, and key configuration enforcement Fixes utksh1#200 (hardcoded vault encryption key "secuscan-dev-key").
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5e896b471
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| nonce = blob[: self._NONCE_LEN] | ||
| ciphertext = blob[self._NONCE_LEN :] |
There was a problem hiding this comment.
Preserve backward decryption for existing vault records
This change switches the wire format to nonce(12) || ciphertext || tag(16) but decrypt() now unconditionally parses every stored blob with that layout, so any secret written before this commit (old format: 16-byte nonce + 32-byte HMAC + ciphertext) will fail authentication and become unreadable after upgrade. In environments with existing credential_vault rows, /vault/{name} will start returning decryption failures until each secret is manually re-entered; add legacy-format decryption or a migration path to avoid data loss on upgrade.
Useful? React with 👍 / 👎.
|
@utksh1 please review it and I have asked to claim few issues, please assign those issues to me under GSSoC 2026 |
What is the problem
Closes #200
The credential vault had two critical weaknesses:
Broken cipher:
VaultCryptoused a custom XOR stream cipher backed by a 32-byte keystream. Stream ciphers of this form are vulnerable to crib-dragging — an attacker with two ciphertexts encrypted under the same key can XOR them together and recover plaintext without knowing the key.Hardcoded fallback key:
config.resolved_vault_keyfell back to"secuscan-dev-key"whenSECUSCAN_VAULT_KEYwas unset. Every default-config deployment shared this key; any attacker who obtained a vault database could decrypt all credentials offline.What was changed
backend/secuscan/vault.pycryptography.hazmat.primitives.ciphers.aead.AESGCM). Fresh 12-byte nonce perencrypt()call. GCM 16-byte auth tag provides authenticated encryption — tampering always raisesValueError.backend/secuscan/config.pyresolved_vault_keynow raisesRuntimeErrorwhen neitherSECUSCAN_VAULT_KEYnorSECUSCAN_PLUGIN_SIGNATURE_KEYis set. Hardcoded"secuscan-dev-key"fallback removed.backend/requirements.txtcryptography>=42.0.0.env.exampleSECUSCAN_VAULT_KEYas required with generation instructionstesting/backend/conftest.pyvault_key = "test-vault-key-for-unit-tests-only"in test environmenttesting/backend/unit/test_vault_failure_messages.pynonce(12) + ciphertext + auth_tag(16))testing/backend/unit/test_vault_security.pyWhy this approach
cryptographypackage is already widely used in the Python ecosystem; it ships with pre-built wheels and requires no system crypto librariesHow to test
Edge cases covered
ValueError("integrity verification failed")resolved_vault_keyraises immediately when unconfigured (no lazy failures)plugin_signature_keystill accepted as a fallback for deployments that set itVerification checklist
pytest testing/backend/— 253 passed (1 pre-existing failure intest_route_rejects_task_when_limiter_full, confirmed failing onmaintoo)VaultCryptoroundtrip withsettings.resolved_vault_key— pass