Skip to content

Security and key management

Corridor never holds the keys

Vaults sign through the customer’s own KMS, HSM or MPC provider behind one interface. Corridor sends transaction hashes and receives signatures. See Keys and signers.

Read-only needs no keys at all

A pilot watches Stellar accounts by id and matches records. Nothing can move.

A challenge is checked before it is signed

The SEP-10 challenge must be built by the anchor’s own SIGNING_KEY and must name this vault, or the vault does not sign it.

An asset is its code and its issuer

A look-alike token from another issuer is never booked as USDC, and an unverified issuer is left unset rather than guessed.

Partner input is authenticated

Webhooks are HMAC-signed, time-bounded and compared in constant time. Forgeries get a 401 and change nothing.

Money moves exactly once

Funding and deliveries are recorded durably before and after they happen, so retries find them instead of repeating them.

Key Development and testnet Production
Stellar vaults (treasury, escrow) A Stellar secret seed in .env.local, generated on first run and funded by Friendbot The customer’s KMS, HSM or MPC provider, through @corridor/signer and ed25519StellarSigner
Solana and Base vaults .env.local The same held key for Solana (Ed25519); AWS KMS or Turnkey for the EVM side
Corridor Reference secret (CORRIDOR_TAG_SECRET) .env.local Secret manager; rotation by reference version
Anchor credentials and webhook secrets .env.local Secret manager

.env.local is gitignored. Keys are never committed, printed or sent anywhere.

The vault key is the most valuable thing in the deployment, and SEP-10 asks it to sign a transaction an anchor supplies. SepAnchor checks the challenge first:

  • it must be built by the anchor’s own SIGNING_KEY, as published in its stellar.toml;
  • it must name this vault as the client account;
  • it must be on the expected network.

Only then is it signed, and the JWT that comes back is cached for its lifetime. A stray or hostile endpoint cannot use the handshake to get a vault to sign something else.

On Stellar anyone can issue an asset whose code is USDC. assetOf matches code and issuer, so a look-alike is never booked. Issuers Corridor has not verified are left unset: asking for one raises an error that names the missing issuer rather than guessing. A deployment supplies its own with CORRIDOR_STELLAR_ISSUER_<CODE>_<NETWORK>. See ADR 0007.

SEP-31 has no webhook — the sending anchor polls GET /transactions/{id}, which is what the saga’s wait loop does. For providers that do deliver webhooks, Corridor verifies them:

X-PAJ-Timestamp: <unix seconds>
X-PAJ-Signature: v1=<hex HMAC-SHA256(secret, "<timestamp>.<raw body>")>
  • The timestamp must be within 5 minutes.
  • The v1= value is compared in constant time (timingSafeEqual); during a secret rotation a header may carry several schemes, and any valid v1 passes.
  • Each order status is stored once; redeliveries are acknowledged without effect.

A payout records its anchor order and the funding transaction (partner_order_links) durably. If a worker crashes after opening an order, the retry reuses it; if it crashes after funding, the retry finds the funding payment on Horizon by its memo and does not send again. A failure after funding stops at manual_review, because the money is at the anchor until its refund is confirmed.

Every commit also checks the saga’s fencing token, so a worker whose lease lapsed mid-leg can never book or mark a step done. See Saga engine.

  • Corridor serves licensed payment companies, which own the KYC and KYB of their end customers and hold the licences for each corridor.
  • Corridor runs in the customer’s environment and signs only through the customer’s signer. It is software, not a custodian or a money transmitter.
  • Fiat touchpoints go through regulated anchors and licensed partners in each corridor.
  • Secrets live outside the repository.
Area Today Production deployment
Vault keys Local seeds on testnet; signer interface on every vault The customer’s KMS or MPC for every vault
Ledger PGlite Managed Postgres with the same schema and triggers
Horizon The public instance CORRIDOR_HORIZON_URL pointing at a private instance
Screening None Address screening and a Travel Rule hook before a payout is funded
Audit export The reconciliation trail and a CSV Signed, retained exports on a schedule