A single paste into a chat window can expose more than you intend. API keys, internal hostnames, and customer emails travel in the prompt. Free model endpoints may store or log that text. You cannot inspect their retention after the request leaves.
The safer pattern is to redact before you send. This tutorial builds a local HTTP proxy. It strips common secrets from outgoing JSON. It logs only a one-way hash of removed values.
MonkeyCode's free model access is one way to run this experiment without a paid account. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The code works with any JSON chat-completion endpoint.
The problem
Chat windows make it easy to paste whole files. Those files often contain secrets. A free model endpoint is not your private vault. Its logs may outlive your session.
You need a boundary that you control. The boundary should act before the request leaves your machine.
What you build
You build two small Python files.
-
proxy.pysits between your client and the model endpoint. -
mock_upstream.pypretends to be the endpoint for offline tests.
The proxy follows four steps.
- Read the JSON body.
- Redact matching text.
- Forward only the cleaned body.
- Return the upstream response unchanged.
Step 1: Define redaction rules
Start with rules for common tokens. Keep the rules explicit. Do not try to catch every possible secret.
| Rule | Pattern | Replacement |
|---|---|---|
| API key | sk-[A-Za-z0-9_-]{16,} |
[REDACTED_API_KEY] |
| JWT | eyJ[A-Za-z0-9_-]{10,}[.][A-Za-z0-9_-]{10,}[.][A-Za-z0-9_-]{10,} |
[REDACTED_JWT] |
[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[.][A-Za-z]{2,} |
[REDACTED_EMAIL] |
|
| AWS key | AKIA[0-9A-Z]{16} |
[REDACTED_AWS_KEY] |
| Private key block | -----BEGIN [A-Z ]*PRIVATE KEY----- |
[REDACTED_PRIVATE_KEY] |
Add one rule for your own secret format. A narrow rule beats a broad one.
Step 2: Write the proxy
Create proxy.py.
import hashlib
import json
import os
import re
import urllib.request
from http.server import BaseHTTPRequestHandler, HTTPServer
UPSTREAM = os.environ.get('UPSTREAM_URL', 'http://127.0.0.1:9000/echo')
KEY = os.environ.get('UPSTREAM_API_KEY', '')
RULES = [
('sk-[A-Za-z0-9_-]{16,}', '[REDACTED_API_KEY]'),
('eyJ[A-Za-z0-9_-]{10,}[.][A-Za-z0-9_-]{10,}[.][A-Za-z0-9_-]{10,}', '[REDACTED_JWT]'),
('[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[.][A-Za-z]{2,}', '[REDACTED_EMAIL]'),
('AKIA[0-9A-Z]{16}', '[REDACTED_AWS_KEY]'),
('-----BEGIN [A-Z ]*PRIVATE KEY-----', '[REDACTED_PRIVATE_KEY]'),
]
def redact(value):
if isinstance(value, dict):
return {k: redact(v) for k, v in value.items()}
if isinstance(value, list):
return [redact(item) for item in value]
if isinstance(value, str):
for pattern, replacement in RULES:
match = re.search(pattern, value)
if match:
token = match.group(0)
digest = hashlib.sha256(token.encode()).hexdigest()[:12]
print('redacted ' + digest, flush=True)
value = re.sub(pattern, replacement, value)
return value
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get('content-length', '0'))
raw = self.rfile.read(length)
data = json.loads(raw.decode('utf-8'))
cleaned = redact(data)
req = urllib.request.Request(
UPSTREAM,
data=json.dumps(cleaned).encode('utf-8'),
method='POST',
headers={'content-type': 'application/json'},
)
if KEY:
req.add_header('authorization', 'Bearer ' + KEY)
with urllib.request.urlopen(req, timeout=30) as resp:
body = resp.read()
self.send_response(resp.status)
self.send_header('content-type', 'application/json')
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt, *args):
pass
HTTPServer(('127.0.0.1', 8000), Handler).serve_forever()
The proxy does not print the secret. It prints only a short hash. That hash is enough to confirm a rule fired.
Step 3: Add a mock upstream
Create mock_upstream.py.
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get('content-length', '0'))
raw = self.rfile.read(length)
print('UPSTREAM_GOT ' + raw.decode('utf-8'), flush=True)
self.send_response(200)
self.send_header('content-type', 'application/json')
self.end_headers()
payload = json.dumps({'choices': [{'message': {'content': 'ok'}}]}).encode('utf-8')
self.wfile.write(payload)
HTTPServer(('127.0.0.1', 9000), Handler).serve_forever()
This file prints exactly what the proxy would forward. Use it to verify redaction before you call a real provider.
Step 4: Run the verification
Start the mock upstream in one terminal.
python mock_upstream.py
Start the proxy in a second terminal.
python proxy.py
Send a fixture from a third terminal.
import json
import urllib.request
payload = {
'model': 'example',
'messages': [
{'role': 'user', 'content': 'Fix sk-12345678901234567890 for dev@example.com'}
]
}
req = urllib.request.Request(
'http://127.0.0.1:8000/',
data=json.dumps(payload).encode('utf-8'),
method='POST',
headers={'content-type': 'application/json'},
)
print(urllib.request.urlopen(req).read().decode())
Check the mock terminal. It should show [REDACTED_API_KEY] and [REDACTED_EMAIL]. It should not show sk-12345678901234567890 or dev@example.com.
The proxy terminal should show two short hashes. Those hashes are not reversible for high-entropy secrets.
Step 5: Point it at a real endpoint
Set the upstream variables and restart the proxy.
export UPSTREAM_URL='https://your-provider.example/v1/chat/completions'
export UPSTREAM_API_KEY='your-key-here'
python proxy.py
MonkeyCode's free model access can be used as the upstream if the provider gives a standard chat-completion URL. The free server option may also host this script if it permits a long-running Python process. Verify current limits first.
Do not hardcode the key in source control. Use environment variables or a secret manager.
Limitations
Regex redaction is a best-effort filter. It will miss secrets in screenshots, PDFs, or base64 blobs. It will also miss unknown token formats.
The local proxy adds one network hop. It is not a replacement for access controls or data classification. Do not use this for regulated data such as health records or payment details.
Do not use this if your team requires approved DLP tooling. This tutorial is a small technical control, not a compliance program.
This version handles non-streaming JSON only. Streaming responses need a different transport path.
Who should not use this
Skip this if you upload files rather than text prompts. Skip it if the extra process is unacceptable. Skip it if you cannot maintain the regex rules over time.
A stale rule gives false confidence. The proxy is only as good as its rule list.
Summary
Redact at the boundary, not inside the prompt. A 60-line proxy stops obvious leaks before they reach an external API. Test with a mock upstream so you can see exactly what leaves your machine.
Run the mock test once. Then replace the upstream with an endpoint you already use. Add one rule that matches your own secret format.
Top comments (0)