🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
🕵️ SicherheitslückenBurn Out, Or Fade Away(14.09.2026 um 14:25 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11: Microsoft entfernt WMIC-Tool gegen Ransomware - ad-hoc-news.de(14.09.2026 um 07:58 Uhr)
🕵️ SicherheitslückenMicrosoft schließt Rekordzahl an Sicherheitslücken - techbook(14.09.2026 um 09:00 Uhr)
🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
🕵️ SicherheitslückenBurn Out, Or Fade Away(14.09.2026 um 14:25 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC(10.09.2026 um 20:11 Uhr)
⚠️ Malware / Trojaner / VirenWindows 11: Microsoft entfernt WMIC-Tool gegen Ransomware - ad-hoc-news.de(14.09.2026 um 07:58 Uhr)
🕵️ SicherheitslückenMicrosoft schließt Rekordzahl an Sicherheitslücken - techbook(14.09.2026 um 09:00 Uhr)

🔧 Programmierung 🕛 vor 1 Monat 8 Min Lesezeit
0

How I built an OpenAI-compatible prompt encryption proxy in 300 lines of Python

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

I sell GPT prompts. Small business, $20-50 per prompt, mostly to people who use ChatBox or Cursor. Last month, one of my buyers screenshotted my prompt text and posted it in a Discord server. Within 24 hours, three other people were reselling it on Telegram. I felt sick.



The obvious fix — "just don't ship the prompt text" — sounded dumb until I realized that's exactly what every SaaS does. ChatGPT doesn't ship OpenAI's system prompt. Midjourney doesn't ship its prompt. They ship access.



So I built a tiny proxy. ~300 lines of Python, FastAPI, and httpx. OpenAI-compatible. Streaming. Bearer key auth. The prompt lives on my server, the buyer gets an API URL + key, they point their client at it, my server injects the system prompt, forwards to the AI provider, returns the response. The buyer never sees the prompt text.



This post walks through the architecture, the interesting bits, and a few mistakes I made along the way. The full Lite version is — but everything in this article is in the open-source Lite version.









The problem in one diagram






CODE
[Buyer's ChatBox]                     [My server]                    [DeepSeek API]
| | |
| POST /v1/chat/completions | |
| Authorization: Bearer sk-... | |
| {model, messages: [{user,...}]} | |
| --------------------------------> | |
| | 1. verify bearer key |
| | 2. load prompt from config |
| | 3. prepend as system message |
| | 4. forward to upstream |
| | ------------------------------>|
| | |
| | <--- response (stream ok) ----|
| | |
| <--- stream chunks --------------| |
| | |






The buyer's request is identical to what they'd send to OpenAI. The only difference is the URL and the API key.









The interesting bits






1. Streaming without buffering



The buyer is waiting for tokens. If you buffer the entire upstream response before returning, you've killed the UX.




CODE
async def _stream_upstream(url, headers, payload):
timeout = httpx.Timeout(connect=10.0, read=120.0, write=10.0, pool=10.0)
async with httpx.AsyncClient(timeout=timeout) as client:
async with client.stream("POST", url, headers=headers, json=payload) as resp:
if resp.is_error:
body = await resp.aread()
err = json.dumps({"error": {"code": resp.status_code, "message": body.decode("utf-8", "replace")}})
yield f"data: {err}\n\n"
yield "data: [DONE]\n\n"
return
async for line in resp.aiter_lines():
if not line:
continue
if line.startswith("data:"):
yield line + "\n\n"
else:
yield f"data: {line}\n\n"
yield "data: [DONE]\n\n"






FastAPI's StreamingResponse will iterate this async generator and flush each chunk to the client immediately. No buffering. The buyer sees the first token as soon as the upstream emits it.



Mistake I made first: I returned response.aiter_raw() directly without re-emitting the data: prefix. ChatBox silently ate the stream because it expected SSE format. Always re-emit in SSE format.






2. System prompt injection (with merge)



The buyer may have configured their own system prompt in ChatBox. You don't want to overwrite it — you want to prepend yours.




CODE
def _inject_system_prompt(messages):
config = load_prompts()
system_text = (config.get("system_prompt") or "").strip()
if not system_text:
return messages

if messages and messages[0].get("role") == "system":
merged = {
"role": "system",
"content": system_text + "\n\n" + messages[0].get("content", ""),
}
return [merged, *messages[1:]]

return [{"role": "system", "content": system_text}, *messages]






Mistake I made first: I appended my system message at the end of the message list. The model treated it as a user instruction and the buyer's prompt leaked through. Always prepend, always merge if there's already a system message.






3. API key auth with constant-time comparison






CODE
import secrets
from fastapi import Header, HTTPException, status

async def verify_client_key(authorization: str | None = Header(default=None)) -> str:
if not authorization or not authorization.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="Missing or invalid Authorization header")
token = authorization[7:].strip()
for configured in settings.client_keys:
if secrets.compare_digest(token, configured):
return token
raise HTTPException(status_code=401, detail="Invalid API key")






secrets.compare_digest is constant-time. token == configured is not — it short-circuits on the first different byte, which leaks key length and prefix info via timing. For 32-character API keys this is a minor risk, but it's free to do correctly.






4. Forcing the upstream model (optional, but useful)



Some buyers will configure gpt-4 in their client even though your upstream is DeepSeek. You can either reject the request or silently override:




CODE
def _override_model(model):
forced = (load_prompts().get("force_model") or "").strip()
return forced or model






I force the model. Buyers don't care which model runs, they care about the result. Forcing also means you can swap upstream providers without telling buyers.









What the Lite version doesn't do (and why I built the Pro version)



After shipping the Lite version, I realized I needed more to actually run a prompt-selling business:





  1. Plaintext prompt storage is a problem. Anyone with shell access to the server can cat prompts.yaml. The Pro version uses AES-256-GCM encryption in SQLite.


  2. Single API key list doesn't scale. If I have 30 buyers, I can't tell who's using what, I can't revoke one without rotating everyone, I can't set expiry. The Pro version has a clients table with per-client keys + expiry + active flag.


  3. No usage analytics. I can't tell which prompts are popular, which clients are heavy users, which requests are failing. The Pro version has a usage_logs table.


  4. One AI backend is limiting. Some prompts work better with Claude, some with DeepSeek, some with GPT-4. The Pro version has an adapter layer that routes per-prompt.


  5. No admin UI. Editing YAML + restarting the server is fine for me, but if I want to delegate client management, I need a web panel.



These are all in the

  • Pro (self-host paid): where I post about indie hacking + Python + AI tooling. And if you want to sell prompts but don't want to build the proxy yourself, the Pro version is $49 for personal use.









    Footer note (Dev.to auto-injects author card)



    Article by tlyyxjz — indie hacker, Python dev, building tools for the AI creator economy.

    Vollständiger Original-Bericht
    Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
    ↗ Original-Artikel auf dev.to lesen
  • Wie bewertest du diesen Beitrag?
    1 Klick Feedback
    Teilen mit Netzwerk & Team:

    Community-Analysen & Experten-Meinungen 0

    Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
    Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
    Community Pulse: Relevanz-Einschätzung
    1 Klick Experten-Votum
    🔴 Akute Relevanz 0%
    🟡 In Evaluierung 0%
    🟢 Keine Auswirkung 0%
    Spannende Innovation 0%
    Verwandte Story-Cluster & Quellen (Vektor-KI)
    Port 8095 Engine
    1 Quelle
    The Gemini desktop app is now available for Windows
    1 Quelle
    Burn Out, Or Fade Away
    1 Quelle
    Windows 11 just dropped the tool ransomware abused, Microsoft says don’t restore WMIC
    Ähnliche Beiträge
    🔍 Verwandte News

    Auch interessante Nachrichten How I built an OpenAI-compatible prompt encryption proxy in 300 lines of Python

    Thematisch verwandte Begriffe: built, OpenAIcompatible, prompt, encryption · 6 Treffer

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...

    Laden...

    Beiträge werden geladen ...

    Laden...

    Videos werden geladen ...