Threat model
Assets, adversaries, attacks and their implemented mitigations, with residual risks stated honestly.
The canonical security home is DESIGN.md §7 in the marketplace repo (PRD §9.9 is a
summary that points there). This page is the public form of that section. Every
mitigation below cites the file that enforces it: paths under backend/ live in the
marketplace repo, paths under enclave/, host-proxy/, jwt/, cosign/, and
deploy/ live in the open-source provider repo
github.com/nodexpert-labs/staked-quic-connection-provider. Items that are designed
but not yet shipped are explicitly labeled planned at the bottom.
Assets
| Asset | Where it lives | Worst-case impact if compromised |
|---|---|---|
| K1, validator identity key (Ed25519) | Plaintext only in Nitro Enclave memory. At rest, a KMS-encrypted envelope installed as a mode-600 file on the EC2 instance in the validator's own AWS account (deploy/scripts/deploy.sh) | Full validator impersonation: votes, transactions, slashing-adjacent damage. Protecting K1 is the design's first priority |
K2, marketplace JWT signing key (ECC P-256 in AWS KMS, used via kms:Sign, never exported) | Marketplace AWS account (backend/crates/jwt/src/sign.rs) | Attacker mints valid lease JWTs, worst case bounded by the signing oracle (see below) |
| Lease JWTs (bearer tokens, default TTL 600 s) | Trader infrastructure, in flight on gRPC metadata | Rate-limited TLS handshakes against one specific TEE until exp |
| Payment funds | The trader's own wallet until the atomic 90/10 transaction lands on-chain | Lease activated without full payment, or one payment settling two leases |
| Signing-path availability | host-proxy + enclave on the validator's EC2 instance | Trader loses staked connections mid-lease (no contractual SLA in v1, see residual risks) |
| Release artifacts (EIF, host-proxy binary) | GitHub Releases of the provider repo | Backdoored binary running next to K1 |
Adversaries
- JWT thief: a network attacker or compromised trader box holding a stolen lease token.
- Malicious or fully compromised marketplace: the marketplace itself turning hostile, including its K2 key.
- Compromised host: root on the validator's EC2 instance, outside the enclave.
- Unauthenticated flooder: anyone who can reach the TEE endpoint on 443.
- Payment fraudster: a trader trying to activate a lease without paying the full amount, or replaying a payment.
- Supply-chain attacker: someone substituting or forging release artifacts.
Out of scope: the validator operator attacking themselves (they own the AWS account and the key, the model protects them rather than defending against them), and Solana protocol-level attacks.
Attacks vs mitigations
| Attack | Mitigations (file that enforces each) | Honest worst case |
|---|---|---|
| Stolen JWT replay | aud claim must equal the TEE's own endpoint URL, mismatch is PERMISSION_DENIED (host-proxy/src/jwt.rs, enforced claims in jwt/src/types.rs). TTL default 600 s, hard cap 3600 s, 0 rejected (backend/crates/api/src/config.rs:200-205). Per-lease quota bucket keyed on lease_id, default 100 handshakes/min, JWT quota claim overrides it (host-proxy/src/rate_limiter.rs:51-66) | Quota-limited handshakes against exactly one TEE for at most the remaining TTL. Never a transaction or vote |
| Malicious marketplace mints JWTs at will | The enclave's signing oracle refuses everything that is not a TLS 1.3 client CertificateVerify: payload length in , first 64 bytes all 0x20, context string "TLS 1.3, client CertificateVerify\0", mirroring Firedancer's fd_keyguard_payload_matches_tls_cv (enclave/src/signer.rs:11-49). The marketplace never holds K1 | Unauthorized handshakes on listed validators for the JWT TTL window. The marketplace can sell access it should not, it can never sign a transaction, vote, or arbitrary message |
| Compromised host instance | The enclave has no network and no persistent disk, it is reachable only over vsock (enclave/src/kms_vsock.rs). The key envelope is useless to the host: KMS Decrypt is gated on the Nitro attestation document, key-policy conditions kms:RecipientAttestation:PCR0/PCR1/PCR2 (deploy/terraform/kms.tf:46-56), so a tampered enclave image gets AccessDenied | A root attacker can talk vsock directly and obtain client-CV signatures (the enclave trusts its host for transport, DESIGN.md TB6), and can drop traffic. It can never extract K1 or obtain a non-handshake signature |
| Key extraction | K1 plaintext exists only inside enclave memory, decrypted at boot via KMS Decrypt with an attestation Recipient (RSAES_OAEP_SHA_256), SigV4 over TLS over vsock (enclave/src/kms_vsock.rs:21-30). At rest there is no copy outside the KMS-encrypted envelope on the instance itself (/etc/staked-quic/seed.bin, mode 600, fed to the enclave via host-proxy/src/bin/provision.rs --seed-file). The seed is zeroized after key construction (enclave/src/signer.rs) | A PCR mismatch means the enclave refuses to start rather than running with a key it cannot prove it should hold |
| Payment fraud (short payment, memo reuse, one tx for two leases) | Settlement is the sole confirmation path. It matches the memo, then verifies via RPC getTransaction at finalized that both transfer legs credited at least their frozen share, a short leg is AmountMismatch and the lease stays unpaid (backend/crates/settlement/src/reconciler.rs:122-168). payment.memo is UNIQUE and onchain_tx_signature is partially UNIQUE, so one on-chain transaction settles at most one lease (DESIGN.md §4.2.4bis) | An attacker paying in full activates the lease they paid for, nothing else |
| Late-payment race (pay after the booking expired, slot already resold) | POST /v1/solana-pay/{memo} returns a uniform 404 for unknown, expired, and already-paid memos, so no transaction can even be built for a dead booking (backend/crates/api/src/routes/solana_pay.rs:213-220). The pending-payment window is 15 minutes, the expiry sweep runs every 60 s | A payment that bypasses the endpoint and lands after expiry is a settlement no-match, refunded manually (DESIGN.md §4.2.1, see residual risks) |
| Unauthenticated flood | Per-IP fallback limiter hardcoded at 100 req/min, burst 20, LRU-capped at 100k buckets so an IP spray cannot exhaust memory (host-proxy/src/rate_limiter.rs:14-19). Release builds fail closed when the transport exposes no peer address (host-proxy/src/grpc_service.rs). gRPC decode cap 4096 bytes, sign payload cap 162 bytes, JWT token cap 8192 bytes | Volumetric DoS at the network layer remains possible (the planned CDN/WAF layer addresses it, see below) |
| Compromised or vulnerable release | Kill switch: the host-proxy polls revocation.json plus a detached K5 Ed25519 signature from the provider repo's GitHub Releases every 60 s (host-proxy/src/revocation.rs:36), verified over the exact asset bytes with verify_strict (cosign/src/lib.rs). The revocation flag latches and never clears, so replaying an older signed list cannot un-revoke a node (host-proxy/src/revocation.rs). Boot fails closed: a revoked version or an unfetchable list is exit(1) (host-proxy/src/main.rs:99-111). A revoked node answers FAILED_PRECONDITION "version_revoked" on every RPC (host-proxy/src/grpc_service.rs:79-84) | Propagation is bounded by the 60 s poll. Note the K5 release key is currently a placeholder pending the production key ceremony (cosign/src/lib.rs:16-21) |
| JWT forgery or malleability | ES256 via kms:Sign (KMS has no Ed25519), DER-to-JOSE conversion with low-S normalization so a signature cannot be reshaped into a second valid token (backend/crates/jwt/src/sign.rs:123-125). Verification is kid-based against a static trust map in the host config, rotation is add-key-and-restart (host-proxy/src/jwt.rs:1-3). Disabling K2 in KMS stops all new minting, outstanding tokens die at exp, at most 10 minutes on the default TTL | See the key-rotation runbook for the compromise procedure |
| Shared co-tenant starvation | Token issuance stamps shared leases with max(1, floor(budget / max_concurrent_shared_leases)) handshakes/min instead of the full budget, and the host-proxy keys its limiter on lease_id so each seat gets its own bucket (backend/crates/api/src/routes/token.rs:268-285, DESIGN.md §5.3.2) | Post-handshake stream allocation is not isolated, see residual risks |
The request pipeline
Every SignCertificateVerify call walks these gates in order
(host-proxy/src/grpc_service.rs:125-156). Order matters: the kill switch stops a
revoked binary even on un-rate-limited bursts, and the per-IP gate keeps a flood of
bad JWTs from exercising the verifier.
- Kill-switch check (revoked version:
FAILED_PRECONDITION "version_revoked") - Per-IP fallback limiter (100/min, burst 20, hardcoded)
- JWT verification (enforced claims:
aud,val_pk,lease_id,exp,nbf,quotaonly, everything else the marketplace mints is deliberately ignored,jwt/src/types.rs) - Per-lease quota bucket (JWT quota override, quota 0 denies outright)
- 162-byte payload cap before anything crosses vsock
- The enclave matcher itself (
enclave/src/signer.rs)
Residual risks and accepted trade-offs
These are known, documented, and accepted for v1. They are listed so you do not have to discover them yourself.
- Leader-side stream dilution (shared mode). A shared lease bounds co-tenants only at the handshake layer (the per-seat quota). Once connections are open, stream allocation across them is the Solana leader's call, not the TEE's (DESIGN.md §2, item 4). This is a performance trade-off shared traders accept for the lower price, not a security issue. Related: there is no contractual SLA in v1, the SDK hard-fails after
UNAVAILABLEx3 with no auto-credit (DESIGN.md OQ9). - Manual refunds. Prorated refunds (revoked lease, admin takedown, late payment) are debited from the platform's SOL buffer funded by the 10% fee leg and triggered by ops within 24 h. There is no refund smart contract (DESIGN.md §4.2.4bis, PRD §9.3).
- vsock is cleartext by design between host and enclave, protected only by Nitro hypervisor isolation (DESIGN.md TB6). Combined with the enclave trusting its host for transport, this is why host compromise yields unauthorized handshakes (and only handshakes).
- The transcript hash is not validated. The matcher checks structure, not the 32/48/64-byte hash content (
enclave/src/signer.rs:38). The signature is still only ever valid as a TLS 1.3 client CertificateVerify, so this widens nothing beyond handshakes. - Nitro side channels are an accepted v1 residual:
ed25519-dalekis constant-time and Nitro provides no cross-VM cache sharing (DESIGN.md §7.9).
Planned, not yet implemented
Honest status of designed-but-unshipped defenses. Do not assume these are live.
- CDN/WAF in front of the TEE (FR-T11): the current Terraform exposes a plain security-group allowlist via a single
allowed_grpc_cidrvariable (deploy/terraform/security.tf:6-11). No Cloudflare module exists yet. - Host-proxy binary self-verification at boot (FR-T12): not implemented in
host-proxy/src/main.rs. - Enclave-side K4-signed audit log (DESIGN.md §7.7.1): not implemented. Marketplace-side audit rows do exist (a
jwt_issuancerow per token,audit_logon revoke). - KMS/IAM policy drift detection: the dashboard field
kms_policy_driftis a placeholder that always reports false. - Production K5 release key: the current key is a placeholder whose private half was destroyed, the production key ceremony is pending (
cosign/src/lib.rs:16-21). - FIDO2 2FA is available as a Clerk policy but not enforced in code.
- External audit, penetration test, and a paid bug bounty are pre-GA checklist items (DESIGN.md §7.10), none is live today.
Dev-only escape hatches (STAKED_QUIC_DEV_BYPASS_MANIFEST_SIG, the dev-plaintext
gRPC feature) are compiled out of release builds (cosign/src/lib.rs:37-46,
host-proxy/src/main.rs:182-196). They are documented behavior, not
vulnerabilities, unless you can reach one in a release build, which would itself be
a critical finding.
See also
The trust-boundary table, who holds which key, and the JWT contract in brief.
How to report privately, what counts as a vulnerability, and the highest-priority surfaces.
Rotation and compromise procedures for the marketplace JWT key.
What to do when a probe alert fires or a version is revoked.