Please do not open a public issue for security problems. Use GitHub's private vulnerability reporting on this repository, or email the maintainer listed in package.json.
Include a description, reproduction steps, and the versions affected. Expect an acknowledgement within a few days.
A phrasing that gets past contentSafety or promptInjection is a bug, but usually not a sensitive one — a public issue with a failing test case is the fastest fix. If the bypass produces genuinely harmful output, report it privately instead.
This is the most common way to get hurt with a RAG SDK, so it is worth being blunt.
Never ship a key you pay for to a browser. Any value in frontend JavaScript — including every VITE_, NEXT_PUBLIC_ or REACT_APP_ variable — is readable by anyone who opens devtools. It will be scraped and used.
Do this instead:
// browser
const client = createRagClient({ url: "/api/rag" });// server — the key never leaves this process
const engine = new RagEngine({ llm: new OpenAILLM({ apiKey: process.env.LLM_API_KEY }), ... });This is enforced, not just advised: @guardrag/core throws a RagError with code key_in_browser if any provider or vector store is constructed with a non-empty API key while window and document exist. There is no opt-out flag, because an opt-out is the thing that eventually gets copied into production.
Two deliberate exceptions, both of which hold no secret:
- An empty key is allowed.
openAICompatible({ baseUrl: "/llm", apiKey: "" })lets a browser talk to a same-origin gateway that injects credentials server-side. - Non-browser runtimes are unaffected. Deno, Bun, Cloudflare Workers, Vercel/Netlify edge and Supabase Edge Functions have
fetchbut nodocument, so they are treated as servers — which they are.
- Keys in environment variables or a secret manager, never in source or the client bundle
- Supabase service-role key server-side only; RLS enabled so the anon key cannot write
-
rateLimitguardrail plus infrastructure-level rate limiting — a single guardrail instance is per-process, so use a shared store (Redis) across replicas -
ALLOWED_ORIGINS/CORS restricted to your domains, not* - Request body size capped (the reference server uses 256 KB)
- Guardrail blocks logged and monitored; a spike is usually abuse or a scraper
- Provider spend limits and alerts configured — guardrails reduce cost, they don't cap it
Indirect prompt injection. If a document in your knowledge base contains "ignore your instructions and…", that text reaches the model as retrieved context. The system prompt explicitly labels passages as untrusted data, and the output guardrail strips prompt echoes — but you should also review what you ingest, particularly anything user-submitted or crawled.
Knowledge base leakage. The model can only reveal what retrieval hands it. If a document contains secrets, restrict it at the store level with metadata filters (ask({ filter: { tenant } })) or keep it out of the index. Enable pii: { redactOutput: true } if your corpus may contain personal data.
Multi-tenant isolation. Filters are applied at the store, not in the prompt. Always pass a tenant filter derived from the authenticated session on your server — never from a value the client sent.