Files
dtf-system/docs/SECURITY_REPORT.md
Cauê Faleiros 7386469404
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 1m18s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images and notify Portainer (push) Successful in 1m26s
docs: move the engineering documents into docs/
Thirteen files at the repository root, seven of them documents. Only README.md
earns a place there; the rest are now in docs/ beside the meeting notes, the
client roadmap and the historical material.

The compose files stay. docker-compose.yml is the path the dtf-cloud Portainer
stack reads, so moving it would break deployment, and Docker resolves a compose
file's relative build contexts against its own directory, so moving the other
two would silently break every build. Both reasons are now written down where
someone would otherwise try it.

Correcting references turned up a live fault: the Portainer stack creation
instructions still named deploy/stack.yaml as the compose path. That file was
removed, so anyone recreating the stack from these instructions would have
failed. It names docker-compose.yml now, with the reason it stays at the root.

ROADMAP.md keeps the paths its closed findings were written with, and says so at
the top. Those entries record where a fault was when it was found; rewriting
them to match a later layout would make the record less true, not more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 17:52:20 -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.