Deployment Hardening Guide
This page covers three concrete hardening artifacts and how to apply them. For enterprise-readiness context (key management, compliance posture, signer rotation), see Enterprise Readiness.Background
QTG is a self-hosted movement control plane. The operator deploys the stack; the copy-and-run scripts below are the security posture. The foundational threat model is in DB Access Control Boundary: the accepted boundary is “DB-write = game over.” The controls here do not eliminate that boundary — they shrink blast radius and raise the attacker’s effort floor.Artifact 1: Least-Privilege DB Role
File:infra/sql/qtg_least_privilege_role.sql
What it does
Creates aqtg_app PostgreSQL role that can only do what the runtime application needs:
SELECT / INSERT / UPDATE / DELETEon all app tables inpublic(global bucket) andqtg_network(per-network bucket)USAGE + SELECTon sequences (required forSERIAL/GENERATEDcolumns)- Cannot execute DDL (
CREATE,ALTER,DROP,TRUNCATE) - Cannot manage roles (
NOCREATEROLE,NOINHERIT) - Cannot access schemas it does not own
qtg) remains separate and is used only by Alembic migrations — never in the runtime app. This maps directly to DB Access Control Boundary § Proportionate investment, item 1.
Schema layout
How to apply
Adding new tables
When a new Alembic migration adds a table, add a correspondingGRANT ... ON TABLE ... TO qtg_app in the SQL file (or rely on the ALTER DEFAULT PRIVILEGES clause at the bottom of the script, which auto-grants for tables created by the qtg DDL role going forward).
Future hardening
The audit tables (movement_events, registry_audit_events, etc.) currently receive full DML. A future pass can add an audit_writer role that has INSERT-only access, so the runtime app cannot rewrite audit history even if compromised.
Artifact 2: Hardened Compose Overlay
File:infra/docker-compose.hardened.yml
What it does
A Docker Compose overlay that tightens every service in the base compose without modifying it. Use it by passing both files todocker compose:
Notes:
postgresdoes not getread_only: truebecause the official postgres image writes to paths in the container root that vary across patch releases. Capability tightening andno-new-privilegesstill apply.ccip-sidecarretainsNET_ADMINbecause the base compose already adds it for network namespace operations.- The Python app containers (
qtg-mainnet,qtg-testnet) write nothing to the root filesystem at runtime;read_only: trueis safe. - The
user: "1000:1000"directive takes effect once the Dockerfile creates the user. AddRUN adduser --uid 1000 --disabled-password --gecos "" qtguser && chown -R qtguser /apptoinfra/Dockerfilebefore running hardened mode in production.
Why not modify the base compose?
The base compose works for local development without hardening overhead. The overlay pattern lets developers run the base compose without friction and deploy hardened compose in staging/production without maintaining two diverging files.Artifact 3: Local Dependency Audit Gate
File:scripts/security_audit.sh
What it does
A local shell script that:- Lockfile-drift check — runs
uv lock --checkto verifyuv.lockis in sync withpyproject.toml. A drifted lockfile means the running app may not use the versions you pinned. - CVE scan — runs
uvx pip-auditagainst the exported locked dependency set. Fails non-zero if any known, non-ignored CVE is found.
How to run
Makefile:
Why local-only (not a CI workflow)?
GitHub Actions CI is disabled until release (workflows switched toworkflow_dispatch in commit 82a80a0e). The script is the gate. Run it:
- Before every
uv lockrefresh (catch newly disclosed CVEs) - Before releasing to staging/production
- After adding/bumping a dependency
Accepting a false-positive CVE
Ifpip-audit flags a CVE that you have assessed and accepted (e.g., only affects a code path you do not use, or is disputed), add it to the IGNORED_VULNS map in the script:
Current CVE floor
Thepyproject.toml already sets explicit security floors from the 2026-06-10 audit:
The audit script enforces that a
uv lock refresh cannot silently drop below these floors.
Applying Everything Together (Quick-Start)
Cross-References
- Threat model: DB Access Control Boundary
- Enterprise readiness (key management, compliance): Enterprise Readiness
- Security audit (prior wave): commit
f122b3bd(H1–H5 secure defaults, dep CVE floors, dashboard opt-out gating)