Security: Password-protect channel key export, add audit plan

Channel Key Protection:
- Hide channel key by default in admin settings
- Require password re-authentication to view/export key
- Add /admin/settings/unlock API endpoint for verification
- Key re-locks on page navigation (per-page-load only)

QR Print Sheet Refinements:
- Key split above/below QR image
- 10pt bold font, 1.6in QR size
- Zero gap between tiles, minimal margins
- No page header/footer for clean printing

Security Audit Plan:
- Comprehensive checklist covering auth, crypto, input validation
- Steganography-specific security considerations
- Air-gap deployment focus with known limitations documented
- Penetration testing checklist and automated tool recommendations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-07 19:16:24 -05:00
parent 4d8575ce33
commit 22cf27d7f6
4 changed files with 499 additions and 44 deletions

View File

@@ -2707,11 +2707,10 @@ def admin_settings():
return render_template(
"admin/settings.html",
# Channel info
# Channel info (key hidden until password verified)
channel_configured=channel_status["configured"],
channel_fingerprint=channel_status.get("fingerprint"),
channel_source=channel_status.get("source"),
channel_key_full=channel_status.get("key") if channel_status["configured"] else "",
# Server config
hostname=os.environ.get("STEGASOO_HOSTNAME") or socket.gethostname(),
port=os.environ.get("STEGASOO_PORT", "5000"),
@@ -2729,6 +2728,35 @@ def admin_settings():
)
@app.route("/admin/settings/unlock", methods=["POST"])
@admin_required
def admin_settings_unlock():
"""Verify password and return channel key (AJAX)."""
from stegasoo.channel import get_channel_status
data = request.get_json() or {}
password = data.get("password", "")
if not password:
return jsonify({"success": False, "error": "Password required"})
# Get current user and verify password
username = get_username()
user = verify_user_password(username, password)
if not user:
return jsonify({"success": False, "error": "Incorrect password"})
# Password verified - return channel key
channel_status = get_channel_status()
channel_key = channel_status.get("key") if channel_status["configured"] else ""
return jsonify({
"success": True,
"channel_key": channel_key
})
@app.route("/admin/users")
@admin_required
def admin_users():