Security

GhostSwap credentials let the holder create swaps that move real funds. Treat them like a payment processor secret key.

Keep credentials server-side

Server-side environment variables loaded into your runtime.

Secret managers (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler, 1Password Connect).

Backend services with TLS 1.2+ to call our API.

Browser code — single-page apps, mobile apps, anything served to a user device. Anything in the client bundle can be extracted in seconds.

URL query strings — leak into server logs, browser history, referrer headers, third-party analytics.

Source control — even private repos. Use .env (gitignored) or a secret manager.

Error messages, logs, response bodies you return to the user.

Protect the secret at rest

The secret half (gssk_live_*) is shown at creation and is re-viewable from the dashboard via the Reveal secret button. Treat it as a production secret regardless.

Where to store it depends on the environment:

# Local development — fine when the file is gitignored:
echo "GHOSTSWAP_SECRET=gssk_live_..." >> .env.local
 
# Production — use a real secret manager so the value is never on disk
# beside your code, never baked into a build artifact, and rotatable
# without a redeploy:
gh secret set GHOSTSWAP_SECRET                                       # GitHub Actions
op item edit "GhostSwap" credential=...                              # 1Password
aws secretsmanager put-secret-value --secret-id ghostswap/prod ...   # AWS
doppler secrets set GHOSTSWAP_SECRET=...                             # Doppler

The cardinal rule: the secret never lives in source control, build artifacts, browser bundles, log lines, or URL query strings.

If you lose track of the secret, you can recover it from /dashboard/api-credentials (Reveal secret on the credential row). Every reveal is audit-logged on our side; we will reach out if we see a pattern that looks unusual (multiple reveals from new IPs, etc.).

Reveal is for recovery, not a routine flow. Step-up auth (re-prompt for password / WebAuthn) and a rate cap on reveals are on the near-term roadmap. In the meantime: if you suspect a secret has leaked, revoke the credential instead of revealing — that invalidates it everywhere immediately and lets you issue a fresh credential after the brief 401 window.

How we store it: the secret lives as both an argon2id hash (used on every authentication) and an AES-256-GCM ciphertext (used for the Reveal flow). Both require the master KEY_ENCRYPTION_KEY env var on our infrastructure to decrypt — neither can be recovered from a database snapshot alone.

Rotate regularly

Recommended cadence: every 90 days, plus on any of:

  • Team member who had access leaves.
  • Suspected secret leak (committed to repo, posted in chat, etc.).
  • Unexplained traffic spike on the credential's last-used metric.

Rotation flow:

  1. Create a new credential in the dashboard.
  2. Roll the new bearer token into your environment.
  3. Verify traffic flows through the new credential (watch its last_used_at).
  4. Revoke the old credential.

Reduce blast radius

  • Separate credentials per environment: one for staging, one for production. (When per-environment keys land — see Roadmap — this becomes a built-in feature.)
  • Don't share credentials between services. If service A and service B both need access, give them separate credentials so you can rotate one without touching the other.
  • Audit credential usage. The dashboard shows last_used_at. Anything stale is a candidate for revocation.

Monitor traffic

  • Log every request's X-Request-Id response header. When escalating to support, this lets us find your exact request.
  • Alert on unexpected 401s. Could indicate a revoked or rotated credential; could also indicate a compromised credential being abused.
  • Alert on unusual rate-limit errors. A sudden spike in 429s without a corresponding traffic increase suggests someone else has your credential.
  • Track per-credential volume against expectations. If a credential normally creates 100 swaps/day and you see 10,000, that's an alert.

TLS only

https://partners-api.ghostswap.io only. We do not serve over plain HTTP. Reject anything that comes back unencrypted.

What we do server-side

For your awareness, here's what we do to protect you:

  • Secrets are stored as argon2id hashes. Nobody at GhostSwap can read your secret — even our DBAs see only the hash.
  • Bearer tokens are checked in constant time to prevent timing attacks.
  • Per-credential and global rate limits cap the damage from abuse.
  • Every request is logged with X-Request-Id for forensics.
  • Idempotency keys prevent replay attacks from creating duplicate swaps.

Reporting a leak

If you suspect your credential has leaked:

  1. Revoke immediately at /dashboard/api-credentials.
  2. Email support@ghostswap.io with the credential's public key (gspk_live_...) and a description of the suspected exposure.
  3. We'll review usage logs and respond within one business day.

What's coming

Roadmap entries that further harden credentials:

  • Per-credential IP allowlists so a leaked credential can't be used outside your servers.
  • Per-credential method scopes for read-only or restricted credentials.
  • Test-mode keys (gssk_test_*) so you can iterate safely without live funds.