Skip to content

Repair scan integrity, access controls, and audit findings - #85

Merged
ttlequals0 merged 16 commits into
mainfrom
fix/code-audit-remediation
Sep 13, 2026
Merged

ttlequals0 merged 16 commits into
mainfrom
fix/code-audit-remediation

Conversation

@ttlequals0

@ttlequals0 ttlequals0 commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Why

PixelProbe 2.9.3 restores selected-scan progress and saved settings. This PR also contains the audit repairs.

Added

  • Authenticated, cursor-paginated access to durable scan-run membership and each member's current raw state.
  • Immutable per-run roots, file membership, task intents, result evidence, cleanup decisions, and notification delivery records.
  • PostgreSQL startup locking, scheduler leadership fencing, required-mount baselines, audit evidence, and token/session hardening.

Fixed

  • Selected-file workers snapshot saved scanner settings before dispatch, including large runs, without relying on Flask context in decode threads.
  • Scan status reports bounded files being checked and ETA even when no single current file is available. It uses durable run state, so a cancelled run does not show an older completed result.
  • Cancellation reclaims only claims owned by its run, preserves unrelated inventory, and keeps terminal run states unchanged.
  • Large selected-file rescans group exact paths by directory, prevent parent and child overlap, record worker failures in run evidence, and do not reactivate terminal runs on redelivery.
  • Media previews identify supported content from the authorized file descriptor. Pending and unscanned files can preview without changing inventory state; unsafe or unrecognized content remains a download.
  • The audit remediation covers path authorization, descriptor-bound media reads, CSRF, tokens, cookies, outbound addresses, rate limits, provider secrets, task ownership, recovery, reports, exports, and unsafe log, CSV, and PDF paths.

Changed

  • Runtime containers use a non-root account with read-only media, reduced capabilities, resource limits, and no pip or unused C headers.
  • API, OpenAPI, documentation, and UI text distinguish immutable run membership from current member state and show files being checked rather than claiming decode progress.
  • Project setup and CI now support Python 3.12 only. The obsolete SQLite helper is retired.

Verification

  • Source SHA: 003bf56aaa75895254585952f571fa0c01f609f2.
  • The local Python 3.12 suite passed: 867 tests passed, with 1 filesystem-dependent skip, in 546.83 seconds.
  • Chromium desktop and mobile layouts plus WebKit mobile layout were reviewed with ETA, current-file, and real media preview behavior.
  • CI and CodeQL passed. The local image checks passed for selected-member activity, ETA, cancellation, and 13 real-media parser tests.
  • The linux/amd64 image runs the app as UID 10001. Trivy found 31 LOW, 528 MEDIUM, 0 HIGH, 0 CRITICAL, 0 fixable, and 0 secrets. The MEDIUM findings have no available fix and are accepted under the agreed no-available-fix policy. The source scan found 0 secrets.
  • The 2.9.3 image is published and deployed. The saved unfinished-member continuation was verified after deployment.
  • PR Repair scan integrity, access controls, and audit findings #85 remains open. The latest image tag remains unchanged.

Related PR

PR #84 is closed and already included. No separate merge is required.

…v2.8.9)

Audio validation ran three full-file passes on fixed deadlines (120s, 120s,
60s) while the video path scaled its deadline with size and duration, so a
two-hour episode timed out and was recorded as a warning without ever being
validated: 159 files on one library. Video and audio now compute the deadline
through one helper, and the audio passes share a single per-file budget.

Orphan cleanup judged a whole run by the share of it that read as missing,
which aborted a cleanup of records for files the operator had deleted and
missed a mount dropping under a scan path holding several. Each file is now
confirmed by reading its own directory: other files in it prove the storage is
present. An empty or missing directory is kept and counted instead, since an
unmounted mountpoint reads the same, and the operator can confirm those.

An aborted run also reported itself as a success, with the orphan count
rendered as a deletion count and the reason unread in the status payload.
The v2.8.9 migration altered 'cleanup_states'; the table is 'cleanup_state'.
Migrations here log and swallow their failures, so the column never arrived
while the model kept selecting it, and /api/cleanup-status returned 500 on
every poll of a deployed 2.8.9. A test now rejects any migration naming a
table the models do not define.
A library with one file per folder could not clean up anything: deleting a
film empties or removes its folder, leaving nothing in it to read, so every
record was kept for confirmation. On a real library that was 44 of 46 flagged
files in one run.

When a folder has nothing to say, the question moves up a level. The parent
has to list folders and one of those has to hold a file this scanner recorded,
which is the library answering rather than leftovers on an unmounted
mountpoint. A tree that has gone entirely still has nothing above it to answer.

Also corrects the completion message, which called kept records ones whose
folder could not be read; a folder emptied by a deletion reads perfectly well.
Three issues found in production logs.

The cross-worker start lock read db.session.bind, which is None unless a
session was bound explicitly, so every call raised and the handler's
log-and-proceed fallback waved through every caller it ever saw.

A schedule skipped by a running scan spent its retry budget while waiting:
144 retries ten minutes apart is one day, and a large scan runs longer. Live,
a 27-hour scan had one schedule at retry 106 of 144. Waiting on a scan that is
reporting progress no longer counts; a scan quiet for half an hour still does.

Two INFO lines on /api/scan-status were 42% of all output, on an endpoint each
open tab polls every few seconds, against a log table already at a million
rows. Both are DEBUG now.
Comment thread pixelprobe/services/maintenance_service.py Fixed
Comment thread pixelprobe/services/maintenance_service.py Fixed
Comment thread pixelprobe/utils/security.py Fixed
Comment thread pixelprobe/utils/security.py Fixed
Comment thread pixelprobe/utils/security.py Fixed
Comment thread pixelprobe/utils/security.py Fixed
Comment thread pixelprobe/utils/security.py Fixed
Comment thread pixelprobe/api/maintenance_routes.py Fixed
Comment thread pixelprobe/api/maintenance_routes.py Fixed
@ttlequals0
ttlequals0 marked this pull request as ready for review September 12, 2026 22:02
@ttlequals0
ttlequals0 merged commit fa05952 into main Sep 13, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants