SecurityApril 23, 2026·8 min read

How Gostly AI handles security

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.

The two-store design

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.

What never leaves your machine

Raw traffic files

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.

Request and response bodies

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.

Header redaction — the non-negotiable floor

Credential headers are redacted before any I/O occurs — at the moment the proxy reads the request, before writing to disk. 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 Rust proxy code, not in a policy document.

Why structural, not policy?

A policy that says "we don't write credentials to disk" is only as strong as the process that enforces it. Structural enforcement means there's no code path that bypasses the redaction — the headers are stripped in the same function that processes every request, before the write even begins.

The AI pipeline — local by design

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.

No request bodies, response bodies, or API payloads are sent to external model providers. The model (a fine-tuned Qwen variant) runs locally. The retrieval index (for RAG) is built from your scrubbed Postgres rows and stays in your stack.

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.

Scrub configuration — your rules on top

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.

Tenant isolation — Postgres row-level security

Multi-tenant deployments enforce tenant isolation at the database, not the application layer. Every tenant-scoped table has Postgres ROW LEVEL SECURITY enabled with a policy that scopes every read and every write to the authenticated tenant. The API binds the tenant per-request from the session before any query runs.

Two database roles back this. The role the API connects with has no RLS bypass — a forgotten WHERE tenant_id = …in application code, a SQL injection that escapes parameter binding, or any in-process bug cannot return rows from another tenant. Postgres rejects the query. A separate elevated role exists only for cron jobs and migrations, with its credentials scoped to a connection pool that doesn't see request traffic.

Defense in depth, not single-point-of-failure

Application-level WHERE tenant_id = … filters still run; RLS is the second wall. The point is that when human code forgets, the database remembers.

Authentication — local password, SAML 2.0, OIDC

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.

Authorization — RBAC with four roles

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 via the <FeatureGate> component, 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.

Audit log

Team-tier deployments record every state-changing action — user invitations, role changes, scrub-config edits, service additions — to a tenant-scoped audit log that admins can query through the API. The log itself is RLS-isolated: an admin in tenant A cannot see actions performed in tenant B, no matter which connection pool the request comes through.

What Gostly sees

To be transparent about what does leave your machine:

License validation

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.

Docker image pulls

When you pull Gostly images from registry.gostly.ai, standard Docker registry logs apply (image name, version, authenticated user).

Nothing else

No traffic data, no API payloads, no response bodies, no scrubbed records are transmitted to Gostly.

Air-gapped deployments

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 summary

The security model is:

  • Credential headers are redacted at the proxy before any disk write. Structural, not policy.
  • Raw traffic never leaves your machine.
  • Bodies are scrubbed locally before writing to Postgres. The scrubbed_at seal prevents raw data from overwriting scrubbed rows.
  • Tenant isolation enforced by Postgres row-level security — a forgotten WHERE clause cannot leak rows.
  • SAML 2.0 and OIDC supported on Team; local bcrypt password auth on every tier.
  • Four-role RBAC (owner / admin / member / viewer) with audit log on Team.
  • The AI pipeline is local — no payloads go to external model providers.
  • Only license validation data leaves your machine, and only to validate your subscription tier.

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.