Files
dtf-system/SECURITY_REPORT.md
Cauê Faleiros b329f76378
All checks were successful
Build and deploy / Validate source (push) Successful in 7s
Build and deploy / Integration suite on a real stack (push) Successful in 1m25s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images and notify Portainer (push) Successful in 1m31s
refactor: lay the repository out by role
local/ held six unrelated things under a name that stopped being true once it
became the production runtime: the service, the frontend, the tests, the ops
commands, the container definitions and the dependency lock, 65 files with
nothing to tell them apart.

  app/      the service: api/ routers, core/ for identity, database, models,
            prices and secret loading, and the worker, bootstrap and schema
  tests/    the twelve suites, no longer inside the shipped package
  ops/      backup, readiness, dependency audit, security summary
  infra/    Dockerfiles, gateway templates, ClamAV and storage configuration,
            the requirements and their hash lock
  web/      the Site, Kanban and portal pages with their scripts

deploy/Dockerfile.api now copies app/ alone, so the tests stop shipping to
production; the local image still carries them, because the suites run inside
the stack's network.

Five kinds of reference had to follow, and each was found by something different
rather than by reading. Imports of the form "from . import db" survived a rewrite
that only matched "from .db import". Tests kept relative imports of modules that
had left the package. A mock.patch target names its module in a string, where no
import rewriting can see it. The browser test resolves a fixture by path. And the
release gate's markers pointed at local/runtime.py and local/worker.py, which is
the decay its new marker test exists to catch — it caught it.

Verified from docker compose down -v: the stack starts, all six integration
suites, both browser suites and the twenty-nine unit tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 17:40:57 -03:00

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. Only clean files 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. Larger files remain blocked. Supporting larger files requires a deliberate resource/timeout design, not simply increasing the upload limit.

Run:

docker compose exec -T api python3 -m app.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.

Corrected 2026-09-21: an earlier version of this section described gates the workflow did not contain. The workflow now runs static validation, the integration suite against a live stack, and a blocking Trivy secret scan before publishing. The source preflight is advisory unless ENFORCE_PRODUCTION_PREFLIGHT is set, and image vulnerabilities are reported rather than enforced, because the current bases carry HIGH/CRITICAL findings with no upstream fix. Base images are still mutable tags, not digests. PORTAINER.md holds the authoritative table of what gates and what does not. It 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.