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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user