Files
server-manager/core/encryption.py
chrome-storm-c442 efb508c982 v1.8.10: security audit fixes
- --list and --status no longer expose IP/port/user (only aliases)
- --list-full for admin use (not in skill)
- Removed --add from /ssh skill (servers added via GUI only)
- Removed exact file paths from skill template
- Added deny-read rules for ~/.server-connections/ files
- Wrapped main() in try/except to prevent traceback leaking
- Added needs_reencrypt() to encryption.py for future migration
- install_key no longer prints server IP

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:56:16 -05:00

55 lines
1.6 KiB
Python

"""
Encryption module — Fernet symmetric encryption for servers.json.
Used by both GUI (ServerStore) and CLI (ssh.py).
"""
import os
from cryptography.fernet import Fernet, InvalidToken
# Strong hardcoded key (generated from os.urandom(32), base64-encoded)
# This is the application-level encryption key — by design it's embedded.
ENCRYPTION_KEY = b"xK9mQ2vL7pR4wZ8nB3jF6hT1yD5sA0cE-gU_iO9lMWk="
# Migration: old key from v1.1.0-1.2.0
_OLD_KEY = b"b8iPQzO8_18Y68NluKLQPUTfXVyRsz_BIzTeqfm0aZk="
_fernet = Fernet(ENCRYPTION_KEY)
_fernet_old = Fernet(_OLD_KEY)
def encrypt(plaintext: str) -> bytes:
"""Encrypt a plaintext string, return Fernet token bytes."""
return _fernet.encrypt(plaintext.encode("utf-8"))
def decrypt(data: bytes) -> str:
"""Decrypt Fernet token bytes, return plaintext string.
Tries new key first, falls back to old key for migration."""
try:
return _fernet.decrypt(data).decode("utf-8")
except InvalidToken:
# Try old key for backward compatibility
return _fernet_old.decrypt(data).decode("utf-8")
def is_encrypted(data: bytes) -> bool:
"""Check if data is a Fernet token (starts with 'gAAAAA') vs plain JSON (starts with '{')."""
try:
return data.decode("utf-8").strip().startswith("gAAAAA")
except UnicodeDecodeError:
return True
def needs_reencrypt(data: bytes) -> bool:
"""Check if data was encrypted with old key and needs re-encryption."""
try:
_fernet.decrypt(data)
return False
except InvalidToken:
pass
try:
_fernet_old.decrypt(data)
return True
except InvalidToken:
return False