Your support bot is someone's free ChatGPT

July 21, 2026

The last two days were about attackers who want to hijack your bot. This one has a simpler goal, and it’s more common: they just want free inference. If you’ve put a paid model behind a public endpoint without much in front of it — an open chat widget, an unauthenticated API route — you have, functionally, published a free ChatGPT on your credit card. And people find these.

How it gets drained

Your public feature calls a paid model. An abuser scripts your endpoint and pipes their own prompts through it — coding help, essay writing, their own product’s LLM calls — all billed to you. Anything the model can do, they now do on your dime. Off-topic use is just you funding strangers’ AI.

They find it the way they find open S3 buckets and exposed Elasticsearch: scrapers, shared “free GPT proxy” lists, and anyone who opens browser devtools on your site and sees the network call to your chat route. An open LLM endpoint gets discovered fast — often within days of going live.

It’s not only a cost problem

This is LLM10 Unbounded Consumption with a human driving, but there’s a second bill: your provider account. Abusers generate content that violates the model provider’s usage policy — through your key. It’s your account that gets rate-limited, flagged, or banned. So a freeloading attack can take your real feature down, not just run up a number.

The defenses (auth + quotas — the base, new target)

The gate in code

The three deterministic controls — identify, meter, scope — are one small wrapper in front of the model call, not a rewrite of the feature:

from fastapi import FastAPI, Header, HTTPException, Depends
from datetime import date

app = FastAPI()

def caller(x_session: str = Header(...)) -> str:
    user = SESSIONS.get(x_session)                # (a) identify — no valid token,
    if not user:                                  #     no model. Anonymous traffic stops here.
        raise HTTPException(401, "auth required")
    return user

@app.post("/support/ask")
def ask(question: str, user: str = Depends(caller)):
    key = f"quota:{user}:{date.today()}"
    used = redis.incr(key); redis.expire(key, 86400)  # (b) per-identity daily counter
    if used > DAILY_LIMIT:                            #     one account can't drain the pool
        raise HTTPException(429, "daily limit reached")
    return llm.chat(messages=[                        # (c) scope to the task — SOFT, a cost-reducer
        {"role": "system", "content":
            "You are ACME's product-support bot. Answer only about ACME; refuse other requests."},
        {"role": "user", "content": question},
    ])                                                # your existing provider call, unchanged

The caller dependency turns away anonymous traffic before a single token is billed (a), and the Redis counter boxes each identity to its own daily budget so one scripted account can’t drain everything (b). Note the order: (a) and (b) are the real boundaries and they run first — the scoped system prompt (c) is soft, a cost-reducer an injection can argue with, so the quota sits in front of it, never behind it.

The honest part

Topic-locking via the prompt is soft. It’s an instruction, and — per Day 7 — instructions can be out-argued by an injection that “unlocks” the assistant. So treat topic-locking as a cost-reducer, not a boundary. The real boundaries here are the deterministic ones: auth, per-user quotas, and a hard cap. Those don’t care how clever the prompt is.

It's not just your bill — abuse can get your provider account banned.

And a legitimate exception: sometimes an open, unauthenticated demo is the point — a marketing trial, a public playground. That’s a fine choice. But then quotas, rate limits, a global cap, and abuse detection aren’t optional, and you’re deciding, deliberately, to accept some abuse as a capped marketing cost — with a ceiling you set, not one the attacker sets for you.

Next in the series — Day 10: when your bot promises a $1 truck — the risk that isn’t the bill at all, but the thing your bot says that you’re suddenly on the hook for.


If your AI feature is reachable without a login, assume someone’s already testing whether it’s free — and closing that is part of the audit I do. Every message comes straight to me — I read and reply to each one myself, usually within a day, and what readers send shapes what I build next. It’s just me for now, so that’s genuinely true; it won’t be forever. Send me your endpoint setup and I’ll tell you whether you’re running a free ChatGPT — free, within a business day.

Free live workshop: cap your AI spend (Oct 8)

A hands-on 90-minute session — wire a fail-closed spend cap + cost-aware rate limiting against a real stack, live, so a leaked key or runaway agent can't run up your bill. Full details & times → Save your seat (and get each new lesson as it lands):

Double opt-in — one email to confirm. The lessons are free; the course is optional. No spam, unsubscribe anytime.