One signed Docker image. Every feature compiled in. Free to run. docker pull crowkis/crowkis:latest
← back to the Roost
securityApril 30, 2026· 9 min read

Cache poisoning is the whole problem

Semantic caching has an obvious failure mode nobody likes to talk about: one bad write, served forever to everyone nearby. This is how Crowkis decides what to trust.

Here is the uncomfortable math of semantic caching. A normal cache serves a bad entry to exactly the requests that match its key. A semantic cache serves a bad entry to every request that lands near it in embedding space. Poison doesn't sit in one cell — it radiates.

the blast radius of one bad entry

Semantic reach is the feature — and, untreated, the vulnerability.

In plain words: In plain words: a smart cache that matches by meaning will also spread a wrong answer by meaning. If one bad response gets in, everyone who asks anything similar gets it back. That's why write-time defense matters more than read-time speed.

And LLM systems produce bad writes constantly. Hallucinations. Prompt injections that smuggle instructions into what looks like an answer. A response computed for tenant A that would be a data leak if tenant B ever saw it. If your cache trusts every write, your cache is an amplifier for your worst outputs.

Five stages, every write

the write-trust pipeline

Weighted, not unanimous — but heavily tilted toward coherence and history.

The two heavyweights are coherence and source trust, and that's deliberate. Coherence catches injected content that doesn't actually answer the question. Source trust means a writer that produced garbage before has to earn its way back — every accept and refuse lands in an append-only ledger, so trust has memory.

Trust has memory

trust ledger, conceptually
2026-04-29T14:02:11  writer=svc-support  ACCEPT  composite=0.91
2026-04-29T14:02:38  writer=svc-support  ACCEPT  composite=0.88
2026-04-29T14:03:02  writer=ext-webhook  REFUSE  stage=1 coherence=0.31
2026-04-29T14:03:05  writer=ext-webhook  REFUSE  stage=3 trust=0.22
# ext-webhook now faces a higher bar on every future write
A cache that can't say no isn't infrastructure. It's a liability with a hit rate.

Refusal is a feature you can see

Blocked writes show up in the dashboard's live feed with the stage that vetoed them, and the rejection counters break down by stage so you can see what kind of poison your system actually attracts. That visibility matters more than it sounds: the first question every operator asks about a safety system is 'what is it actually doing?' — and the answer should never be 'trust us.'

Tenant isolation deserves the last word. It isn't only an access-control rule at read time — it's a scored stage at write time. An entry that smells like it crossed a tenant boundary doesn't get a chance to be mis-served later, because it never enters the cache at all.