> ## Documentation Index
> Fetch the complete documentation index at: https://jephalabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Enterprise Readiness

> Procurement and DevOps-security reference: CI gates, signer model, callback and request auth, address allowlist, and known limitations

# 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](#known-limitations--threat-boundary) before deployment; the accepted boundary is stated explicitly there.

For the narrative defense-in-depth walkthrough, see the [Security Model](/concepts/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.

| Workflow file             | Gate                       | What it enforces                                                                                                                                                                                                                             |
| ------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lint.yml`                | Lint                       | `ruff check src tests` — style and lint pass (line-length 110).                                                                                                                                                                              |
| `typecheck.yml`           | Type check                 | `mypy src` with an error cap (build fails if the mypy error count exceeds the pinned baseline).                                                                                                                                              |
| `test.yml`                | Tests                      | `pytest` against a real **PostgreSQL 16** service (no SQLite). Runs the OSS contract surface plus Pro-extraction sentinel tests on CI; operators run the full suite (3000+ tests) locally before merge to main.                              |
| `pro-boundary.yml`        | Open-core boundary         | `scripts/check_pro_boundary.py` (AST-based) asserts the OSS code never imports commercial Pro modules — the boundary is one-directional and mechanically enforced; `scripts/check_oss_api_surface.py` asserts the OSS API surface is intact. |
| `pro-graceful-import.yml` | Graceful Pro degradation   | Boots `qtg.main` with the Pro bootstrap module forced unavailable and asserts the app still builds its routes — the OSS distribution must run without any Pro module present.                                                                |
| `alembic-fresh.yml`       | Schema bootstrap           | Runs the 3-bucket Alembic migration chain (`global` / `mainnet` / `testnet`) from an empty PostgreSQL database to head — a fresh deploy must migrate cleanly.                                                                                |
| `gitleaks.yml`            | Secret scanning            | `gitleaks detect` over full history with `.gitleaks.toml` — blocks committed secrets (API keys, tokens, private keys).                                                                                                                       |
| `license-checker.yml`     | Python dependency licenses | `pip-licenses` fails the build on copyleft / source-available licenses (GPL, AGPL, LGPL, SSPL, CDDL, Commons Clause, BUSL, Elastic, Polyform) in third-party deps.                                                                           |
| `frontend-licenses.yml`   | Dashboard licenses         | `npm run licenses:check` in `frontend/` (frozen lockfile) — fails on copyleft frontend dependencies.                                                                                                                                         |
| `sidecar-licenses.yml`    | Sidecar licenses           | `pnpm run licenses:check` for the CCIP sidecar (`infra/sidecars/ccip`, frozen lockfile) — fails on copyleft sidecar dependencies.                                                                                                            |

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:

| Signer              | File                                     | Use                                                                                                                                                                        |
| ------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AwsKmsEvmSigner`   | `infrastructure/signers/aws_kms_evm.py`  | **Default / production** — keys in your KMS.                                                                                                                               |
| `RemoteSignerProxy` | `infrastructure/signers/remote_proxy.py` | Delegates signing to an external HTTP signing service (optional bearer auth, timeout / status-code-aware retry classification). For teams running a separate signing tier. |
| `NoopSigner`        | `infrastructure/signers/noop.py`         | Returns an empty signature. **Non-signing lanes / tests only — never for real funds.**                                                                                     |

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`:

```python theme={null}
class SignerProtocol(Protocol):
    signer_key: str
    async def sign(self, request: SignRequest) -> SignResult: ...
    async def health(self) -> dict[str, Any] | None: ...
```

* `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](/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](/concepts/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.

| Posture            | Meaning                                                                   | Example action types                                                     |
| ------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `STRICT_BOTH`      | Source **and** destination both allowlist-checked                         | `cctp_burn`, `cctp_mint`, `gateway_*`, `ccip_send`, `evm_erc20_transfer` |
| `DESTINATION_ONLY` | Source is signer-bound (not redirectable), so only destination is checked | `stargate_send`, `usdt0_send`, `lighter_secure_withdraw`                 |
| `CEX`              | CEX withdrawal address-check lane                                         | `cex_withdrawal`                                                         |
| `NONE`             | No on-chain outflow address (observe / status / preflight / probe)        | `*_observe`, `*_status`, `*_preflight`, `finality_probe`                 |

### 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](/concepts/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](/concepts/bridges/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](mailto: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`](https://github.com/jephalabs/quant-transfer-guard/blob/main/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.
