Skip to main content

Enterprise Readiness

This page is a single-stop reference for a procurement reviewer or a DevOps / security engineer evaluating quant-transfer-guard (QTG) for deployment. Every claim below is grounded in the shipping source tree and CI configuration — file paths are given so you can verify each control yourself. QTG is positioned for small quant teams that need to protect themselves against their own runaway or buggy strategy code and against a malicious or buggy signer — not as a Fireblocks-tier custody product hardened against a database-internal adversary. Read Known Limitations & Threat Boundary before deployment; the accepted boundary is stated explicitly there. For the narrative defense-in-depth walkthrough, see the Security Model. This page is the condensed, verifiable control inventory.

1. CI Gates

QTG ships a suite of GitHub Actions workflows under .github/workflows/. Each enforces a single invariant; together they form the merge gate.
Status note for reviewers. The repository’s CI auto-run triggers are currently set to workflow_dispatch (manual) while the project is pre-release and GitHub Actions minutes are conserved. The gates below are run locally on every change before merge and are restored to push / pull_request triggers at release. The workflow definitions are the authoritative description of what each gate enforces.
Two boundary controls outside the CI matrix are worth noting for procurement:
  • Open-core split. QTG is distributed open-core. The OSS distribution is AGPL-3.0-or-later; commercial Pro features are governed by a separate EULA. The pro-boundary and pro-graceful-import gates mechanically guarantee the OSS artifact never embeds or hard-depends on Pro code. For AGPL-averse buyers, a commercial relicense is available (contact below).
  • Startup schema validation. Independent of CI, the server runs boot_validate_schema_invariants() at boot, which verifies schema existence and Alembic-head consistency before serving traffic.

2. Signer Model

The default EVM proving lane uses a local AWS KMS signer running in your own AWS account.
  • Implementation: src/qtg/infrastructure/signers/aws_kms_evm.py (AwsKmsEvmSigner).
  • Key custody: QTG holds only a KMS key ID (MG_LOCAL_SIGNER_KMS_KEY_ID), never private key material. Signing is an API call to KMS; the key never leaves the KMS HSM boundary. Even a fully compromised QTG host gains only the ability to request signatures — bounded by the approval gate and only while access is maintained.
  • Key spec validation: at health-check / bootstrap the signer asserts the KMS key is ECC_SECG_P256K1, usage SIGN_VERIFY, and state Enabled before it is trusted; a misconfigured key fails closed (INVALID_KMS_KEY_METADATA / INVALID_KMS_KEY_STATE).
  • Payload hash pinning: before signing, the signer recomputes sha256(payload) and rejects the request unless it matches both request.payload_hash and signing_intent.allowed_payload_hash (PAYLOAD_HASH_MISMATCH / ALLOWED_PAYLOAD_HASH_MISMATCH) — the signer will not sign a payload that differs from the one the caller declared.
  • Transient vs fatal: KMS throttling / timeout / key-unavailable map to a temporary error (SIGNER_UNAVAILABLE, retried); rejections map to fatal (SIGNER_REJECTED).
  • Configuration: MG_LOCAL_SIGNER_BACKEND=aws_kms_evm, MG_LOCAL_SIGNER_KMS_KEY_ID, MG_LOCAL_SIGNER_AWS_REGION. AWS credentials use the standard AWS SDK default credential chain; an explicit profile is optional.

Signer alternatives

The signer is an interface, not a hard dependency on KMS. Two other implementations ship: A development-only plain private-key signer also exists; it holds the key in process memory and must not be used with real funds — migrate to KMS before moving value.

3. Signer Protocol

All signers implement the SignerProtocol runtime-checkable protocol in src/qtg/domain/protocols.py:
  • SignRequest carries a typed SigningIntent (action, asset, amount, destination, max_fee_usd, chain_family, allowed_payload_hash) plus the payload and its hash — the signer always sees the declared intent alongside the bytes.
  • health() is invoked at bootstrap so a broken or misconfigured signer is surfaced at startup, not at first fund movement.
  • The same protocol file defines the ExecutorProtocol (preflight / prepare / submit / observe / recover / health), so every transport adapter follows one auditable contract.

4. Callback Authentication (HMAC v3)

Outbound callbacks to your receiver are signed with HMAC-SHA256 v3 and defended against replay. Implementation: src/qtg/callback_auth.py.
  • Headers: x-qtg-callback-signature, x-qtg-callback-timestamp, x-qtg-callback-nonce, x-qtg-callback-signature-version (v3). All four are required; any missing → reject.
  • Canonical string: timestamp \n nonce \n sha256(body) — the signature covers the timestamp, nonce, and a hash of the full body, so tampering with any part invalidates it.
  • Constant-time compare: signatures are compared with hmac.compare_digest (no timing leak).
  • Timestamp window: requests outside max_age_seconds (default 300s) are rejected (callback timestamp out of range).
  • Nonce replay defense: a nonce_recorder hook records each nonce; a duplicate nonce is rejected (callback replay detected). Receivers must persist seen nonces — see the Callback Verification Contract for the receiver-side requirement.
  • Delivery durability: callbacks are dispatched from a durable outbox with retry, so a transient receiver failure does not lose the event.
  • URL allowlist: callback target hosts are constrained by an operator-configured allowlist (MG_CALLBACK_ALLOWED_HOSTS_CSV), with an SSRF / DNS-rebind guard on dispatch.
Inbound API requests use the same HMAC discipline (method + path + query + body hash + timestamp + nonce, per-key role/purpose scoping). See Security Model → Inbound Request Authentication.

5. Address Allowlist Model

On-chain destination (and, where applicable, source) addresses are checked against a database-backed allowlist at both creation and dispatch time — revoking an address stops the next dispatch even for already-approved, queued movements. Implementation: src/qtg/application/services/address_allowlist.py.

Postures

Every action_type a real executor or template can introduce has exactly one posture entry in a single source-of-truth map (ADDRESS_GUARD_POSTURE); a completeness gate fails loudly if a new on-chain adapter is added without one.

chain_match pin

Selected actions additionally require a strict (address, chain_id) match — an allowlist row scoped to the wrong chain does not satisfy the check, and a NULL-chain row is rejected under strict mode. cctp_burn carries chain_match=True. (cctp_mint deliberately does not: the mint recipient is bound by the Circle attestation, so a wrong-domain mint fails attestation rather than diverting funds.) is_address_allowed(..., require_chain_match=True) returns False when chain_id is absent — fail-closed by construction.

Enforcement properties

  • Enforced at the database level inside the executor at dispatch — not a client-side check that can be bypassed.
  • Address normalization is chain-family-aware (EVM addresses lower-cased for storage and comparison).
  • A non-allowlisted address raises FatalMovementError with error_code=ADDRESS_NOT_ALLOWED; a misconfigured strict check raises MovementValidationError with ADDRESS_VALIDATION_MISCONFIG. The movement does not proceed.
The allowlist works together with the signed-recipient invariant (the broadcast transaction is decoded and its recipient re-derived and compared to the allowlist-validated destination before broadcast) and the outflow velocity cap (a per-token rolling-window aggregate ceiling on the CEX-balance lanes). Both are described in the Security Model.

6. Known Limitations & Threat Boundary

QTG states its boundaries explicitly. Reviewers should weigh these against their threat model.

”DB-write = game over” — the accepted boundary

Reference: docs/reference/security/db-access-control-boundary.md. QTG’s submit-time guards (signed-recipient invariant, signed-amount/value envelope) close the malicious / swapped / buggy signer vector and a tamper of the prepared action / calldata alone. They do not close a full-DB-row tamper that rewrites the trusted anchor (ctx.intent, ctx.node_config, or the allowlist itself) in lockstep with the signer output — the equality / cap checks would then compare tampered-against-tampered. This is a deliberate, documented boundary, not an oversight. An in-band DB integrity hash raises no real bar against an adversary who can already write the movements DB (they can forge the hash too) — it would be security theater. Under QTG’s positioned threat model, DB-write access is treated as out of scope: an attacker with write access to the movements DB has already won. The proportionate mitigations are deployment / infrastructure controls the operator owns, in descending value:
  1. Least-privilege DB roles (runtime app role holds only the DML it needs; schema / allowlist / approval tables owned by separate roles).
  2. Separate approval authority (allowlist / approval changes require a credential path distinct from the runtime app credential).
  3. Immutable / append-only audit shipped to an external sink the app role cannot rewrite.
  4. Network isolation + credential hygiene + TLS in transit.
  5. Backups + point-in-time recovery as the recovery floor.
A node_config-only tamper can inflate the OFT (Stargate / USDT0) and ERC20 amount ceilings — a bounded over-transfer to the still-pinned recipient — which is the same accepted DB-write boundary.

CCIP lane: signed-artifact invariant

Reference: CCIP Lane. The CCIP bridge lane is implemented and participates in the submit-time signed-artifact invariant. Before the out-of-process sidecar broadcasts, QTG decodes the signed legacy transaction and pins router, destination chain selector, receiver, token, amount, native fee, signer identity, and source chain id against the trusted intent. A mismatch fails closed as ccip_signed_artifact_mismatch. The most operationally-proven lane today remains the Upbit ↔ Bithumb CEX withdrawal path, while the EVM lanes carry submit-time signed-artifact guards.

Other operating notes

  • PostgreSQL only (v0.1.0+). There is no SQLite runtime path; tests run against real PostgreSQL.
  • Approval gate is mandatory. Every movement stops at PENDING_APPROVAL; agents can propose but not approve. Auto-approve policies evaluate automatically but do not remove the stage.
  • Outflow velocity cap covers the CEX-balance lanes; on-chain bridge lanes are out of scope for the cap by construction.

7. Reporting Security Issues & Support

  • Vulnerability reports: email security@jerryquanthouse.com — do not open a public GitHub issue for security vulnerabilities. Acknowledgement target 48 hours; fix / mitigation plan within 7 days for critical issues. See SECURITY.md for the full policy and coordinated-disclosure terms.
  • Commercial / procurement enquiries (including AGPL relicensing for the OSS distribution and Pro EULA terms): contact JephaLabs through your existing commercial channel or the GitHub organization contact path.
Reviewer tip. Every control on this page is verifiable in the source tree at the path cited. If a claim here ever diverges from the code, the code is authoritative — please report the discrepancy via the security contact above.