Files
truf-server/DOCKER_READINESS_AUDIT.md
2026-09-30 20:30:56 +03:00

44 KiB

Docker Readiness Audit

Date: 2026-09-14. Scope: the application and runtime launch chain in D:\truf.

This is an inspection report, not an implementation. Application code, configuration, secrets, databases and runtime data were not changed. The target assumed here is a Linux container. Windows containers, target CPU architecture, deployment host and resource budget have not been specified.

Verdict

The project is not ready to containerize unchanged. Adding a Dockerfile around the PowerShell launchers is insufficient. There are both packaging gaps and concrete defects in the POSIX process/security paths. PostgreSQL is also part of a local process-ownership protocol, not just a replaceable connection URL.

The first deployment should retain one supervisor and its authenticated children in one container, with one runtime replica. PostgreSQL can initially remain locally managed in that container, or become a separate service after an explicit external-database authority mode is implemented. Neither option is currently a configuration-only change.

No deployment Dockerfile or .dockerignore was found in the inspected application/root. docker-compose.postgres.yml is explicitly a noncanonical manual recovery fixture, not the production runtime definition. DockerHub scanning in the application is unrelated to deployment packaging.

Priority definitions:

  • P0 / B01-B14: resolve before a working, safely restartable Linux deployment. Some items need packaging/provisioning rather than application changes.
  • P1 / R01-R07: resolve before unattended operation with persistent data.
  • Conditional / C01-C07: required only for the stated feature or deployment choice. These are not all prerequisites for a headless, single-runtime deployment.

Runtime Map

Component Actual entry points and role
Canonical startup app/runtime_bootstrap.py, app/child_bootstrap.py; isolated interpreter startup and authenticated imports
Lifecycle app/supervisor.py, app/supervisor_instance.py, app/lifecycle_authority.py; admission, ownership, control, manifests and shutdown
Subprocess containment app/owned_process.py, app/process_identity.py
Scanning app/console_runner.py, app/scanner.py; native TruffleHog and Git
Key checking app/keycheck_runner.py, app/keycheckers/; Python provider processes, HTTP and AWS SDK
Database app/postgres_runtime.py, app/db_backend.py, app/scanner_db.py; managed PostgreSQL and normalized runtime schema
Result pipeline app/result_bundle.py, app/result_ingester.py, app/jsonl_projector.py, app/janitor.py
Optional UI app/dashboard.py; supervised read-only Streamlit dashboard
Retired entry points app/app.py:1-13 and app/scan_manager.py:8; do not use as the container application

P0: Deployment Blockers

B01. Replace Windows Path Assumptions, Not Just Environment Variables

Evidence: app/config.yaml:8-12,30-37,70-89,109-120,140-150; app/paths.py:7-8,17-18,46-59,70-110.

The configuration contains D:\truf, S:\postgres-data, S:\scanner-result-bundles, S:\scanner-work, C:\Tools\trufflehog.exe and backslash-based derived paths. POSIX treats a Windows drive path as relative and a backslash as an ordinary filename character. os.path.normpath() does not translate them.

YAML root_dir wins over SCANNER_ROOT_DIR/SCANNER_PROJECT_ROOT; YAML trufflehog_path wins over TRUFFLEHOG_PATH. Several defaults remain Windows-specific even if those YAML values are removed. The managed PostgreSQL DSN has its own precedence and must remain consistent with the selected authority mode.

Isolated reproduction: executing the actual path functions with POSIX path semantics, a config location under /opt/truf/app, and Linux environment overrides produced /opt/truf/app/D:\truf for the root and /opt/truf/app/C:\Tools\trufflehog.exe for TruffleHog. With empty YAML, the root became portable but the default log path still became /srv/truf/runtime\logs. No application was imported or started.

Correction: supply a complete Linux config/profile and make path defaults platform-aware with joins or portable separators. Cover global paths, supervisor instance/status/lock paths, policy assets, caches and maintenance paths. Define and document precedence rather than assuming environment variables override YAML. Preserve the working Windows profile; do not replace backslashes indiscriminately in arbitrary settings or stored data.

B02. Use the Canonical Foreground Entrypoint

Evidence: start_runtime.ps1:18; start_core_runtime.ps1:23; app/runtime_bootstrap.py:24-31,115-129; app/supervisor.py:4468-4481,4878-4882,5000-5001,5277-5278.

The launchers use --background, return after starting a child, and therefore have the wrong lifetime for a container entrypoint. Interactive mode is not automatically disabled without a TTY; EOF can end its loop. Noninteractive mode without autostart is not sufficient either.

Correction: use exec-style startup of the canonical supervisor, without daemonization, with explicit --non-interactive --autostart. Keep -I -S -B and the bootstrap entrypoint binding. Use --no-dashboard for the initial headless deployment. Do not launch workers directly or substitute streamlit run app.py.

Illustrative command contract for the embedded-PostgreSQL option, only after the other fixes and offline provisioning; not executed during this audit:

python3 -u -I -S -B /opt/truf/app/runtime_bootstrap.py supervisor -- --runtime-bootstrap-entrypoint /opt/truf/app/supervisor.py --config /opt/truf/app/config.yaml --non-interactive --autostart --no-dashboard --no-clear --with-postgres

Select sources explicitly. start_core_runtime.ps1 and app/config.linux.yaml use the exact distributed producer set gitlab,dockerhub,huggingface; operational workers such as keychecks are configured independently. --once is not a guarantee that the whole supervised pipeline is a terminating batch job (app/supervisor.py:974).

B03. Fix the POSIX OwnedProcess Identity Handshake

Evidence: app/owned_process.py:1006-1014; app/scanner.py:11479-11495,1825-1842.

The POSIX handshake returns payload/host identities without creation_time; the payload executable is taken from command text. The scanner passes that identity into its ownership marker, which requires pid, creation_time and executable. Access to the missing field can raise KeyError on the real native-scanner launch path.

Correction: return complete, canonical, verified process identities before startup acknowledgement. Preserve the stdlib-only containment bootstrap and exact identity checks; a PID alone is insufficient. Add a real POSIX handshake-to-marker test, not a mock that supplies the missing field.

B04. Make Nested POSIX Process Containment Actually Contain the Tree

Evidence: app/owned_process.py:442-445,917-921,989-993; app/supervisor.py:1180-1189; app/keycheck_runner.py:2226-2233; app/scanner.py:11479.

An outer payload owns process group A. Its inner containment host inherits A, but the inner provider/native payload creates session/group B. Killing A can kill the inner host while leaving B running. The dead host can no longer reliably process its parent pipe and stop B. This affects source restarts and dependency-loss handling inside a still-running container, not only final container termination.

Correction: implement nested containment with verified tree termination, such as isolated observer hosts with cascading stop/acknowledgement, or an appropriately designed cgroup mechanism. Do not replace identity-based ownership with name-based process killing. An init/reaper alone does not fix this defect.

B05. Integrate Container Signals and a Real Shutdown Budget

Evidence: app/supervisor.py:2338-2361,3447,3551,5293-5305; app/postgres_runtime.py:824; app/config.yaml:168-170,185.

The supervisor handles KeyboardInterrupt but does not register a SIGTERM handler. Docker's normal stop signal therefore is not wired into coordinated shutdown; PID 1 also has special Linux signal semantics. The shutdown path includes admission closure, pipeline draining, sequential child stops and PostgreSQL shutdown. A short container grace period can interrupt that protocol. Locally managed PostgreSQL is daemonized through pg_ctl, so reaping also needs attention.

Correction: connect SIGTERM to the existing STOPPING/shutdown event flow, provide an init/reaper, and forward signals to the supervisor rather than indiscriminately to its whole process tree. Size stop_grace_period from the total measured shutdown deadline, not only the PostgreSQL timeout. Current configuration includes a 120-second PostgreSQL shutdown timeout and a 180-second background-shutdown timeout; neither proves that a particular total container grace is sufficient.

An explicit SIGINT stop signal could be an interim tested workaround, not a substitute for the complete TERM/PID1/nested-process fix. An unconfirmed stop intentionally enters FAILED_HOLD; no finite grace period can guarantee a clean outcome there. Preserve authority, expose failure and require an escalation procedure instead of releasing locks optimistically.

B06. Make Restart Safe Across Container PID Reuse

Evidence: app/supervisor.py:5067; related identity and instance handling in app/supervisor_instance.py and app/process_identity.py.

Persisted, otherwise valid instance metadata combined with a reused PID can block a new foreground supervisor. PID reuse is particularly predictable across fresh container PID namespaces.

Correction: reconcile stale instance state using exact identity under the correct authority lock, and/or place runtime-instance/control metadata in deliberately ephemeral storage. Keep persistent business data separate from per-instance PID, control, shutdown-receipt and session state. Never delete a lock or metadata merely because it is old. Verify that the previous runtime cannot still own the database before recovery.

B07. Provision Non-Root Ownership, Private Paths and a Usable Lock Root

Evidence: app/runtime_security.py:391-400,651-685,842-889,989-998; app/postgres_runtime.py:258-271.

POSIX private-file policy requires ownership by the effective UID and no group/other permissions. Lifecycle preflight is read-only and requires configured directories to exist already, including application/root paths, runtime data, bundle subdirectories and PostgreSQL paths. A fresh named volume or a default root-owned Docker secret does not automatically meet this contract. The PostgreSQL process inherits the runtime UID; Linux PostgreSQL cannot run as root.

The authority lock root is hardcoded to /var/lock/truf. /var/lock is a symlink on many Linux images and conflicts with the no-symlink policy; creating it as an unprivileged user is another problem.

Correction: choose a stable non-root UID/GID, provision all required paths and file ownership before normal startup, and use a real prepared private authority directory, for example /run/truf/authority. Make its location explicit instead of depending on a distribution's /var/lock layout. Data/config modes normally need owner-only access. Verify the actual behavior of named volumes, secret mounts and Docker Desktop mounts; do not solve this with chmod 777 or privileged mode.

Provisioning may need a separate controlled initialization step. The steady-state application should not require root, and startup should retain its fail-closed validation rather than silently repairing arbitrary mounted data.

B08. Separate Executable Trust Policy From Data-File Hardening

Evidence: app/runtime_security.py:651-685; app/lifecycle_authority.py:432-443,636-639; app/migrate_runtime_safety.py:2502-2503,2508-2521.

The current hardener sets every POSIX file to 0600, removing native executable bits. Conversely, ordinary system-installed 0755, root-owned Git/TruffleHog executables do not satisfy the current exact-private manifest policy. The offline hardener also hardens each file's parent and runtime trees; pointing it at a system binary can attempt to harden a shared system directory.

Correction: define and verify executable permissions separately, preserving x and a trusted owner. Choose deliberately between private executable copies and an explicit immutable-system-binary trust policy. Account for the complete Git installation and PostgreSQL libraries/helpers, not just one binary. Do not run the existing recursive hardener over /usr/bin or blindly apply 0600 to a native runtime. Test hardening idempotency without breaking execution.

B09. Package the Code Authority and Isolated Import Layout Correctly

Evidence: app/lifecycle_authority.py:21,38-70,378-443; app/runtime_bootstrap.py:47-83; app/child_bootstrap.py:177-195.

The manifest unconditionally includes ../runtime/check-openrouter-keys.ps1, ../start_runtime.ps1 and ../stop_runtime.ps1. Excluding all PowerShell or all runtime/ content before changing this contract can break authentication even on Linux. Application-tree symlinks and cached application bytecode are rejected. Creating an ordinary virtualenv under the application tree can introduce both.

Correction: make the external authority-file set OS-aware, or retain these inert first-party files at the required relative locations until that change is made. Ship all required application modules and detector/policy assets, without application .pyc/__pycache__ artifacts or symlinked application paths. Keep the dependency environment outside the inspected app/ tree. Install dependencies for the exact interpreter used by isolated bootstrap; arbitrary PYTHONPATH and user-site packages are not a substitute.

Use immutable releases with a full controlled restart. Live edits to mounted code/config are incompatible with manifest drift detection (app/supervisor.py:3045).

B10. Decide and Implement the PostgreSQL Authority Topology

Evidence: app/postgres_runtime.py:112-130,249-255,642-671,1335; app/db_backend.py:42-101; docker-compose.postgres.yml:1-20.

Current managed mode expects loopback, local PostgreSQL executables, a local data directory, bound cluster identity and an inspectable local postmaster process. Changing the DSN host to a Compose service name does not implement external PostgreSQL support. The URL parser also rejects query parameters, so appending ?sslmode=... is not currently a supported TLS configuration route.

Option Required work
Locally managed PostgreSQL in the runtime container Preserve a shared PID/network namespace and lifecycle owner. Supply Linux PostgreSQL executables at the currently fixed runtime/postgres/pgsql/bin layout, or make the binary paths configurable. Keep PGDATA separate from binaries and use the compatible non-root UID. Include init/reaping and coordinated database shutdown.
Separate PostgreSQL container/service Add an explicit external authority/backend mode across postgres_runtime.py, DSN validation, supervisor lifecycle and readiness. It must not require local postmaster PIDs/data paths/binaries or attempt local start/stop. Preserve authenticated endpoint/cluster identity checks, fencing and schema readiness. Define explicit TLS settings if required.

Simply disabling authority, process or endpoint checks is not an acceptable implementation. A pre-existing PostgreSQL instance can be observed without being owned; do not assume the supervisor will stop it. maintenance-start returns after startup and is not a PostgreSQL container service entrypoint (app/postgres_runtime.py:1463).

Keep docker-compose.postgres.yml separate: it is profile-gated, uses restart: no, Windows bind paths and a deliberately noncanonical endpoint. Its postgres:16 image is not evidence of the version required by the authoritative cluster. Do not silently promote this recovery database to production authority.

B11. Add an Explicit Offline Provisioning and Data-Migration Procedure

Evidence: app/postgres_runtime.py:321-356,400; app/scanner_db.py:238,5145-5177,20520-20530; app/migrate_runtime_safety.py:2971-3008.

bootstrap_cluster_identity() does not run initdb; it expects an existing cluster and executable set. It rejects supervisor metadata, postmaster.pid and a listening endpoint. The identity binds paths, binaries and cluster identity, so an old Windows identity file must not be reused as a Linux authority binding.

Workers also require the runtime safety schema and a valid postgres-normalized-v2-authority final-cutover marker with evidence. A fresh PostgreSQL service reporting pg_isready is not an application-ready database.

Correction: distinguish two offline phases. First, initialize/restore and bind the embedded cluster identity while the target PostgreSQL server is stopped. Then run database schema/cutover work with PostgreSQL available in controlled maintenance mode but all normal sources/pipeline workers stopped. Use --initialize-base for a genuinely fresh installation, not as a substitute for understanding an existing dataset. Complete the applicable normalization, projection reconciliation and final-cutover checks before admitting workers.

Determine the real source/target PostgreSQL versions before transfer. Prefer a planned logical dump/restore for the Windows-to-Linux move unless another backup method is explicitly validated as compatible; do not assume copying Windows PGDATA works. Back up and transfer matching result bundles/projection data as well. Review legacy absolute Windows locators and use the applicable migration/reconciliation paths, not blanket database string replacement.

app/migrate_layout.py:18,280 and the legacy spool default in app/migrate_runtime_safety.py:74 also contain host-specific paths; do not use their defaults as Linux provisioning instructions. Optional pg_trgm creation is attempted defensively, not a proven unconditional startup prerequisite. No live migration should be run until restore/rollback and exclusive ownership are established.

B12. Build a Complete, Reproducible Linux Dependency Set

Evidence: app/requirements.txt:1-9; app/requirements-keycheckers.txt:1-4; app/child_bootstrap.py:24-34,177-195; app/keycheck_runner.py:2168-2175; app/lifecycle_authority.py:242-291,378-390; app/scanner.py:11168,11403-11405,11932,12993,13012,13834.

  • Installing only requirements.txt misses boto3/botocore; isolated bootstrap requires them for every keycheck-provider. Installing only requirements-keycheckers.txt misses PyYAML. Install the union or define complete, tested profiles. zstandard is currently required for every scanner bootstrap, not just an enabled Docker source.
  • Pin a tested, patched CPython minor and dependency resolution, including a compatible boto3/botocore pair. The host has Python 3.12.3, but that is neither a recommended security patch level nor a Linux compatibility result. Archive extraction uses version-sensitive tarfile APIs; a strict minimum of 3.12 was not established because some security APIs were backported.
  • Supply Linux TruffleHog and full Git for the target architecture, with release/checksum verification. The resolver currently prefers a present private runtime/git/cmd/git.exe without an OS check. Exclude Windows vendor binaries and make the Linux resolution explicit. Git and TruffleHog are required by the current global manifest even for a restricted source set.
  • Ensure TruffleHog's subprocess PATH resolves the same intended Git installation as the manifest, including its HTTPS transport helper. Copying a lone git executable is insufficient.
  • Validate native wheels/ABI and stdlib ssl, sqlite3, zlib, bz2, lzma, plus CA certificates. Check shared-library requirements of the selected TruffleHog and, if embedded, PostgreSQL build. Windows wheels and extensions cannot be reused. A glibc-based image is a simpler first target than assuming Alpine/musl compatibility.
  • psycopg[binary] with a supported wheel does not automatically require libpq-dev/pg_config; source-build requirements depend on wheel availability. The zstandard CLI does not replace the Python package. Go/CGO are build dependencies only if the selected TruffleHog is compiled from source.
  • Validate the actual TruffleHog CLI and output contract: the wrapper uses Git/Docker/filesystem/HuggingFace paths, archive flags and branch/SHA/local-development options. A successful version probe alone does not validate these. The parser also uses the finished scanning diagnostic to distinguish complete work from an incomplete command.

B13. Prevent Secrets and Host State From Entering the Image

Evidence: root .gitignore; root/application file layout; app/runtime_security.py:872-889,989-998; manifest exceptions in app/lifecycle_authority.py:66-70.

The working directory contains credential files, credential backups, databases, scan output, keycheck output, state, logs, temporary data and bundled Windows tools. Their contents were not read for this audit. .gitignore does not protect a Docker build context.

Correction: create .dockerignore plus an allowlisted COPY strategy. Exclude real .env*, secret/backup/lock variants, databases including WAL/SHM, findings/results, queues, logs, state, scratch data, caches, local tool state and Windows vendor distributions. Account explicitly for the currently manifested first-party runtime scripts instead of blindly excluding them. Do not bake credentials into layers, build arguments or a committed Compose file.

Provide non-secret example configuration and inject secrets at runtime. Test owner and mode compatibility under B07: a root-owned 0444 secret mount does not satisfy the current effective-UID private policy. Keep code/config read-only after provisioning where feasible. The optional credential-writeback workflow is covered separately in C04.

B14. Persist the Whole Data Pipeline and Preserve Filesystem Semantics

Evidence: app/config.yaml:11-19,34-37,79-81,109-120,140,148; app/result_bundle.py:67-85,331-354,388-403; app/jsonl_projector.py:109-153; app/keycheck_runner.py:2201-2203.

PostgreSQL is not the only durable store. Result reservations refer to payload bundles on disk. Bundles are flushed/fsynced and atomically published from tmp to ready under one root; the commit reference is relative to that root. Losing the bundle volume while keeping PostgreSQL can lose pending ingestion inputs. Output publication also has file-level state and locks.

Data class Deployment treatment
PostgreSQL data Durable volume; version-compatible backup/restore; one authority
Result bundles Durable volume with tmp, ready and quarantine together; preserve atomic rename/fsync behavior
Results and keycheck outputs Preserve JSONL, rotation/publication state and needed replay inputs; maintain owner-only access
Queue/state/resolver and SQLite caches Classify individually; persist required resume state, distinguish rebuildable caches from authoritative PostgreSQL data
Work clones/download/extraction scratch Separate bounded writable storage; do not assume it fits memory-backed tmpfs
Control/PID/session metadata Deliberately per-instance storage or exact-identity reconciliation; do not restore stale runtime identity as business data
Logs Bounded retention or a secure collector; do not grow the container writable layer indefinitely
Config and secrets Separately provisioned/injected; not bundled into data/image backups indiscriminately

Do not mount bundle tmp on a different filesystem from ready. Validate ownership, no-symlink policy, locks and durable atomic publication on the actual storage driver. Do not assume Windows binds, SMB or NFS have the required POSIX behavior. Named volumes backed by a suitable native Linux filesystem are the safer first choice, but still require testing.

Mounting only the old runtime/ directory misses the configured S: locations. Root-level scanner.db and old output files are not automatically the authoritative deployment dataset. Establish the transfer inventory before copying.

Current capacity settings include a 3 GiB bundle budget, a 192 MiB per-event cap, a 2 GiB projection backlog budget and a 20 GiB free-space floor. Provision space for concurrent work, PostgreSQL/WAL, bundles and outputs, or deliberately retune those policies. A small default container disk can refuse scans even while the process is healthy. Test a coordinated database-plus-bundle restore, not just pg_dump in isolation.

P1: Unattended Operation

R01. Retain Ownership When Failed Startup Cleanup Cannot Confirm Exit

app/supervisor.py:1200-1208 swallows errors from terminate/wait and clears the retained process reference. Preserve the owner/identity and enter the existing failed-hold path if rollback cannot prove that a child stopped. Otherwise a failed start can leave an untracked process. Verify this after the POSIX containment correction.

R02. Preserve Signal/OOM Exit Status

app/owned_process.py:930 attempts to reproduce a signalled payload exit through signal handling, but installing a handler for SIGKILL is invalid. A payload terminated with -9 can be reported as host exit 127. Correct the signal-exit reproduction and test OOM/SIGKILL separately from ordinary program failures; do not label this as a container memory-policy fix by itself.

R03. Unify Foreground Shutdown Completion

app/supervisor.py:5327-5339 writes shutdown receipts only for background children, while POSIX inspection of a non-child process cannot retrieve its exit code. This can make the existing stop workflow report failure after a foreground container runtime has actually exited. Define one completion protocol for both launch modes. Until then, authenticated --cmd shutdown plus independently waiting for the supervisor/container to exit is different from trusting the shutdown acknowledgement alone.

R04. Add Dependency-Aware Health and Recovery Semantics

app/supervisor.py:5225,1285,3361-3367,3551 distinguishes activation, held workers, initial ingester readiness and failed-hold state. A live PID, ACTIVE handshake, Streamlit health response or PostgreSQL TCP response is not enough to certify the pipeline.

Expose a read-only machine health result covering supervisor phase, authenticated database/cluster identity, schema/cutover readiness, ingester/projector heartbeat or singleton lease, configured required workers, storage/backlog health and any unrecoverable hold. Allow an honest startup period without admitting work prematurely. The initial source gate opens once; explicitly decide whether later dependency loss should close it or allow bounded asynchronous intake, and test that policy through outage, backlog exhaustion and recovery.

Distinguish degraded readiness from a dead process. A Docker healthcheck alone does not restart an unhealthy container; a restart policy normally responds to process exit. Do not configure blind health-triggered replacement that discards a FAILED_HOLD ownership dispute.

R05. Replace Host Resource Assumptions With Container Budgets

app/scanner.py:291,1221,1279-1290,11460-11472; app/owned_process.py:965; app/config.yaml:90-95,145.

Windows Job memory/CPU/priority settings are not enforced by the POSIX branch. The configured TruffleHog Job memory limit is 4 GiB; putting that value in YAML does not create a Linux limit. CPU counts may describe the host rather than the effective quota. The optional bonus scan slot uses Windows resource counters and fails closed on Linux.

Set explicit workload concurrency, cgroup CPU/memory/PID budgets and storage limits. Account for PostgreSQL, Python, native payloads, one containment-host process per owned job and threads. A whole-container memory cap is not equivalent to the old per-tree Windows Job cap. Explicitly disable the bonus slot initially or implement quota-aware Linux telemetry without weakening admission safety. Derive limits from representative tests rather than multiplying configured maxima into an asserted minimum RAM requirement.

R06. Make Logs Observable Without Depending on Ignored Python Variables

app/supervisor.py:240 and app/keycheck_runner.py:2168-2175 launch isolated interpreters. -I ignores PYTHONUNBUFFERED and PYTHONIOENCODING; adding those variables to Compose is not a reliable buffering/encoding fix. Use explicit interpreter flags such as -u or configure streams, and verify child output under the container locale. Retain necessary file logs with rotation/collection and keep credential-bearing output private and redacted from generic health messages.

R07. Enforce Provider Process Deadlines

app/keycheck_runner.py:2263 waits for the provider process without a process-level timeout. A provider can outlive a scheduler deadline even when individual HTTP operations have timeouts. Add a bounded process deadline/watchdog using corrected owned-tree termination; preserve partial durable results and lease recovery. A liveness check must not silently treat a stuck provider as productive work.

Conditional Requirements

C01. Authenticated Git Needs a POSIX Askpass Helper

Applies when: Git requests credentials, including relevant Git/HuggingFace/package paths.

app/scanner.py:11426-11433 unconditionally creates a Windows git-askpass.cmd using batch syntax when TRUF_GIT_TOKEN is set. Callers include app/scanner.py:12384-12388,12600-12605. Anonymous clones can hide the defect.

Provide a POSIX helper with a correct interpreter/shebang, LF and private executable permissions; retain the Windows branch. Read the token from the controlled environment, not a credential-bearing URL or argv. If the work volume is noexec, a prepackaged trusted helper outside that scratch volume is preferable to weakening the whole volume. Coordinate this with executable hardening and immutable code policy. Test using fake credentials and a local/mocked Git interaction.

C02. Dashboard Publication Requires an Explicit Security Design

Applies when: the UI must be accessed from outside the runtime container.

app/.streamlit/config.toml:1-4 sets 127.0.0.1:5000; app/supervisor.py:3608-3624 and app/dashboard.py:2528-2538 independently reject non-loopback hosts. Dashboard launch also requires authenticated supervisor-child context. Changing only Streamlit configuration or publishing a Docker port will not make the in-container loopback listener reachable.

Either add an explicit secured container-bind mode in both guards, or use a proxy/tunnel in the same network namespace that can reach the existing loopback listener. A normal separate bridge-network proxy cannot reach it. Add access control and TLS at the appropriate boundary; read-only database access does not make scan/credential observability safe for public exposure. Preserve the supervised launch contract.

Keep the control interface 127.0.0.1:8765 private (app/supervisor.py:3726-3733; app/config.yaml:182-183). Use authenticated bootstrap commands such as --cmd status, --cmd shutdown and --attach through docker exec in the same container and UID. Do not publish port 8765 or broadly remove loopback restrictions. This entire UI exposure change can be deferred by using --no-dashboard.

C03. Restricted Egress, Proxies, Custom CA and IPv6 Need Explicit Support

Applies when: deployment cannot use the existing direct outbound network behavior.

  • app/scanner.py:374-375,560,11397 uses different routing for discovery and downloads/native Git/TruffleHog. Some paths deliberately remove proxy environment variables or use direct clients. Configured download-proxy flags do not themselves implement that routing. HTTP_PROXY alone is not enough for a proxy-only deployment.
  • app/scanner.py:480-484 accepts proxy formats that differ from app/keycheckers/keycheck_common.py:2200; OpenAI/Gemini/OpenRouter also have duplicated parsers. Unify or explicitly constrain all formats and fallback behavior, including escaped credentials and ambient environment proxies. Add PySocks/requests[socks] only if SOCKS is required; it is not currently declared.
  • app/scanner.py:13932 ignores ambient CA settings on the downloader path. Plumb the trusted CA explicitly for corporate interception/custom trust instead of disabling verification. app/scanner.py:105 forces IPv4 by default; consider SCANNER_FORCE_IPV4=0 only if the target network needs IPv6 and the path is tested.
  • Allow the selected sources' API, registry/CDN, download and redirect destinations, plus configured provider/resolver endpoints. DNS/private-address protections can reject destinations (app/scanner.py:9340-9389,9416). Preserve SSRF safeguards while making any intended private-network exception explicit. Provider resolution may contact DeepSeek/Z.ai/Qwen/Kimi depending on configured order (app/keycheckers/provider_resolution.py:64-156).

Use mocks/local fixtures for proxy, TLS, redirect and DNS tests. Do not use recovered credentials to test network readiness.

C04. Offline Credential Writeback Needs a Different Mount Contract

Applies when: sync_alive_github_tokens.py will update the canonical secrets file.

app/sync_alive_github_tokens.py:120-138,162-175,257-262 requires verified stopped authority, an adjacent lock, a same-directory private temporary file and atomic replacement. A read-only secret can be suitable for normal runtime but not for this maintenance operation. A single-file bind mount also cannot be assumed to support replacement of its mountpoint.

Choose a separate external/offline rotation workflow or a private writable containing directory for this maintenance mode. Preserve atomic publication and exact canonical path checks. Do not make all application code/secrets permanently writable merely to support an optional operation.

C05. Multiple Replicas or Split Workers Require New Coordination

Applies when: scaling the runtime or moving authenticated workers into separate containers.

app/runtime_security.py:502 uses filesystem-scoped locks. app/lifecycle_authority.py:641 relies on local process verification, metadata and control reachability. app/jsonl_projector.py:140-153 has both a singleton database lease and a file lock; ingester/projector are not arbitrary scalable workers.

Local lock paths in separate container filesystems do not provide a cross-container exclusion guarantee. Worker authentication also does not become remote authentication simply because a directory is mounted. A scale-out design needs explicit shared/distributed fencing, control/identity transport, data ownership and volume semantics. Until then, use one owner and one runtime replica, prevent the old host runtime from remaining active, and do not suggest docker compose --scale as an operational option.

C06. Decide Whether Windows Archive-Name Rules Remain Policy

Applies when: Linux deployments should accept archive entries valid on POSIX but invalid on Windows.

app/scanner.py:13760-13775 still rejects Windows reserved names, colons and trailing dot/space on Linux. This is a policy limitation, not an unconditional container boot defect. Either document it unchanged or separate OS-specific name restrictions. Keep traversal, entry-type, size and expansion-budget protections intact.

C07. Extend the Manifest if First-Party Linux Native Modules Are Added

Applies when: native .so application modules are introduced inside the authenticated application tree.

app/lifecycle_authority.py:21 includes .pyd but not Linux .so in application import suffixes. Add the appropriate native extension suffix policy and tests when such first-party modules exist. This is not a reason to add every installed dependency to the current application-code manifest or to block the present pure-Python application solely on this basis.

What Is Not Required

  • No Docker daemon, Docker socket mount, Docker CLI, DinD, privileged container or image-architecture emulation is needed for the inspected DockerHub scanning path. It reads image content rather than executing the image (app/scanner.py:12980,13645).
  • DOCKER_CONFIG is an authentication input, not a need for Docker Desktop. Recovery intentionally rejects implicit keychain/helper assumptions; preserve the managed credential pool (app/scanner.py:4790,13433-13456).
  • npm/PyPI content is scan data. Node.js and a browser are not runtime dependencies of these scanner/keychecker paths. AWS CLI, gcloud and az are not required merely because those providers are checked.
  • Go is not needed in the final image when supplying a compatible prebuilt TruffleHog. GCP keychecker RSA handling does not establish a dependency on Google SDK/cryptography/openssl CLI (app/keycheckers/gcp/gcpKeycheck.py:288).
  • Existing POSIX /proc identity support, flock, PostgreSQL command branches, activation/STOPPING states, leases and owned-versus-observed database semantics should be preserved and completed, not rewritten wholesale.
  • A general path-case rename or repository-wide CRLF rewrite was not justified. Fix genuinely platform-specific helpers and configured paths instead.

Documentation and Operational Corrections

Create a deployment Compose definition separate from the recovery fixture, an allowlisted image build, a non-secret Linux configuration example, an ownership/volume provisioning procedure, and a backup/restore/upgrade runbook. These are missing deployment deliverables, not files generated by this audit.

Document the exact source profile and foreground lifecycle, explicit health semantics, stop/restart deadlines, singleton restriction, volume classes, secret maintenance, pinned versions and supported architecture. Remove Windows freeze-counter/diagnostic scripts from the Linux launch chain (start_freeze_counters.ps1:27); keeping a script as inert manifest data is different from executing it.

Correct any assumption that provider checks are free/read-only readiness probes. app/KEYCHECKERS.md:44,87,109 must be reconciled with actual provider defaults. Qwen and several other providers can perform generation by default (app/keycheckers/qwen/qwenKeycheck.py:544); AWS/Replicate/Azure paths can probe IAM, resources or RBAC, with additional optional model requests. TruffleHog no-verification does not disable the separate keychecker subsystem (app/config.yaml:136). Healthchecks and image smoke tests must not invoke those real credential checks.

  1. Choose Linux distribution/CPU architecture, PostgreSQL topology, UI requirement, source set, egress policy, non-root UID and storage/resource budgets. Confirm which existing data is authoritative and define rollback.
  2. Fix portable paths, POSIX identity/containment, signal/restart behavior and executable/ACL policy. Add focused offline Linux regression tests while preserving the current Windows contracts.
  3. Assemble the pinned dependency/native-tool image and code-authority layout. Add .dockerignore, a foreground entrypoint contract, private provisioning and a separate deployment Compose definition. Keep one runtime replica.
  4. Build and exercise a disposable Linux environment with fake credentials and local fixtures. Validate process lifetime, shutdown, restart, permissions, imports, native CLI contracts, health and resource limits before touching real data.
  5. Implement the chosen PostgreSQL mode. Rehearse fresh initialization and a restored dataset, schema/cutover migration, bundle/projection reconciliation and coordinated recovery. Embedded identity binding needs a stopped target PostgreSQL; SQL migration needs PostgreSQL available with normal workers stopped.
  6. Complete the conditional features actually needed: authenticated Git, secured UI, restricted-network support or credential maintenance. Defer unrelated scale-out work.
  7. Perform a controlled real-data cutover only after backup/restore rehearsal, exclusive ownership and rollback checks. Start with conservative concurrency and verify health/backlog behavior before increasing load.

Acceptance Tests

Area Required evidence before claiming support
Build and ABI Build on each supported target architecture; resolved dependency check; stdlib/native imports through the intended isolated interpreter; TruffleHog/Git help/version and library compatibility
Authority image layout No rejected application bytecode/symlinks; required manifest files/assets present; stable code/config hashes; dependencies outside the application tree
Paths and permissions Linux path resolution for every configured directory/file; non-root fresh-volume provisioning; correct private owners/modes; executable bits survive hardening; usable real lock root
Process identity Real POSIX host/payload handshake can create the scanner owner marker; exact identity survives normal lifecycle checks
Process containment Nested provider/native grandchildren terminate on source restart, parent death and failed startup; no orphan payloads or unreaped zombies
Container lifecycle Foreground no-TTY autostart; SIGTERM during active work; confirmed drain/stop; bounded ordinary shutdown; explicit failed-hold escalation; restart after reused PID/stale metadata
Database Fresh schema and valid cutover; wrong cluster rejected; offline identity rebinding; authenticated outage/recovery; external mode, if chosen, has no local PG start/stop dependency
Durability Container recreation preserves reservations/bundles/publications; interrupted atomic publication recovers safely; coordinated PG-plus-bundle backup can actually be restored
Limits Full disk, low free-space floor, bounded backlog, constrained CPU/RAM/PIDs, OOM exit status, bonus-slot denial and provider process deadline
Network Mocked direct/proxy/SOCKS-as-needed, parser formats, CA, IPv4/IPv6-as-needed, redirects and DNS/SSRF behavior
Git POSIX authenticated askpass with fake credentials, private executable permissions and the selected noexec work-volume arrangement
Archives gzip/zstd/PAX, malformed archives/missing decoders, entry-name policy and traversal/size protections on the chosen patched CPython
Optional UI Reachable only by the intended secured path; supervised authentication intact; health distinguished from full pipeline readiness; control port not published
Safe probes No provider generation, credential validation or production endpoint activity from build/health tests

Existing tests to extend/select carefully:

  • tests/test_owned_process.py:79-82,98: the identity assertion covers PID, and tree cleanup coverage is Windows-specific.
  • tests/test_temp_owner_child_safety.py:49-59: a mock supplies complete identity and can hide the real POSIX handshake defect.
  • tests/test_supervisor_safety.py:978: a mocked zero exit code can hide the foreground completion gap.
  • tests/test_pipeline_postgres_integration.py:69: adapt .exe assumptions to a disposable Linux PostgreSQL setup, not the authoritative host database.
  • tests/test_runtime_security.py:227: add actual container UID/mount/symlink/executable-policy cases.
  • tests/test_api_proxy_routing.py:62,92,215,248: extend mocked routing, proxy-parser and trust behavior.
  • tests/test_docker_codec_recovery.py:72,130,149,175 and tests/test_resource_lifecycle_fixes.py:191: extend codec and cgroup/admission coverage.
  • Some tests exercise installed/native/live paths, including tests/test_docker_codec_recovery.py:666 and tests/test_huggingface_long_paths.py:324. Separate offline tests from explicitly opted-in integration tests; do not run the entire suite against existing data or credentials by default.

Verification Performed and Limits

  • Inspected the application, configuration, entrypoints, dependency manifests, security/process/DB/result-pipeline code, recovery Compose and relevant tests. Findings cite inspected file/line locations; line numbers may move with later edits.
  • Reproduced the Windows-path/config-precedence failure using the actual pure path functions under POSIX path semantics, without application startup or filesystem mutation.
  • Parsed all 61 application Python files with Python 3.12.3 using AST-only analysis: zero syntax errors. This is not an import, dependency, Linux execution or behavior test.
  • Docker CLI was unavailable in this session: docker version --format '{{json .Server}}' failed because the command was not found. This does not prove that the host has no Docker installation or can never run containers.
  • No Docker build, Compose deployment, Linux process integration test or full pytest suite was run. No supervisor, scanner, provider checker or PostgreSQL server was started. Secret contents, real credential checks and database migrations were not used for verification.
  • Only this report was added. The findings identify the correction surface visible from repository inspection; target-platform tests may reveal additional issues. No claim of Docker readiness is made until the acceptance checks pass.