A proxy that records API traffic is a proxy that sees credentials, tokens, and potentially sensitive response data. We take that seriously — not as a policy promise, but as an architectural constraint. Here's exactly what we do and why.
The most common question we get from security-conscious teams is straightforward: "Your proxy sees every API call. What happens to my credentials?"
It's the right question to ask. Here's the complete answer.
Gostly uses two separate data stores with different purposes and different safety properties. Understanding the distinction is the foundation of the security model.
Local JSONL (verbatim)
Raw recorded traffic. Lives on your machine only. Headers are redacted at write time. Response bodies are verbatim — this is intentional for test fidelity. Never transmitted externally.
Postgres (scrubbed)
Scrubbed mock library. PII and credentials removed by the transition pipeline. Used for AI training and team deployments. Safe to move off-machine.
The verbatim store and the scrubbed store are never conflated. Each has its own invariants and the architecture enforces them.
The JSONL traffic files written during LEARN mode live in a Docker volume on the machine running the proxy. They are never transmitted to Gostly servers, never included in telemetry, and never read by any process outside your stack.
In a managed Postgres (Team tier) deployment, the Postgres rows may live outside your local machine — but only scrubbed rows, never raw ones. The scrubbing pipeline runs locally before anything is written to Postgres.
Response bodies are written verbatim to the local JSONL files. This is intentional — tests that pattern-match on specific field values only work because the data is accurate. Gostly doesn't infer what the API probably returns; it records what it actually returned in whichever environment you pointed it at (local, staging, or production — your choice).
When you trigger a transition, the scrubbing pipeline applies your configured redaction rules to bodies before writing to Postgres. Configured scrub paths (e.g. $.user.email, $.payment.card_number) are replaced with [REDACTED] and the row is marked with a scrubbed_at timestamp that acts as a permanent safety boundary. A scrubbed row can never be overwritten with raw data.
Credential headers are scrubbed by default on every sink that touches disk or leaves the box — the on-disk replay JSONL, the Postgres-backed library, and every shipped or exported stream. This is the non-overridable floor:
authorization proxy-authorization cookie set-cookie x-api-key x-auth-token x-session-id x-access-token x-user-token x-csrf-token x-amz-security-token x-amz-session-token x-goog-api-key grpc-metadata-authorization api-key token
This list cannot be removed or replaced by configuration. The REDACT_HEADERS environment variable adds to it — it cannot shrink it. The enforcement is structural: it lives in the proxy itself, not in a policy document.
Privacy by sink, not a single switch
A policy that says "we don't write credentials to disk" is only as strong as the process that enforces it. We enforce it structurally, per sink, on the same path that processes every request. Three sinks, three rules: every shipped or exported stream is always redacted, no opt-out; the on-disk replay JSONL is redacted by default, withMOCK_PERSIST_VERBATIMas an explicit air-gap-only opt-out; and one in-memory store keeps headers verbatim on purpose so stateful flows (Set-Cookie, CSRF, OAuth) replay byte-perfect — that store is RAM-only, dropped on restart, and never written or transmitted. Fidelity where it can't leave the box; redaction everywhere it can.The inference server (the component that generates AI responses for unmatched requests) runs inside your Docker stack. It receives requests from the proxy over the internal Docker network.
By default, no request bodies, response bodies, or API payloads leave the box: the model (a fine-tuned Qwen variant) runs locally, and the retrieval index (for RAG) is built from your scrubbed Postgres rows and stays in your stack. There is an optional bring-your-own-key cloud-LLM backend (OpenAI / Azure / Bedrock) in the image, but it is off unless you explicitly configure it — nothing is sent to an external provider until you turn it on with your own key.
The only external calls the AI pipeline makes are to the Gostly license server to validate your subscription tier. Those calls contain your license key and a machine identifier — no traffic data.
Beyond the header floor, the redaction list is configurable via the REDACT_HEADERS environment variable (additive only — see above) and via the per-service scrub config in the dashboard at gostly.ai/dashboard/settings/scrub. That page lets you add JSONPath body rules and extra header redactions on top of every default pattern.
Available now — body-level scrub config
PUT /v1/scrub/config upserts your JSONPath body rules; POST /v1/scrub/test previews any rule set against a sample payload before you save. The dashboard surface lives at /settings/scrub.PUT /v1/scrub/config
{
"service_id": "stripe",
"jsonpath_rules": [
{ "path": "$.user.email", "replacement": "<scrubbed>" },
{ "path": "$.source.card.number", "replacement": "<card>" },
{ "path": "$.source.card.cvc", "replacement": "<cvc>" }
],
"header_redactions": ["Authorization", "Cookie", "X-Api-Key", "X-Custom-Auth"],
"enabled": true
}JSONPath expressions are validated at save time — a path that would replace the entire payload (e.g. $ or $..*) is rejected. The goal is targeted redaction, not blanket removal that breaks your test data. The default pattern-based scrub continues to run underneath every per-service config — you can add to the floor, never lower it.
Gostly is self-hosted and single-tenant per deployment. Your captured traffic, your mocks, and your scrubbed library live in your own database — they are never co-mingled with another customer's data, because another customer's data is never in the same deployment to begin with.
Within a deployment, every query is tenant-scoped in application code: the API binds the tenant per-request from the session before any query runs, and that scoping is the authoritative isolation control. On top of that, all tenant-scoped tables carry per-tenant Postgres Row-Level Security policies as a defense-in-depth layer — a second posture defined at the data layer rather than a guarantee the application leans on.
Isolation by deployment, policies as defense in depth
The strongest isolation property is structural: single-tenant per deployment means there is no other tenant's data in the box to leak. Application-level tenant scoping is the authoritative control on every query; the Row-Level Security policies on all tenant-scoped tables sit underneath it as defense in depth.Self-hosted deployments default to local password auth: bcrypt with a cost factor of 12, password length capped at 72 bytes (the bcrypt limit, not a policy choice), and session cookies marked HttpOnly and Secure.
Team-tier deployments unlock SAML 2.0 and OIDC alongside or in place of password auth. Group-to-role mapping is driven by the IdP's assertion attributes, with a configurable default for users whose groups don't map. Setup specifics — IdP metadata, attribute mapping, role assignment — live in the SSO documentation.
Multi-user workspaces (Team tier) ship four built-in roles:
owner — full control; cannot demote the last owner in a tenant admin — manage users, services, scrub config; cannot delete the workspace member — record traffic, manage their own services viewer — read-only access to dashboards and recorded traffic
Role enforcement runs at every gated endpoint and is mirrored in the dashboard UI, so a viewer who tries to access a member-only page sees the same answer the API would give them. Free and Pro tiers run in single-user mode with the admin auto-bootstrapped on first boot.
Team-tier deployments record every state-changing action — user invitations, role changes, scrub-config edits, service additions — to an append-only, tenant-scoped audit log that admins can query through the API. Entries are written once and never edited or deleted in place, so the log is a durable record of who changed what and when.
To be transparent about what does leave your machine:
Your license key and a machine fingerprint are sent to api.gostly.ai on startup and cached locally for 24 hours. This is how tier features are gated.
When you pull Gostly images from registry.gostly.ai, standard Docker registry logs apply (image name, version, authenticated user).
No traffic data, no API payloads, no response bodies, no scrubbed records are transmitted to Gostly.
Full air-gapped deployment support — local JWKS for license verification, offline image distribution, and a sealed update channel — is on the roadmap for compliance-driven buyers. If you need to evaluate Gostly inside a restricted-egress perimeter today, reach out at security@gostly.ai and we'll work through it with you.
The security model is:
If you have specific questions about data handling for a regulated industry deployment — healthcare, financial services, or anything with specific compliance requirements — reach out at security@gostly.ai. We'll give you a straight answer.