Harden XSS escaping and encrypt Claude API key at rest

- results.js: add esc() helper and apply to all scan-derived fields
  (name, account_name, folder, source, modified, label, img alt) across
  card/list/preview/subject-lookup/related views. Scan-derived strings can
  carry attacker-controlled markup (e.g. a OneDrive file named with HTML),
  so they must be escaped before innerHTML/attribute embedding. Also escape
  the related-docs onclick JSON to match the delete/redact " pattern.
- cpr_detector._placeholder_svg: escape label/name before embedding — served
  as image/svg+xml via /api/thumb?name=, so an unescaped value was a
  reflected-XSS vector when the URL is opened directly.
- cpr_detector: remove 44-line unreachable duplicate of the face-detection
  body left inside _extract_audio_metadata after its return.
- app_config: encrypt claude_api_key at rest with the machine-keyed Fernet
  (same as the SMTP password); add get_claude_api_key() for decryption.
  Legacy plaintext keys still read and are re-encrypted on next save.
  Update readers in document_scanner.py and routes/app_routes.py.

201 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
StyxX65andClaude Fable 5 committed 2026-06-10 11:06:36 +02:00
1 parent 1903115e02
commit b6d2915d49
5 files changed
+48 -65

No files matched your search

+2 -2
View File
@@ -99,8 +99,8 @@ def claude_settings():
@bp.route("/api/settings/claude/test", methods=["POST"])
def claude_test():
from app_config import _load_config
api_key = _load_config().get("claude_api_key", "")
from app_config import get_claude_api_key
api_key = get_claude_api_key()
if not api_key:
return jsonify({"ok": False, "error": "No API key saved"}), 400
try: