Skip to main content

DB Access Control — the “DB-write = game over” threat boundary

Status: Reference / security posture Date: 2026-06-06 Related: submit-time signed-artifact hardening (signed-recipient invariant) and submit-amount envelope hardening (signed-amount/value caps).

What the submit-time guards do and do not cover

QTG’s on-chain executors sign EVM transactions through a KMS signer (an unconstrained oracle — it signs whatever payload it is handed) and broadcast them. Two layers of submit-time, fail-closed guards re-derive fields from the actual signed transaction and pin them to the trusted live intent before broadcast:
  • Signed-recipient invariant — recipient / router / token / chain-selector pinned (the who).
  • Signed-amount/value envelope — transferred amount, tx.value, and fee legs capped to the approved envelope (the how much).
Together these close the malicious / swapped / buggy signer vector and a tamper of the prepared_action / calldata alone (signed artifact ≠ live anchor → caught fail-closed). They do not close a full-DB-row tamper that rewrites the trusted anchor itself — ctx.intent (recipient + amount) or ctx.node_config (constant-authority + base-unit amount/decimals) — together with the signer output. If the attacker can rewrite the anchor and the signed artifact in lockstep, the equality/cap checks compare tampered-against-tampered and pass.

Why an in-band DB hash is not the answer (theater)

A DB-stored “approved-template hash” or “approved-row hash” verified at submit raises no real bar against a DB-write adversary: the allowlist itself is DB-trusted, and so is the hash. An attacker who can write the movements DB can rewrite the allowlist, the intent, the reserved amount, and any in-band hash together. Adding the hash spends engineering effort without moving the threat boundary — it is security theater.

The accepted boundary

QTG’s positioned threat model is small quant teams protecting against their own runaway / buggy strategy code and a malicious or buggy signernot Fireblocks-tier custody hardened against a DB-internal adversary. Under that model, DB-write access is treated as out of scope (“DB-write = game over”): an attacker with write access to the movements DB has already won, and the proportionate defense is access control around the database, not an in-band integrity token the same attacker can forge.

Proportionate investment (the real mitigations)

Operators who need to raise the DB-internal-adversary bar should invest here, in roughly descending value:
  1. Least-privilege DB roles. The runtime application role should hold only the DML it needs. Schema/DDL, allowlist mutation, and approval-authority tables should be owned by separate roles the app role cannot assume. No shared superuser for the app.
  2. Separate approval authority. Approval / allowlist changes should require a credential path distinct from the runtime app credential, so a compromised app credential cannot self-approve or widen the allowlist. (Operationally: a different role, ideally a different human/automation boundary.)
  3. Immutable / append-only audit. Ship audit_events / audit_outbox to an append-only or external sink the app role cannot rewrite, so a DB tamper is at least detectable after the fact even if not preventable.
  4. Network isolation + credential hygiene. The DB reachable only from the runtime network segment; rotate DB credentials; no DB credentials in shared logs or images; TLS in transit.
  5. Backups + point-in-time recovery as the recovery floor if a tamper does occur.
These are deployment / infrastructure controls, not application code. They are the correct place to spend effort for the DB-internal-adversary class — the submit-time guards already cover the malicious-signer class that application code can close.

Residual documented in the amount-envelope spec

Per the submit-amount envelope hardening design (§3/§8), the OFT (Stargate/USDT0) and ERC20 amount ceilings are anchored to compiled node_config base-units (sufficient vs the malicious-signer threat), and CCTP token_decimals is read from node_config. A node_config-only DB tamper could inflate those ceilings (a bounded over-transfer to the still-pinned recipient). That is the same DB-write boundary described here — accepted, not closed by in-band code.