LeakyByte

Before the model

Veil

Claude can help with a support ticket without ever seeing the customer.

Veil is a proxy that sits between your app and the Claude API. It swaps secrets and personal data for placeholders on the way out, and puts the real values back in the reply.

Try it in your browserSet it upEarly stage. Works locally today.

Try it

Edit the text on the left. Detection and swapping run in your browser and nothing is sent anywhere. The reply in step 3 is simulated, to show how restoring works.

1. Your app sends

Edit this text

5 sensitive values found

2. Claude receives

Placeholders only

Customer Dana Reyes (‹EMAIL_1›, ‹PHONE_1›) says checkout fails. Card on file: ‹CARD_1›. Server ‹IP_1› logs show our key ‹ANTHROPIC_KEY_1› leaking in the stack trace.

3. Your app gets back

Simulated reply, restored

Thanks. I'll email dana.reyes@northwind.example and rotate sk-ant-api03-Zk3vQ9xT2mLp8RwYc5HnJd7A right away.

Model saw: Thanks. I'll email ‹EMAIL_1› and rotate ‹ANTHROPIC_KEY_1› right away.

Why use it

Prompts collect sensitive data without anyone deciding to send it. A stack trace includes an API key. A support ticket includes a phone number and a card. A retrieved document includes an email address. Each of these goes to a third-party service in plain text.

You can clean every code path by hand, but each team ends up rewriting the same fragile pattern matching, and one missed path is enough. Veil does it in one place, for every request, with no change to your application logic.

Why placeholders instead of deleting

Veil replaces a value with a stable name, so Claude can still reason about it. The same email is always ‹EMAIL_1› within a request, so a question like who reported this and when did they last write in still makes sense. When Claude mentions ‹EMAIL_1› in its answer, Veil puts the real address back before your app sees it.

How it works

  1. 1

    Your app calls Veil

    Your SDK sends a normal Messages API request to Veil on your own machine, using your own Anthropic key.
  2. 2

    Veil scans the request

    Every string in the request body is scanned, including the system prompt, messages, tool definitions, tool results and tool inputs. Structural fields such as roles, ids and the model name are left alone.
  3. 3

    Values become placeholders

    Each detected value is replaced with a placeholder such as ‹EMAIL_1›. The mapping lives in memory for the length of that one request and is then discarded.
  4. 4

    Veil forwards the clean request

    The redacted request goes to the Claude API with your headers. Your key is passed through and never stored.
  5. 5

    Veil restores the reply

    Placeholders in the reply from Claude, in normal responses and in streams, are replaced with the original values. A placeholder split across two stream chunks is held back until it is complete.
  6. 6

    Veil writes an audit line

    One line per request records the time, model and how many values of each kind were swapped. It never records the values.

Set it up

You need Node 22.18 or newer. The proxy has no runtime dependencies and no build step.

1. Run the proxy

terminal
git clone https://github.com/tcvdh/leakybyte.git
cd leakybyte
npm install
npm run proxy
# LeakyByte proxy on http://127.0.0.1:8787

2. Point your SDK at it

Change the base URL. Nothing else in your code changes.

TypeScript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "http://127.0.0.1:8787", // the only change
});

const msg = await client.messages.create({
  model: "claude-sonnet-5-5",
  max_tokens: 300,
  messages: [{ role: "user", content: "Why did the deploy for dana@acme.io fail?" }],
});
Python
import anthropic

client = anthropic.Anthropic(base_url="http://127.0.0.1:8787")

Or test with curl

terminal
curl -i http://127.0.0.1:8787/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"claude-sonnet-5-5","max_tokens":200,
       "messages":[{"role":"user","content":"Email dana@acme.io about the outage"}]}'

The response includes a header, x-leakybyte-redacted: 1, with the number of distinct values that were swapped, and the reply contains the real address.

Example

This is real output from the command line tool, using the same engine as the proxy.

What your app sends
Customer dana.reyes@northwind.example called from +1 415-555-0134.
Card 4242 4242 4242 4242, server 10.4.2.19.
What Claude receives
Customer ‹EMAIL_1› called from ‹PHONE_1›.
Card ‹CARD_1›, server ‹IP_1›.

The audit log gets one line like this for the request:

leakybyte-audit.jsonl
{"ts":"2026-03-02T09:14:07.512Z","model":"claude-sonnet-5-5","stream":false,"redacted":{"EMAIL":1,"PHONE":1}}

What it detects

PlaceholderFindsNotes
‹ANTHROPIC_KEY_n›sk-ant-… keys
‹AWS_KEY_n›AKIA… and ASIA… access key ids
‹GITHUB_TOKEN_n›ghp_, gho_, ghu_, ghs_, ghr_ tokens
‹API_KEY_n›Other sk-… keysGeneric pattern
‹JWT_n›JSON Web Tokens
‹EMAIL_n›Email addresses
‹CARD_n›Card numbers, 13 to 19 digitsMust pass the Luhn check, so random digit strings are left alone
‹SSN_n›US social security numbersFormat 123-45-6789
‹PHONE_n›Phone numbersCommon international and US formats
‹IP_n›IPv4 addresses

Reference

Settings

VariableDefaultWhat it does
PORT8787Port to listen on
LEAKYBYTE_UPSTREAMhttps://api.anthropic.comWhere redacted requests are sent
LEAKYBYTE_AUDITleakybyte-audit.jsonlPath of the audit log, one JSON object per line

Routes

RequestWhat happens
POST /v1/messagesRedacted, forwarded, and restored. Normal and streaming responses.
POST /v1/messages/count_tokensRedacted and forwarded.
Any other POSTBlocked with status 501, so nothing leaves unredacted.
GET and other methodsForwarded as they are.

Errors

StatusMeaningWhat to do
400The request body was not valid JSONCheck the body your client sends
501A POST route Veil cannot redactUse /v1/messages, or call the API directly for that route
502Veil could not reach the upstreamCheck LEAKYBYTE_UPSTREAM and your network

Limits to know about

  • Detection is pattern-based. It does not find names, street addresses or other free-text personal details.
  • A secret split across two separate text blocks is not matched.
  • Error responses from the upstream are passed through as they are.
  • Tested against a mock Claude server, including streaming and tool calls. Not yet validated at scale against the live API.
  • If Claude writes a placeholder it was never given, it stays as written.

Using Veil on a real project?

Tell us what you are building and what is missing. We read and reply to every message.