11 KiB
Local security closeout
Date: 2026-09-15
Scope and conclusion
This report covers the localhost-only Site, Portal/API, Kanban, PostgreSQL, MinIO, ClamAV, and fake integration worker. It does not approve production use or real Mercado Pago, R2, Tiny/Olist, WhatsApp, freight, or other providers. Malware scanning is a quarantine control; it is not artwork or print pre-flight.
The local stack is healthy and its security regressions pass. Existing orders and the named PostgreSQL/MinIO volumes were preserved. Open dependency and image findings remain and are recorded below; there is no claim of zero vulnerabilities, complete OWASP compliance, or production readiness.
Implemented controls
- Loopback-only published ports, local-only adapter guards, internal API/worker/ database/scanner network, Host checks, cross-origin write rejection, CSP, frame denial, MIME sniffing protection, and no interactive API documentation.
- Escaped untrusted filenames and browser regression coverage with CSP bypassed.
- Expiring, revocable, hashed HttpOnly operator sessions. No Basic credentials or operator password are retained in browser storage.
- Seven-day revocable customer sessions, stronger scrypt password hashes, legacy verification and upgrade, comparable missing-account password work, and login throttling.
- Upload extension/size/count quotas, storage quotas, private UUID object keys,
exact signed multipart
Content-Length, completion/ownership checks, and five-minute signed downloads. - Files start
pending. Onlycleanfiles may be quoted, approved, paid, downloaded, attached as final files, or moved into queue/printing states. Unknown, scanner error, unsafe/encrypted, over-limit, and malware outcomes fail closed. Rejected/error files expire within three days. - Restricted PostgreSQL and MinIO runtime identities provisioned separately from administrator credentials. Containers use read-only filesystems/capability drops where compatible.
- Structured redacted security logs plus a 30-day PostgreSQL event table. Worker health includes outbox progress, scan-thread liveness, and a live ClamAV PING. The alert summary also reports signature age.
- Logout revokes server sessions and clears checkout keys and IndexedDB File blobs across open Site tabs.
- Production delivery definitions use unprivileged application/web users, read-only filesystems, dropped capabilities, external Swarm secrets, private service networking, immutable base images, commit-SHA rollback tags, health-monitored rolling updates, and automatic application rollback. A fail-closed source/configuration gate prevents the current local-only runtime from being released as production.
Verified checks
All results below were observed against the final application source. Smoke and workflow were repeated after the refreshed PostgreSQL 17 image was activated.
| Check | Result |
|---|---|
| Pricing parity against Site JavaScript | Pass: all 4,444 comparisons plus invalid inputs |
| Security regression | Pass: headers, Host/origin, sessions, quotas, multipart signing, throttling |
| Real ClamAV EICAR regression | Pass: rejected and blocked from download/quote; clean control released |
| Smoke workflow | Pass: multipart, ownership, all modes, pricing/freight, payment idempotency, states/history |
| Customer/final-file workflow | Pass: identity, revocation, corrections, secure downloads, invalidation and queue gate |
| Runtime security | Pass: PostgreSQL/MinIO least privilege, hashes, quarantine and scanner failure behavior |
| Retention | Pass: expired bytes removed, live bytes preserved, metadata retained |
| Browser workflow | Pass: end-to-end flow, filename XSS probe, no stored operator password, logout blob cleanup |
| Dependency lock | Pass: 26 exact packages, SHA-256 hashes on every entry, direct-pin parity, hash-enforced image build, pip check |
| Staging readiness gate | Pass with synthetic non-secret metadata in a network-disabled container; unsafe/incomplete regression cases rejected |
| Production delivery definitions | Pass: unit/syntax/YAML checks, Swarm render, immutable-base image builds, non-root read-only web runtime and Host rejection |
| Production source preflight | Expected block: local-only/fake adapters and Docker secret-file loading are not production implementations |
| Production ClamAV runtime | Pass: UID 100:101, read-only filesystem, no capabilities/no-new-privileges, live PONG |
| Repository secret scan | Pass: no HIGH/CRITICAL Trivy secret findings; release workflow enforces the same gate |
Malware-scanner operations
The scanner image is digest-pinned and has no external network route. It uses
the signatures bundled into that image. On closeout it reported ClamAV
1.5.4/28122/Sun Sep 13 06:26:25 2026; the summary calculated 31.8 hours of age,
below the seven-day alert threshold.
The multipart transport supports uploads up to 5 GiB, but SCAN_MAX_BYTES and
ClamAV stream limits release at most 128 MiB by default. As of 2026-09-23 the
API and customer picker reject larger files before transfer. Supporting them
requires a deliberate resource/timeout design, not simply increasing the
transport limit.
Run:
docker compose -f compose.local.yaml exec -T api python3 -m ops.security_status
An exit status of 1 requires review. At closeout, attention was expected because
the regression suite produced two rate-limit alerts, 20 synthetic failed operator
logins, and two rejected SECURITY-EICAR.cdr fixtures. Both rejected fixtures
expire on 2026-09-17. ClamAV was available and its signatures were not stale.
Do not automatically dismiss later alerts: verify filename, timestamp, test run,
scanner availability, and signature age. Treat non-test rejected files, scanner
errors, unexplained authentication bursts, or stale signatures as incidents.
Dependency and image audit
pip-audit inspected the exact packages installed in the hash-enforced rebuilt API
and found no known Python advisories on 2026-09-15. All 26 direct and transitive
runtime packages are pinned with artifact hashes in infra/requirements.lock.
local/lock_dependencies.sh regenerates it in a disposable Python 3.12 container.
This is a point-in-time package-database result, not proof that the dependencies
or image are vulnerability-free. Scheduled lock refresh and audit automation remain
unfinished.
Trivy JSON reports are in output/security/. HIGH/CRITICAL results after the
available custom-image package upgrades were:
| Image | Findings | Qualification |
|---|---|---|
| API | 46 HIGH | 44 Debian records had no fix; Trivy's two Python records named msgpack and setuptools, which pip list confirmed are absent from the runtime. Trivy warned that the third-party SBOM may be inaccurate. |
| Site | 5 HIGH | Nginx package records with fixes listed by Trivy, but the current official nginx:1.28-alpine image/repository did not supply them. |
| PostgreSQL 17 | 30 HIGH, 1 CRITICAL | Nine Alpine library records and 22 records in /usr/local/bin/gosu; presence is confirmed, reachability through this local stack is not. |
| MinIO | 102 HIGH, 6 CRITICAL | Findings span the MinIO and mc binaries and two OS packages. A verified local clean-object archive now exists, but the old pinned release was not changed without a tested version-migration and rollback plan. |
| ClamAV | 0 HIGH/CRITICAL | This scan result is not a guarantee that no vulnerability exists. |
| Production API validation image | 55 HIGH, 3 CRITICAL | 44 records had no fix. Fourteen listed fixes, but the msgpack and setuptools records came from a third-party SBOM and both packages were confirmed absent with pip list; the remaining fixed records are Debian packages. |
| Production web validation image | 52 HIGH, 2 CRITICAL | All 54 records listed fixed Alpine versions. A current approved Nginx base digest must replace the locally available validation base before release. |
Scanner presence is evidence, while exploitability/reachability requires separate
analysis. MinIO and the remaining base-image findings block any production-readiness
claim even though ports are loopback-only here. The production validation reports
are production-api-container-audit.json and
production-web-container-audit.json; both make the release workflow's
HIGH/CRITICAL gate fail. Counts reflect the 2026-09-15 Trivy database and can
change when the advisory database or selected base digest changes.
Production delivery security boundary
The files in deploy/ and .gitea/workflows/ are a guarded delivery mechanism,
not an approval to operate the current application on the public internet.
Updated 2026-09-23: pushes to main run checks only. A manual workflow run on
main requires the source preflight and a configured Portainer webhook before
building. It scans built images before publication; CRITICAL findings block and
HIGH findings are reported. The browser suites run in a required Compose Chrome
container. PORTAINER.md holds the authoritative gate table. A permitted release
publishes both latest and the full commit SHA, then calls the Portainer
webhook. Application/provider secrets are created directly as versioned external
Swarm secrets and never cross the workflow. Rollback selects the prior commit SHA
in Portainer and does not roll back the database.
The gate currently identifies real blockers: production adapters and authenticated payment webhooks are absent, the runtime intentionally rejects production/R2, Docker secret-file configuration is not implemented, approvals are unset, and the existing image findings are unresolved. These checks must be satisfied by implementation and review, not by replacing the checks with permissive values.
PDF.js review
The Site loads version 3.11.174 from a versioned CDN URL. That version is affected
by GHSA-wgrm-67xf-hhpq,
whose documented workaround is isEvalSupported:false; the existing Site call
already sets that value, so the known eval path is mitigated and is not reported
as unmitigated. The newer
GHSA-hq66-cqwq-w95j
affects versions from 5.6.83 up to the patched 6.2.108 and therefore does not
include 3.11.174.
Version 3.11.174 is nevertheless old, remotely loaded, and not a satisfactory long-term dependency posture. Plan a compatibility-tested upgrade and preferably vendor or integrity-pin the asset. Keep script execution disabled and do not weaken CSP merely to support a preview.
Before any staging or production work
- Resolve or formally accept current MinIO, PostgreSQL, Nginx, and Debian image findings. Use the verified local clean-object bundle to rehearse a MinIO migration and rollback; it is not a scheduled, offsite, or production backup.
- Schedule controlled dependency-lock refreshes and exact-runtime audits; review and test every resulting version change.
- Establish a signature update/rebuild process that works without giving the scanner an unrestricted external network route.
- Replace disposable local secrets; add TLS, production session/cookie settings, account verification/recovery, centralized monitoring, and complete backup/ restore procedures.
- Re-run every security, malware, workflow, browser, retention, dependency, and image check in the target staging architecture.