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
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>
183 lines
11 KiB
Markdown
183 lines
11 KiB
Markdown
# 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:
|
|
|
|
```bash
|
|
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](https://github.com/mozilla/pdf.js/security/advisories/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](https://github.com/mozilla/pdf.js/security/advisories/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.
|