Fix black formatting and target Python 3.12 in CI
Some checks failed
CI / lint (push) Failing after 36s
CI / typecheck (push) Failing after 37s
CI / test (push) Failing after 24s

Reformat 8 files and add --target-version py312 to avoid
3.13 AST parsing issues with Python 3.12 container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee 2026-04-01 18:26:32 -04:00
parent 6d6a626b6b
commit 5c74a5f4aa
9 changed files with 448 additions and 394 deletions

View File

@ -18,7 +18,7 @@ jobs:
git clone --depth=1 --branch="${GITHUB_REF_NAME}" https://git.golfcards.club/alee/soosef.git "$GITHUB_WORKSPACE" || git clone --depth=1 https://git.golfcards.club/alee/soosef.git "$GITHUB_WORKSPACE"
- run: pip install ruff black
- name: Check formatting
run: black --check src/ tests/ frontends/
run: black --check --target-version py312 src/ tests/ frontends/
- name: Lint
run: ruff check src/ tests/ frontends/

View File

@ -477,7 +477,9 @@ def _register_stegasoo_routes(app: Flask) -> None:
detail=None if success else message,
)
if success:
flash(f"User '{username}' created with temporary password: {temp_password}", "success")
flash(
f"User '{username}' created with temporary password: {temp_password}", "success"
)
else:
flash(message, "error")
return redirect(url_for("admin_users"))
@ -535,7 +537,9 @@ def _register_stegasoo_routes(app: Flask) -> None:
if not use_pin and not use_rsa:
flash("You must select at least one security factor (PIN or RSA Key)", "error")
return render_template("stego/generate.html", generated=False, has_qrcode=_HAS_QRCODE)
return render_template(
"stego/generate.html", generated=False, has_qrcode=_HAS_QRCODE
)
pin_length = int(request.form.get("pin_length", 6))
rsa_bits = int(request.form.get("rsa_bits", 2048))
@ -569,7 +573,11 @@ def _register_stegasoo_routes(app: Flask) -> None:
temp_storage.save_temp_file(
qr_token,
creds.rsa_key_pem.encode(),
{"filename": "rsa_key.pem", "type": "rsa_key", "compress": qr_needs_compression},
{
"filename": "rsa_key.pem",
"type": "rsa_key",
"compress": qr_needs_compression,
},
)
return render_template(
@ -594,7 +602,9 @@ def _register_stegasoo_routes(app: Flask) -> None:
)
except Exception as e:
flash(f"Error generating credentials: {e}", "error")
return render_template("stego/generate.html", generated=False, has_qrcode=_HAS_QRCODE)
return render_template(
"stego/generate.html", generated=False, has_qrcode=_HAS_QRCODE
)
return render_template("stego/generate.html", generated=False, has_qrcode=_HAS_QRCODE)
@ -633,8 +643,10 @@ def _register_stegasoo_routes(app: Flask) -> None:
compress = file_info.get("compress", False)
qr_png = generate_qr_code(key_pem, compress=compress)
return send_file(
io.BytesIO(qr_png), mimetype="image/png",
as_attachment=True, download_name="soosef_rsa_key_qr.png",
io.BytesIO(qr_png),
mimetype="image/png",
as_attachment=True,
download_name="soosef_rsa_key_qr.png",
)
except Exception as e:
return f"Error generating QR code: {e}", 500
@ -656,8 +668,10 @@ def _register_stegasoo_routes(app: Flask) -> None:
key_id = secrets.token_hex(4)
filename = f"soosef_key_{private_key.key_size}_{key_id}.pem"
return send_file(
io.BytesIO(encrypted_pem), mimetype="application/x-pem-file",
as_attachment=True, download_name=filename,
io.BytesIO(encrypted_pem),
mimetype="application/x-pem-file",
as_attachment=True,
download_name=filename,
)
except Exception as e:
flash(f"Error creating key file: {e}", "error")
@ -667,12 +681,15 @@ def _register_stegasoo_routes(app: Flask) -> None:
from stego_routes import register_stego_routes
register_stego_routes(app, **{
"login_required": login_required,
"subprocess_stego": subprocess_stego,
"temp_storage": temp_storage,
"has_qrcode_read": _HAS_QRCODE_READ,
})
register_stego_routes(
app,
**{
"login_required": login_required,
"subprocess_stego": subprocess_stego,
"temp_storage": temp_storage,
"has_qrcode_read": _HAS_QRCODE_READ,
},
)
# /about route is in stego_routes.py
@ -683,22 +700,26 @@ def _register_stegasoo_routes(app: Flask) -> None:
def api_channel_status():
result = subprocess_stego.get_channel_status(reveal=False)
if result.success:
return jsonify({
"success": True,
"mode": result.mode,
"configured": result.configured,
"fingerprint": result.fingerprint,
"source": result.source,
})
return jsonify(
{
"success": True,
"mode": result.mode,
"configured": result.configured,
"fingerprint": result.fingerprint,
"source": result.source,
}
)
else:
status = get_channel_status()
return jsonify({
"success": True,
"mode": status["mode"],
"configured": status["configured"],
"fingerprint": status.get("fingerprint"),
"source": status.get("source"),
})
return jsonify(
{
"success": True,
"mode": status["mode"],
"configured": status["configured"],
"fingerprint": status.get("fingerprint"),
"source": status.get("source"),
}
)
@app.route("/api/compare-capacity", methods=["POST"])
@login_required
@ -711,23 +732,25 @@ def _register_stegasoo_routes(app: Flask) -> None:
result = subprocess_stego.compare_modes(carrier_data)
if not result.success:
return jsonify({"error": result.error or "Comparison failed"}), 500
return jsonify({
"success": True,
"width": result.width,
"height": result.height,
"lsb": {
"capacity_bytes": result.lsb["capacity_bytes"],
"capacity_kb": round(result.lsb["capacity_kb"], 1),
"output": result.lsb.get("output", "PNG"),
},
"dct": {
"capacity_bytes": result.dct["capacity_bytes"],
"capacity_kb": round(result.dct["capacity_kb"], 1),
"output": result.dct.get("output", "JPEG"),
"available": result.dct.get("available", True),
"ratio": round(result.dct.get("ratio_vs_lsb", 0), 1),
},
})
return jsonify(
{
"success": True,
"width": result.width,
"height": result.height,
"lsb": {
"capacity_bytes": result.lsb["capacity_bytes"],
"capacity_kb": round(result.lsb["capacity_kb"], 1),
"output": result.lsb.get("output", "PNG"),
},
"dct": {
"capacity_bytes": result.dct["capacity_bytes"],
"capacity_kb": round(result.dct["capacity_kb"], 1),
"output": result.dct.get("output", "JPEG"),
"available": result.dct.get("available", True),
"ratio": round(result.dct.get("ratio_vs_lsb", 0), 1),
},
}
)
except Exception as e:
return jsonify({"error": str(e)}), 500
@ -751,11 +774,13 @@ def _register_stegasoo_routes(app: Flask) -> None:
def api_generate_credentials():
try:
creds = generate_credentials(use_pin=True, use_rsa=False)
return jsonify({
"success": True,
"passphrase": creds.passphrase,
"pin": creds.pin,
})
return jsonify(
{
"success": True,
"passphrase": creds.passphrase,
"pin": creds.pin,
}
)
except Exception as e:
return jsonify({"error": str(e)}), 500

View File

@ -95,7 +95,9 @@ def register_stego_routes(app, **deps):
def _cleanup_old_jobs(max_age_seconds=3600):
now = time.time()
with _jobs_lock:
to_remove = [jid for jid, data in _jobs.items() if now - data.get("created", 0) > max_age_seconds]
to_remove = [
jid for jid, data in _jobs.items() if now - data.get("created", 0) > max_age_seconds
]
for jid in to_remove:
cleanup_progress_file(jid)
del _jobs[jid]
@ -112,7 +114,8 @@ def register_stego_routes(app, **deps):
with Image.open(io.BytesIO(image_data)) as img:
if img.mode in ("RGBA", "LA", "P"):
bg = Image.new("RGB", img.size, (255, 255, 255))
if img.mode == "P": img = img.convert("RGBA")
if img.mode == "P":
img = img.convert("RGBA")
bg.paste(img, mask=img.split()[-1] if img.mode == "RGBA" else None)
img = bg
elif img.mode != "RGB":
@ -128,15 +131,27 @@ def register_stego_routes(app, **deps):
temp_storage.cleanup_expired(TEMP_FILE_EXPIRY)
def allowed_image(fn):
return bool(fn and "." in fn and fn.rsplit(".", 1)[1].lower() in {"png", "jpg", "jpeg", "bmp", "gif"})
return bool(
fn
and "." in fn
and fn.rsplit(".", 1)[1].lower() in {"png", "jpg", "jpeg", "bmp", "gif"}
)
def allowed_audio(fn):
return bool(fn and "." in fn and fn.rsplit(".", 1)[1].lower() in {"wav", "flac", "mp3", "ogg", "aac", "m4a", "aiff", "aif"})
return bool(
fn
and "." in fn
and fn.rsplit(".", 1)[1].lower()
in {"wav", "flac", "mp3", "ogg", "aac", "m4a", "aiff", "aif"}
)
def format_size(n):
if n < 1024: return f"{n} B"
elif n < 1024*1024: return f"{n/1024:.1f} KB"
else: return f"{n/(1024*1024):.1f} MB"
if n < 1024:
return f"{n} B"
elif n < 1024 * 1024:
return f"{n/1024:.1f} KB"
else:
return f"{n/(1024*1024):.1f} MB"
# ── Routes below are extracted from stegasoo app.py ──
@ -247,7 +262,6 @@ def register_stego_routes(app, **deps):
finally:
cleanup_progress_file(job_id)
def _run_encode_audio_job(job_id: str, encode_params: dict) -> None:
"""Background thread function for async audio encode (v4.3.0)."""
progress_file = get_progress_file_path(job_id)
@ -333,13 +347,14 @@ def register_stego_routes(app, **deps):
finally:
cleanup_progress_file(job_id)
@app.route("/encode", methods=["GET", "POST"])
@login_required
def encode():
if request.method == "POST":
# Check if async mode requested
is_async = request.form.get("async") == "true" or request.headers.get("X-Async") == "true"
is_async = (
request.form.get("async") == "true" or request.headers.get("X-Async") == "true"
)
def _error_response(msg):
"""Return error as JSON (async) or HTML flash (sync)."""
@ -366,7 +381,9 @@ def register_stego_routes(app, **deps):
)
if not ref_photo or not carrier:
return _error_response("Both reference photo and audio carrier are required")
return _error_response(
"Both reference photo and audio carrier are required"
)
if not allowed_image(ref_photo.filename):
return _error_response("Reference must be an image (PNG, JPG, BMP)")
@ -458,7 +475,9 @@ def register_stego_routes(app, **deps):
if not result.is_valid:
return _error_response(result.error_message)
key_password = None if rsa_key_from_qr else (rsa_password if rsa_password else None)
key_password = (
None if rsa_key_from_qr else (rsa_password if rsa_password else None)
)
if rsa_key_data:
result = validate_rsa_key(rsa_key_data, key_password)
@ -578,7 +597,9 @@ def register_stego_routes(app, **deps):
# Check DCT availability
if embed_mode == "dct" and not has_dct_support():
return _error_response("DCT mode requires scipy. Install with: pip install scipy")
return _error_response(
"DCT mode requires scipy. Install with: pip install scipy"
)
# Determine payload
if payload_type == "file" and payload_file and payload_file.filename:
@ -764,7 +785,11 @@ def register_stego_routes(app, **deps):
filename = encode_result.filename
if not filename:
filename = generate_filename("stego", output_ext)
elif embed_mode == "dct" and dct_output_format == "jpeg" and filename.endswith(".png"):
elif (
embed_mode == "dct"
and dct_output_format == "jpeg"
and filename.endswith(".png")
):
filename = filename[:-4] + ".jpg"
# Store temporarily
@ -796,12 +821,10 @@ def register_stego_routes(app, **deps):
return render_template("stego/encode.html", has_qrcode_read=_HAS_QRCODE_READ)
# ============================================================================
# ENCODE PROGRESS ENDPOINTS (v4.1.2)
# ============================================================================
@app.route("/encode/status/<job_id>")
@login_required
def encode_status(job_id):
@ -819,7 +842,6 @@ def register_stego_routes(app, **deps):
return jsonify(response)
@app.route("/encode/progress/<job_id>")
@login_required
def encode_progress(job_id):
@ -843,7 +865,6 @@ def register_stego_routes(app, **deps):
# Running but no progress file yet
return jsonify({"percent": 0, "phase": "initializing"})
@app.route("/encode/result/<file_id>")
@login_required
def encode_result(file_id):
@ -867,7 +888,9 @@ def register_stego_routes(app, **deps):
"encode_result.html",
file_id=file_id,
filename=file_info["filename"],
thumbnail_url=url_for("encode_thumbnail", thumb_id=thumbnail_id) if thumbnail_id else None,
thumbnail_url=(
url_for("encode_thumbnail", thumb_id=thumbnail_id) if thumbnail_id else None
),
embed_mode=file_info.get("embed_mode", "lsb"),
output_format=file_info.get("output_format", "png"),
color_mode=file_info.get("color_mode"),
@ -877,7 +900,6 @@ def register_stego_routes(app, **deps):
channel_fingerprint=file_info.get("channel_fingerprint"),
)
@app.route("/encode/thumbnail/<thumb_id>")
@login_required
def encode_thumbnail(thumb_id):
@ -888,7 +910,6 @@ def register_stego_routes(app, **deps):
return send_file(io.BytesIO(thumb_data), mimetype="image/jpeg", as_attachment=False)
@app.route("/encode/download/<file_id>")
@login_required
def encode_download(file_id):
@ -906,7 +927,6 @@ def register_stego_routes(app, **deps):
download_name=file_info["filename"],
)
@app.route("/encode/file/<file_id>")
@login_required
def encode_file_route(file_id):
@ -924,7 +944,6 @@ def register_stego_routes(app, **deps):
download_name=file_info["filename"],
)
@app.route("/encode/cleanup/<file_id>", methods=["POST"])
@login_required
def encode_cleanup(file_id):
@ -937,12 +956,10 @@ def register_stego_routes(app, **deps):
return jsonify({"status": "ok"})
# ============================================================================
# DECODE
# ============================================================================
def _run_decode_job(job_id: str, decode_params: dict) -> None:
"""Background thread function for async decode."""
progress_file = get_progress_file_path(job_id)
@ -1022,7 +1039,6 @@ def register_stego_routes(app, **deps):
finally:
cleanup_progress_file(job_id)
def _run_decode_audio_job(job_id: str, decode_params: dict) -> None:
"""Background thread function for async audio decode (v4.3.0)."""
progress_file = get_progress_file_path(job_id)
@ -1100,7 +1116,6 @@ def register_stego_routes(app, **deps):
finally:
cleanup_progress_file(job_id)
@app.route("/decode", methods=["GET", "POST"])
@login_required
def decode_page():
@ -1118,19 +1133,27 @@ def register_stego_routes(app, **deps):
# ========== AUDIO DECODE PATH (v4.3.0) ==========
if not HAS_AUDIO_SUPPORT:
flash("Audio steganography is not available.", "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
if not ref_photo or not stego_image:
flash("Both reference photo and stego audio are required", "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
if not allowed_image(ref_photo.filename):
flash("Reference must be an image", "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
if not allowed_audio(stego_image.filename):
flash("Invalid audio format", "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
passphrase = request.form.get("passphrase", "")
pin = request.form.get("pin", "").strip()
@ -1144,7 +1167,9 @@ def register_stego_routes(app, **deps):
if not passphrase:
flash("Passphrase is required", "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
ref_data = ref_photo.read()
stego_data = stego_image.read()
@ -1170,29 +1195,40 @@ def register_stego_routes(app, **deps):
rsa_key_from_qr = True
else:
flash("Could not extract RSA key from QR code image.", "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
result = validate_security_factors(pin, rsa_key_data)
if not result.is_valid:
flash(result.error_message, "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
if pin:
result = validate_pin(pin)
if not result.is_valid:
flash(result.error_message, "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
key_password = None if rsa_key_from_qr else (rsa_password if rsa_password else None)
key_password = (
None if rsa_key_from_qr else (rsa_password if rsa_password else None)
)
if rsa_key_data:
result = validate_rsa_key(rsa_key_data, key_password)
if not result.is_valid:
flash(result.error_message, "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
is_async = (
request.form.get("async") == "true" or request.headers.get("X-Async") == "true"
request.form.get("async") == "true"
or request.headers.get("X-Async") == "true"
)
decode_params = {
@ -1237,7 +1273,9 @@ def register_stego_routes(app, **deps):
)
else:
flash(error_msg, "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
if decode_result.is_file:
file_id = secrets.token_urlsafe(16)
@ -1323,7 +1361,9 @@ def register_stego_routes(app, **deps):
rsa_key_from_qr = True
else:
flash("Could not extract RSA key from QR code image.", "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
# Validate security factors
result = validate_security_factors(pin, rsa_key_data)
@ -1336,7 +1376,9 @@ def register_stego_routes(app, **deps):
result = validate_pin(pin)
if not result.is_valid:
flash(result.error_message, "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
# Determine key password
key_password = None if rsa_key_from_qr else (rsa_password if rsa_password else None)
@ -1346,7 +1388,9 @@ def register_stego_routes(app, **deps):
result = validate_rsa_key(rsa_key_data, key_password)
if not result.is_valid:
flash(result.error_message, "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
# Check for async mode (v4.1.5)
is_async = (
@ -1392,8 +1436,13 @@ def register_stego_routes(app, **deps):
# Check for channel key related errors
if "channel key" in error_msg.lower():
flash(error_msg, "error")
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
if "decrypt" in error_msg.lower() or decode_result.error_type == "DecryptionError":
return render_template(
"stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ
)
if (
"decrypt" in error_msg.lower()
or decode_result.error_type == "DecryptionError"
):
raise DecryptionError(error_msg)
raise StegasooError(error_msg)
@ -1462,7 +1511,6 @@ def register_stego_routes(app, **deps):
return render_template("stego/decode.html", has_qrcode_read=_HAS_QRCODE_READ)
@app.route("/decode/download/<file_id>")
@login_required
def decode_download(file_id):
@ -1481,12 +1529,10 @@ def register_stego_routes(app, **deps):
download_name=file_info["filename"],
)
# ============================================================================
# DECODE PROGRESS ENDPOINTS (v4.1.5)
# ============================================================================
@app.route("/decode/status/<job_id>")
@login_required
def decode_status(job_id):
@ -1512,7 +1558,6 @@ def register_stego_routes(app, **deps):
return jsonify(response)
@app.route("/decode/progress/<job_id>")
@login_required
def decode_progress(job_id):
@ -1536,7 +1581,6 @@ def register_stego_routes(app, **deps):
# Running but no progress file yet
return jsonify({"percent": 5, "phase": "reading"})
@app.route("/decode/result/<job_id>")
@login_required
def decode_result(job_id):
@ -1567,7 +1611,6 @@ def register_stego_routes(app, **deps):
has_qrcode_read=_HAS_QRCODE_READ,
)
@app.route("/about")
def about():
from stegasoo.channel import get_channel_status
@ -1588,19 +1631,16 @@ def register_stego_routes(app, **deps):
is_admin=is_admin_user,
)
# ============================================================================
# TOOLS ROUTES (v4.1.0)
# ============================================================================
@app.route("/tools")
@login_required
def tools():
"""Advanced tools page."""
return render_template("stego/tools.html", has_dct=has_dct_support())
@app.route("/api/tools/capacity", methods=["POST"])
@login_required
def api_tools_capacity():
@ -1621,7 +1661,6 @@ def register_stego_routes(app, **deps):
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 400
@app.route("/api/tools/strip-metadata", methods=["POST"])
@login_required
def api_tools_strip_metadata():
@ -1641,11 +1680,12 @@ def register_stego_routes(app, **deps):
buffer = io.BytesIO(clean_data)
filename = image_file.filename.rsplit(".", 1)[0] + "_clean.png"
return send_file(buffer, mimetype="image/png", as_attachment=True, download_name=filename)
return send_file(
buffer, mimetype="image/png", as_attachment=True, download_name=filename
)
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 400
@app.route("/api/tools/exif", methods=["POST"])
@login_required
def api_tools_exif():
@ -1675,7 +1715,6 @@ def register_stego_routes(app, **deps):
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 400
@app.route("/api/tools/exif/update", methods=["POST"])
@login_required
def api_tools_exif_update():
@ -1715,7 +1754,6 @@ def register_stego_routes(app, **deps):
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@app.route("/api/tools/exif/clear", methods=["POST"])
@login_required
def api_tools_exif_clear():
@ -1759,7 +1797,6 @@ def register_stego_routes(app, **deps):
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@app.route("/api/tools/rotate", methods=["POST"])
@login_required
def api_tools_rotate():
@ -1786,7 +1823,9 @@ def register_stego_routes(app, **deps):
# For JPEGs, use jpegtran for lossless rotation/flip (preserves DCT stego)
has_jpegtran = shutil.which("jpegtran") is not None
use_jpegtran = original_format == "JPEG" and has_jpegtran and (rotation or flip_h or flip_v)
use_jpegtran = (
original_format == "JPEG" and has_jpegtran and (rotation or flip_h or flip_v)
)
if use_jpegtran:
# Chain jpegtran operations for lossless transformation
@ -1930,7 +1969,6 @@ def register_stego_routes(app, **deps):
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@app.route("/api/tools/compress", methods=["POST"])
@login_required
def api_tools_compress():
@ -1969,7 +2007,6 @@ def register_stego_routes(app, **deps):
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@app.route("/api/tools/convert", methods=["POST"])
@login_required
def api_tools_convert():
@ -2022,10 +2059,8 @@ def register_stego_routes(app, **deps):
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
# Add these two test routes anywhere in app.py after the app = Flask(...) line:
@app.route("/test-capacity", methods=["POST"])
def test_capacity():
"""Minimal capacity test - no stegasoo code, just PIL."""
@ -2059,7 +2094,6 @@ def register_stego_routes(app, **deps):
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/test-capacity-nopil", methods=["POST"])
def test_capacity_nopil():
"""Ultra-minimal test - no PIL, no stegasoo."""
@ -2075,8 +2109,6 @@ def register_stego_routes(app, **deps):
}
)
# ============================================================================
# AUTHENTICATION ROUTES (v4.0.2)
# ============================================================================

View File

@ -433,9 +433,7 @@ class ChainStore:
if prev_record is not None:
expected_hash = compute_record_hash(prev_record)
if record.prev_hash != expected_hash:
raise ChainIntegrityError(
f"Record {record.chain_index}: prev_hash mismatch"
)
raise ChainIntegrityError(f"Record {record.chain_index}: prev_hash mismatch")
elif record.chain_index == 0:
if record.prev_hash != ChainState.GENESIS_PREV_HASH:
raise ChainIntegrityError("Genesis record has non-zero prev_hash")

View File

@ -47,9 +47,7 @@ class DeadmanSwitch:
state["grace_hours"] = grace_hours
state["last_checkin"] = datetime.now(UTC).isoformat()
self._save_state(state)
logger.info(
"Dead man's switch armed: %dh interval, %dh grace", interval_hours, grace_hours
)
logger.info("Dead man's switch armed: %dh interval, %dh grace", interval_hours, grace_hours)
def disarm(self) -> None:
"""Disarm the dead man's switch."""
@ -83,9 +81,7 @@ class DeadmanSwitch:
if not state["armed"] or not state["last_checkin"]:
return False
last = datetime.fromisoformat(state["last_checkin"])
deadline = last + timedelta(
hours=state["interval_hours"] + state["grace_hours"]
)
deadline = last + timedelta(hours=state["interval_hours"] + state["grace_hours"])
return datetime.now(UTC) > deadline
def status(self) -> dict:

View File

@ -92,14 +92,16 @@ def execute_purge(scope: PurgeScope = PurgeScope.ALL, reason: str = "manual") ->
]
if scope == PurgeScope.ALL:
steps.extend([
("destroy_auth_db", lambda: _secure_delete_file(paths.AUTH_DB)),
("destroy_attestation_log", lambda: _secure_delete_dir(paths.ATTESTATIONS_DIR)),
("destroy_chain_data", lambda: _secure_delete_dir(paths.CHAIN_DIR)),
("destroy_temp_files", lambda: _secure_delete_dir(paths.TEMP_DIR)),
("destroy_config", lambda: _secure_delete_file(paths.CONFIG_FILE)),
("clear_journald", _clear_system_logs),
])
steps.extend(
[
("destroy_auth_db", lambda: _secure_delete_file(paths.AUTH_DB)),
("destroy_attestation_log", lambda: _secure_delete_dir(paths.ATTESTATIONS_DIR)),
("destroy_chain_data", lambda: _secure_delete_dir(paths.CHAIN_DIR)),
("destroy_temp_files", lambda: _secure_delete_dir(paths.TEMP_DIR)),
("destroy_config", lambda: _secure_delete_file(paths.CONFIG_FILE)),
("clear_journald", _clear_system_logs),
]
)
for name, action in steps:
try:

View File

@ -135,9 +135,7 @@ class KeystoreManager:
from datetime import UTC, datetime
meta_path = self._identity_meta_path()
meta_path.write_text(
json.dumps({"created_at": datetime.now(UTC).isoformat()}, indent=None)
)
meta_path.write_text(json.dumps({"created_at": datetime.now(UTC).isoformat()}, indent=None))
return self.get_identity()
@ -263,8 +261,7 @@ class KeystoreManager:
from datetime import UTC, datetime
(archive_dir / "rotation.txt").write_text(
f"Rotated at: {datetime.now(UTC).isoformat()}\n"
f"Old fingerprint: {old_fp}\n"
f"Rotated at: {datetime.now(UTC).isoformat()}\n" f"Old fingerprint: {old_fp}\n"
)
new_key = self.generate_channel_key()

View File

@ -52,8 +52,7 @@ def test_concurrent_append_no_fork(chain_dir: Path):
# Every index must be unique (no fork)
assert len(all_indices) == len(set(all_indices)), (
f"Duplicate chain indices detected — chain forked! "
f"Indices: {sorted(all_indices)}"
f"Duplicate chain indices detected — chain forked! " f"Indices: {sorted(all_indices)}"
)
# Indices should be 0..N-1 contiguous
@ -100,7 +99,7 @@ def test_truncated_chain_file(chain_dir: Path, private_key: Ed25519PrivateKey):
# Truncate the file mid-record
chain_file = chain_dir / "chain.bin"
data = chain_file.read_bytes()
chain_file.write_bytes(data[:len(data) - 50])
chain_file.write_bytes(data[: len(data) - 50])
store2 = ChainStore(chain_dir)
records = list(store2._iter_raw())

View File

@ -22,7 +22,6 @@ from pathlib import Path
import pytest
from click.testing import CliRunner
# ── Fixtures ────────────────────────────────────────────────────────────────
@ -205,7 +204,9 @@ def cli_runner():
return CliRunner()
def test_check_deadman_disarmed(tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch):
def test_check_deadman_disarmed(
tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch
):
"""check-deadman exits 0 and prints helpful message when not armed."""
from soosef.fieldkit import deadman as deadman_mod
from soosef.cli import main
@ -219,7 +220,9 @@ def test_check_deadman_disarmed(tmp_path: Path, cli_runner: CliRunner, monkeypat
assert "not armed" in result.output
def test_check_deadman_armed_ok(tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch):
def test_check_deadman_armed_ok(
tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch
):
"""check-deadman exits 0 when armed and check-in is current."""
from soosef.fieldkit import deadman as deadman_mod
from soosef.cli import main
@ -241,7 +244,9 @@ def test_check_deadman_armed_ok(tmp_path: Path, cli_runner: CliRunner, monkeypat
assert "OK" in result.output
def test_check_deadman_overdue_in_grace(tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch):
def test_check_deadman_overdue_in_grace(
tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch
):
"""check-deadman exits 0 but prints OVERDUE warning when past interval but in grace."""
from soosef.fieldkit import deadman as deadman_mod
from soosef.cli import main