Security hardening: admin password handling, malware-gate errors, -O-safe validation, basename traversal#81
Conversation
…dation, basename traversal - rebuild_staging_db.sh: admin password now comes from WOCO_ADMIN_PASSWORD and is applied only when the user is first created — a re-run no longer silently resets an existing admin's password to 'admin' on an internet-facing box. Site domain honors DJANGO_APP_HOSTNAME; setup SQL path references corrected to tools/; set -euo pipefail. - fingerprint.sh: grep exit >=2 (scanner failure) now exits 2 instead of being swallowed and reported as 'OK: no malware fingerprints'. - pipeline_llm.py / ascc_page_extract.py / ascc_page_processor.py: API-key presence and LLM-output validation converted from assert to explicit raises so the gates survive python -O; fallback except-tuples updated to catch ValueError where AssertionError was load-bearing. - ascc_page_processor/extract/image_extract Paths: --basename rejected if it contains path separators or '..' (writes/deletes could escape tools/wip/ when stage scripts are invoked directly). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Code review overview
Syntax ErrorsNo issues found. Code SmellsFile: The pattern of passing secrets via environment variables into Suggested fix: Extract the admin-creation and site-update logic into proper Django management commands (e.g., File: The path-traversal guard is duplicated verbatim across three files with identical comments. This is a DRY violation; if the rule ever needs to change (e.g., to also reject null bytes or colons on Windows), it must be updated in three places. Suggested fix: Extract BugsFile:
Suggested fix: Add a structured log/warning (e.g., File:
Suggested fix: if not (isinstance(images_above, int) and not isinstance(images_above, bool) and images_above >= 0):
raise ValueError(f"images_above bad: {images_above!r}")The same pattern applies to File:
Suggested fix: Consider adding Security VulnerabilitiesFile: The path-traversal guard checks for Suggested fix: Use resolved = (WIP_DIR / "cache" / basename).resolve()
if not str(resolved).startswith(str(WIP_DIR.resolve())):
raise ValueError(...)This is more robust than substring matching and eliminates false positives on names like File:
Suggested fix: Consider passing the password via a temporary file with restricted permissions and having the Python code read it, or use a secrets manager. At minimum, document the exposure window in the script comments. |
Fixes from a security review of the tools/ scripts (2026-07-02).
Changes
rebuild_staging_db.sh— the admin password previously reset toadmin/adminunconditionally on every run on an internet-facing box. Now: password comes fromWOCO_ADMIN_PASSWORD(script refuses to run without it) and is applied only when the admin user is first created; an existing admin's password is never touched. Also: Site domain honorsDJANGO_APP_HOSTNAME, setup-SQL path correctedscripts/→tools/,set -euo pipefail.fingerprint.sh— grep exit ≥2 (scanner itself failed: bad path, unreadable tree) was swallowed by2>/dev/nulland reported as "OK: no malware fingerprints" — a false negative in a security gate. Now exits 2 loudly. The documented exit code 2 is now reachable.assert→ explicit raises (pipeline_llm.py,ascc_page_extract.py,ascc_page_processor.py) — API-key presence and LLM-output validation were enforced only byassert, whichpython -Ostrips entirely. Converted toRuntimeError/ValueError; the two fallback handlers that caughtAssertionErrornow catchValueError, so recovery behavior is unchanged.test_pipeline_llm.pyupdated to expectRuntimeError.--basenamepath traversal —Paths.__init__in all three stage scripts now rejects basenames containing/,\, or..(a value like../../xcould write/delete outsidetools/wip/when the stage scripts are invoked directly;ascc_cli.pyvalidates state codes but the scripts are also runnable standalone).Not touched (flagging, not fixing here)
ascc_data_munger.py's ~117 inline self-test asserts (same-Oconcern, but moving them to the test suite is a refactor).test_ascc_cli.pyfailures from the ASCC2 → ASCC6 default change (tests still expect ASCC2).Tests
Full tools suite: 159 ran; only those 4 pre-existing
test_ascc_cli.pyfailures remain (present onstagingbefore this branch).fingerprint.shexercised on both the clean and scanner-error paths.🤖 Generated with Claude Code