Skip to content

Security: StellarGateLabs/StellarGate

Security

SECURITY.md

Security Policy

Last reviewed: 2026-08-29

StellarGate handles Stellar-network payments — destination addresses, memos, webhook secrets, and (in self-hosted deployments) gateway wallet keys. Vulnerabilities here can have direct financial impact, so we ask that you report them privately rather than through a public GitHub issue.

Supported Versions

StellarGate is pre-1.0 and does not yet maintain parallel release branches. Security fixes are made against the main branch only. Deployments should track main (or the latest tagged release, once releases exist) to receive fixes. The minimum supported Rust version is 1.94 (see rust-toolchain.toml).

Version Supported
main / latest release ✅
older commits / forks ❌

Reporting a Vulnerability

Do not open a public issue or pull request for a security vulnerability. Public disclosure before a fix is available puts every deployment at risk.

Instead, report privately using one of these channels:

  1. Preferred: GitHub Private Vulnerability Reporting. Go to the Security tab of this repository and open a new draft security advisory. This notifies maintainers directly without making the report public, and lets us collaborate with you on a fix (including credit in the advisory, if desired) before disclosure.
  2. Alternative: email. If you're unable to use GitHub's advisory flow, email the maintainers at security@stellargatelabs.com with a description of the issue, steps to reproduce, and any proof-of-concept code. If you don't receive a response within 72 hours, please follow up — email can be missed.

Please include as much of the following as you can:

  • A clear description of the vulnerability and its impact (e.g. fund loss, webhook signature bypass, SSRF, auth bypass on /merchants or /payments).
  • Steps to reproduce, or a minimal proof of concept.
  • The affected component (e.g. webhook signing in src/webhook.rs, payment verification in src/horizon.rs, the SSRF guard on webhook_url).
  • Any suggested remediation, if you have one.

What to Expect

  • Acknowledgement: within 72 hours of your report.
  • Triage and severity assessment: within 7 days — we'll confirm the issue, assess severity/impact, and let you know if we need more information.
  • Resolution target: within 90 days for critical severity issues; within 180 days for all other severity levels. Once a fix is released, we'll coordinate public disclosure timing with you and credit reporters (unless you prefer to stay anonymous).

Scope

In scope:

  • The StellarGate API server (src/), including payment creation/lookup, webhook signing and delivery, the Horizon listener/poller, merchant provisioning, and configuration/validation logic.
  • The database schema and migrations (db::migrate in src/db.rs — there is no migrations/ directory).
  • Supply-chain issues in this repository (e.g. Cargo.lock, CI workflows).

Out of scope:

  • Vulnerabilities in the Stellar network, Horizon, or third-party wallets themselves — report those upstream to the Stellar Development Foundation.
  • Issues that require an attacker to already control a merchant's ADMIN_PROVISIONING_SECRET, WEBHOOK_SECRET, or Stellar secret key.
  • Denial-of-service reports that rely purely on brute-force volume rather than a logic flaw.

Audit Logging

Security-relevant actions emit a tracing event with audit = true so operators can build log alerts on a stable schema. Every audit event carries:

Field Meaning
audit Always true; use it to select audit records from other logs.
action Stable action name (see table below).
actor Who performed the action (e.g. admin, merchant:<id>).
outcome success or failure.
source_ip Caller's source address, derived via client_ip_key_from_parts.
request_id Caller's x-request-id (or a generated one) via api::request_id().
subject ids Action-specific ids (e.g. merchant_id, api_key_id, payment_id).

Every admin action emits exactly one audit = true event carrying source_ip and request_id. The current action values are:

action Handler Subject ids
merchant.provision provision_merchant merchant_id
api_key.issue issue_api_key merchant_id, api_key_id
api_key.list list_api_keys merchant_id
api_key.revoke revoke_api_key merchant_id, api_key_id
payment.create payments::create merchant_id, payment_id
webhook.redeliver payments::redeliver_webhook payment_id

Known Security Design Notes

For context when triaging reports, StellarGate already implements:

  • HMAC-SHA256 request signing on outbound webhooks with a signed timestamp (replay-resistant); see the "Verifying webhooks" section of the README.
  • An SSRF guard on webhook_url that rejects loopback/link-local/private/ reserved destinations, re-checked on redelivery against the resolved address (not a fresh DNS lookup) to mitigate DNS rebinding. WEBHOOK_ALLOW_PRIVATE_TARGETS bypasses this guard and is only intended for local development and tests — it is rejected at boot when STELLAR_NETWORK=public (issue #246).
  • Admin-gated merchant provisioning (X-Admin-Secret), disabled entirely when ADMIN_PROVISIONING_SECRET is unset.
  • webhook_url is treated as a secret. Webhook endpoint URLs commonly embed capability tokens in the path or query string (e.g. https://hooks.example.com/t/SecretToken). StellarGate never logs the full URL; log statements redact it to scheme and host only (https://hooks.example.com/…) so the credential is not copied into log sinks, aggregators, or support tickets (issue #240).

If you find a way around any of the above, that's exactly what this policy is for — please report it privately.

Webhook Payload Exposure

HMAC signing (above) proves a webhook body is authentic and unmodified. It says nothing about confidentiality — anyone who can observe the connection can read the body, and on any network other than STELLAR_NETWORK=public, ALLOWED_WEBHOOK_SCHEMES may be configured to allow plain http, so that connection is not necessarily encrypted. (public enforces HTTPS unconditionally, independent of ALLOWED_WEBHOOK_SCHEMES.)

What the default payload exposes. WEBHOOK_PAYLOAD_DETAIL defaults to minimal: event, payment_id, status, and updated_at. None of that is sensitive on its own — a payment id without its amount or owning merchant tells an observer only that some event happened to some payment.

What WEBHOOK_PAYLOAD_DETAIL=full additionally exposes. merchant_id, amount, paid_amount, asset, asset_issuer, tx_hash, and (on over/underpaid events) delta. merchant_id identifies which tenant the event belongs to; the amounts reveal transaction size; tx_hash links the event to a specific Stellar ledger entry. An operator who sets WEBHOOK_PAYLOAD_DETAIL=full — or who allows http on a non-public network — is choosing to let this detail be readable by anyone who can observe the connection (or, if http, by any network intermediary at all). Boot logs a warn when ALLOWED_WEBHOOK_SCHEMES includes http, on every network, so this is never a silent choice.

Getting the detail back safely. A receiver that needs the fields minimal omits already holds an API key (it's how the merchant integrated in the first place) and can call GET /v1/payments/:id over that authenticated, presumably-HTTPS channel instead of receiving it unauthenticated in the webhook body.

There aren't any published security advisories