DEV Community

oleg-vdv
oleg-vdv

Posted on

"Your company banned ChatGPT. There's a third option."


Most companies handle the "employees are pasting customer data into ChatGPT" problem in one of two ways.

Option one: block it. Firewall rules, an acceptable-use policy, a stern email. What actually happens is that people use their phones, personal laptops, and a browser profile you don't manage. You haven't stopped the leak; you've stopped seeing it.

Option two: allow it and hope. Training, a wiki page about "don't paste PII", and an incident waiting to be discovered by someone else.

There's a third option that I don't think gets built often enough: let the request through, but strip the sensitive parts on the way out and put them back on the way in. The model never sees the customer's name. The employee never notices anything happened.

That's what I built AI-Gate for.

The idea in one example

An internal app sends this prompt:

Customer Yerzhan Nursultanuly, ID 900715300005, disputes a charge of 145,000 KZT...
Enter fullscreen mode Exit fullscreen mode

What actually leaves your network:

Customer [PERSON_1], ID [NATIONAL_ID_1], disputes a charge of [AMOUNT_1]...
Enter fullscreen mode Exit fullscreen mode

The model answers about [PERSON_1]. The gateway substitutes the real values back before the response reaches the application. From the app's perspective it just talked to OpenAI normally. From OpenAI's perspective, it never received a single piece of personal data.

The mapping between [PERSON_1] and the real name lives in memory only, encrypted, with a TTL and guaranteed destruction once the response is detokenized. It is never written to disk and never appears in the audit log — the log records types and counts, not values.

Integrating it is one line

For any application already using an OpenAI-compatible client, you change the base URL and nothing else:

from openai import OpenAI

client = OpenAI(
    base_url="http://aigate.internal:8080/v1",
    api_key="<channel key>",
)
resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Customer Yerzhan Nursultanuly, ID 900715300005..."}],
)
Enter fullscreen mode Exit fullscreen mode

A detail that matters more than it looks: the real provider API key lives only on the gateway. Applications get a channel key instead. Revoking one team's access stops being a redeployment and starts being a config change — and a leaked application key no longer means a leaked OpenAI key.

Three channels, because the leak isn't only in your code

An egress proxy covers your own applications, RAG pipelines and n8n workflows. It does not cover the two places people actually leak data:

The browser. A Chrome extension intercepts text before it's sent to chat.openai.com, claude.ai or gemini.google.com, masks it through the gateway, and substitutes values back into the assistant's reply as it renders. Deployable via GPO/MDM, so it's not opt-in.

The clipboard. A workstation agent catches sensitive data on its way into local LLM apps — Cursor, Claude Desktop — and detokenizes copied responses. This is the gap neither the browser nor the network perimeter can see, and it's the one that grows every month as AI moves into desktop tools.

Design decisions worth arguing about

Fail-closed, always. Any error in detection or parsing blocks the request instead of passing it through. This will occasionally annoy someone. The alternative is that a parser bug becomes a data breach, and the asymmetry there isn't close: a false block costs a retry, a false pass costs a regulatory fine and a disclosure letter.

Zero dependencies. The core is pure Python 3.11+ standard library. No PyPI at install time, which means it drops into air-gapped environments — exactly the kind of environment where this problem is most acute and where "just pip install" is not an available move. It also means the supply-chain attack surface of a tool that sees all your prompts is approximately zero.

Tamper-evident audit. The log is append-only JSONL with a SHA-256 hash chain. You can't quietly delete the record of a blocked request; verification detects it. For a compliance tool this is the difference between "we have logs" and "we have evidence".

Sensitive prompts can be routed to a local model. The provider registry supports OpenAI-compatible endpoints including vLLM, Ollama and LM Studio, plus the Anthropic Messages API. A policy rule can say: anything containing sensitive data goes to the local model only, never to a vendor. Masking and routing solve different halves of the same problem.

Semantic detection on top of pattern detection. Regexes catch IDs, account numbers and names. They do not catch "our Q3 acquisition target is a logistics company in Almaty with a 40% margin" — a leak with no PII in it at all. An LLM-judge runs over the already anonymized text and flags meaning-level leaks, in flag or block mode, fail-closed if the judge is unreachable.

Try it

cp .env.example .env      # set AIGATE_PROVIDER_API_KEY
python -m gateway.main    # gateway on :8080
Enter fullscreen mode Exit fullscreen mode

or on-prem in a container:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

The control plane console is at http://localhost:8080/admin — policies, RBAC, dashboards and compliance reports.

Where this is honest about its limits

Detection is imperfect and always will be. Names in particular are the hard case in every language, and a masking gateway that claims 100% recall is lying. Fail-closed helps, the local-model routing rule helps more, and semantic detection catches a class regexes structurally cannot — but the right mental model is defense in depth, not a solved problem.

It's also worth saying plainly: this reduces exposure, it doesn't create legal compliance by itself. What it gives you is the technical control and the evidence trail that a compliance program needs underneath it.

Repo: https://github.com/oleg-vdv/AI-Gateway

I'd be interested to hear how others are handling this. Has your organization landed on block, allow, or something in between — and if you've deployed a masking layer, what broke first?




---
Enter fullscreen mode Exit fullscreen mode

Top comments (0)