CanaryRAG is designed around planted facts and reproducible signals so operators can distinguish real leakage from vague answer drift.
Companies are feeding private documents into RAG systems so AI can answer questions from internal data. The problem is that nobody can tell whether the AI is leaking private information or just generating plausible-sounding answers from its training data.
CanaryRAG solves this by injecting fake but realistic facts into your documents before they enter the RAG pipeline. These planted facts — fake names, fake numbers, fake dates — are unique to your documents and tracked by CanaryRAG.
If any AI system ever outputs one of these planted facts, you have proof that your document was accessed and its contents leaked. Not suspicion. Not correlation. Proof. The fake fact could only have come from your specific document.
Each planted fact gets a unique identifier (like CNRY-4829) and is monitored continuously. When a match is detected, CanaryRAG fires an alert with the exact fact that surfaced, the document it came from, and a trigger record holding the matched output and the detection timestamp. One correction to an earlier version of this post: that record lives in CanaryRAG's own table and is not written into the hash-sealed evidence log, so if you need a re-verifiable content hash for a RAG hit today, write it yourself through CanaryAudit.
This approach works because it converts a hard problem (did the AI leak my data?) into an easy problem (did a specific fake fact appear in an output?). The signal is binary and unambiguous.
This article is about a shipped surface: CanaryRAG — trap facts for documents. Integration details live in the docs.
Continue reading
More notes from the CanaryVaults team.
What a paste-site canary sees: the anatomy of a credential-stuffing hit
A walkthrough of the seeding pipeline end to end: how a decoy credential ends up on a paste site, what happens in the moments after someone tries to use it, and what lands in your alert channel.
PostmortemEvery page on our site was shipping an empty body
One call to useSearchParams() sat inside the root layout's only Suspense boundary, and deopted the entire application to client-side rendering. Twelve words of markup left our server. Nothing in the build said so.
EngineeringIt worked, it said so, and nothing happened
A contact form returned 201 and showed a green confirmation every time. Nobody was ever notified. We went looking for more of these and found about thirty, all with the same shape.