From 586aa50c951ab076e9ecfa0cba8af496e1d382ca Mon Sep 17 00:00:00 2001 From: "Aaron D. Lee" Date: Wed, 1 Apr 2026 19:48:12 -0400 Subject: [PATCH] Add /health endpoint for system capability reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unauthenticated endpoint that reports what's installed, what's missing, and what's degraded — without exposing secrets or key material. Reports: - Module status (stegasoo, verisoo) with versions - Optional capabilities: DCT, audio, video stego, LMDB, imagehash, USB monitoring, GPIO — each with actionable install hints - Key existence (identity, channel, trusted count, backup status) - Fieldkit status (killswitch, deadman, chain enabled) - System info (Python version, platform, available memory) Overall status is "ok" when core modules + keys are present, "degraded" otherwise. Memory reporting helps diagnose Argon2 OOM issues on constrained hardware (RPi). Co-Authored-By: Claude Opus 4.6 (1M context) --- frontends/web/app.py | 148 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/frontends/web/app.py b/frontends/web/app.py index 44f6fed..76c65ef 100644 --- a/frontends/web/app.py +++ b/frontends/web/app.py @@ -222,6 +222,154 @@ def create_app(config: SoosefConfig | None = None) -> Flask: def index(): return render_template("index.html") + # ── Health check ───────────────────────────────────────────── + + @app.route("/health") + def health(): + """System health and capability report. + + Unauthenticated — returns what's installed, what's missing, + and what's degraded. No secrets or key material exposed. + """ + import platform + import sys + + from flask import jsonify + + from soosef.keystore.manager import KeystoreManager + + ks = KeystoreManager() + + # Core modules + modules = {} + for name, import_path in [ + ("stegasoo", "soosef.stegasoo"), + ("verisoo", "soosef.verisoo"), + ]: + try: + mod = __import__(import_path, fromlist=["__version__"]) + modules[name] = {"status": "ok", "version": getattr(mod, "__version__", "unknown")} + except ImportError as e: + modules[name] = {"status": "missing", "error": str(e)} + + # Optional capabilities + capabilities = {} + + # DCT steganography + try: + from soosef.stegasoo import has_dct_support + capabilities["stego_dct"] = { + "status": "ok" if has_dct_support() else "unavailable", + "hint": None if has_dct_support() else "Install soosef[stego-dct] (scipy, jpeglib, reedsolo)", + } + except ImportError: + capabilities["stego_dct"] = {"status": "missing", "hint": "Install soosef[stego-dct]"} + + # Audio steganography + try: + from soosef.stegasoo import HAS_AUDIO_SUPPORT + capabilities["stego_audio"] = { + "status": "ok" if HAS_AUDIO_SUPPORT else "unavailable", + "hint": None if HAS_AUDIO_SUPPORT else "Install soosef[stego-audio] (soundfile, numpy)", + } + except ImportError: + capabilities["stego_audio"] = {"status": "missing", "hint": "Install soosef[stego-audio]"} + + # Video steganography + try: + from soosef.stegasoo.constants import VIDEO_ENABLED + capabilities["stego_video"] = { + "status": "ok" if VIDEO_ENABLED else "unavailable", + "hint": None if VIDEO_ENABLED else "Requires ffmpeg in PATH", + } + except (ImportError, AttributeError): + capabilities["stego_video"] = {"status": "missing", "hint": "Requires ffmpeg"} + + # LMDB (verisoo storage) + try: + import lmdb # noqa: F401 + capabilities["lmdb"] = {"status": "ok"} + except ImportError: + capabilities["lmdb"] = {"status": "missing", "hint": "Install soosef[attest]"} + + # Perceptual hashing + try: + import imagehash # noqa: F401 + capabilities["imagehash"] = {"status": "ok"} + except ImportError: + capabilities["imagehash"] = {"status": "missing", "hint": "Install soosef[attest]"} + + # USB monitoring + try: + import pyudev # noqa: F401 + capabilities["usb_monitor"] = {"status": "ok"} + except ImportError: + capabilities["usb_monitor"] = {"status": "unavailable", "hint": "Install soosef[fieldkit] (Linux only)"} + + # GPIO (RPi killswitch) + try: + import gpiozero # noqa: F401 + capabilities["gpio"] = {"status": "ok"} + except ImportError: + capabilities["gpio"] = {"status": "unavailable", "hint": "Install soosef[rpi] (Raspberry Pi only)"} + + # Key status (existence only, no material) + keys = { + "identity": "ok" if ks.has_identity() else "missing", + "channel_key": "ok" if ks.has_channel_key() else "missing", + "trusted_keys": len(ks.get_trusted_keys()), + } + + # Backup status + backup_info = ks.last_backup_info() + keys["last_backup"] = backup_info["timestamp"] if backup_info else "never" + keys["backup_overdue"] = ks.is_backup_overdue(config.backup_reminder_days) + + # Fieldkit status + fieldkit = { + "killswitch_enabled": config.killswitch_enabled, + "deadman_enabled": config.deadman_enabled, + "chain_enabled": config.chain_enabled, + } + if config.deadman_enabled: + from soosef.fieldkit.deadman import DeadmanSwitch + dm = DeadmanSwitch() + dm_status = dm.status() + fieldkit["deadman_armed"] = dm_status["armed"] + fieldkit["deadman_overdue"] = dm_status.get("overdue", False) + + # System info (no secrets) + system = { + "python": sys.version.split()[0], + "platform": platform.machine(), + "os": platform.system(), + } + + # Memory (for Argon2 sizing awareness) + try: + import os as _os + mem_bytes = _os.sysconf("SC_PAGE_SIZE") * _os.sysconf("SC_PHYS_PAGES") + system["memory_mb"] = mem_bytes // (1024 * 1024) + except (ValueError, OSError): + pass + + # Overall status + all_ok = ( + all(m["status"] == "ok" for m in modules.values()) + and keys["identity"] == "ok" + and keys["channel_key"] == "ok" + ) + + return jsonify({ + "status": "ok" if all_ok else "degraded", + "version": __import__("soosef").__version__, + "modules": modules, + "capabilities": capabilities, + "keys": keys, + "fieldkit": fieldkit, + "system": system, + }) + return app