Encrypt sensitive credential fields before event bus emission (#215)

- Add encrypt_credential_dict() and decrypt_credential_field() to utils/crypto.py
- Create utils/credential_encryption.py with encryption/decryption helpers
- Add get_credential_encryption_key() to retrieve key from Infisical at runtime
- Add emit_credential_found() wrapper for modules to use instead of direct bus.emit()
- Update all 15 modules emitting CREDENTIAL_FOUND to use encrypted wrapper
- Update 4 modules consuming CREDENTIAL_FOUND to decrypt payload before processing
- Sensitive fields (username, password, hash, token, etc.) encrypted with AES-256-GCM
- Falls back to plaintext if encryption key unavailable or encryption fails
This commit is contained in:
Cobra
2026-04-06 11:43:46 -04:00
parent f15b8994cd
commit edf2ea7c36
18 changed files with 256 additions and 63 deletions
+59
View File
@@ -73,6 +73,65 @@ class CryptoEngine:
return self._aesgcm.decrypt(nonce, ct, aad)
def encrypt_credential_dict(payload: dict, key: bytes) -> dict:
"""Encrypt sensitive fields in credential event payload before bus emission.
Sensitive fields: username, password, hash, domain, token, secret, api_key
Returns a new dict with encrypted fields as bytes wrapped in __encrypted__ markers.
"""
if len(key) != KEY_SIZE:
raise ValueError(f"Key must be {KEY_SIZE} bytes")
engine = CryptoEngine(key)
encrypted = {}
sensitive_fields = {
"username", "password", "hash", "hash_value", "domain",
"token", "secret", "api_key", "access_token", "refresh_token",
"credential_value", "nt_hash", "lm_hash", "response",
"auth_string", "bearer_token"
}
for field, value in payload.items():
if field.lower() in sensitive_fields and isinstance(value, str) and value:
try:
# Encrypt the string value
plaintext = value.encode("utf-8")
ciphertext = engine.encrypt(plaintext)
# Encode to base64 for transport over JSON
import base64
encrypted[field] = f"__encrypted__{base64.b64encode(ciphertext).decode('ascii')}"
except Exception:
# If encryption fails, include plaintext (log warning in caller)
encrypted[field] = value
else:
# Pass through unencrypted
encrypted[field] = value
return encrypted
def decrypt_credential_field(value: str, key: bytes) -> str:
"""Decrypt a single encrypted credential field."""
if not isinstance(value, str) or not value.startswith("__encrypted__"):
return value
if len(key) != KEY_SIZE:
raise ValueError(f"Key must be {KEY_SIZE} bytes")
try:
import base64
engine = CryptoEngine(key)
encrypted_part = value[len("__encrypted__"):]
ciphertext = base64.b64decode(encrypted_part)
plaintext = engine.decrypt(ciphertext)
return plaintext.decode("utf-8")
except Exception:
# Return original if decryption fails
return value
def encrypt_file(
src: str,
dst: str,