A Cloudflare Workers RAG (Retrieval Augmented Generation) chatbot for technical blogs, with a transparent safety framework you can read, audit, and reuse.
The interesting part isn't the chatbot. It's the guardrail framework around it — six runtime defenses and four operational practices, documented and tested, that apply to any LLM-backed product behind a public input.
Runs entirely on Cloudflare's free tier. No external APIs. No npm dependencies in the Worker.
Most "AI safety" claims in production are vibes. "We use guardrails." "Built-in safety." Without specifics, that's the AI equivalent of "bank-grade encryption" — true of nothing in particular.
This repo takes the opposite approach: every defense is named, located in the source, paired with what it does not protect against, and backed by an automated eval suite. If the safety story isn't worth that level of openness, it isn't worth claiming.
Six runtime defenses execute in the Worker, deterministically, before and after the model:
- Prompt Injection Shield. NFKC Unicode normalization (so
homoglyph attacks like
ignorecollapse to ASCII), then 20+ pattern matches against known injection styles. Runs before the prompt ever reaches the LLM. - Topic Boundary. Seven category regexes block politics, religion, medical, legal, financial, NSFW, and violent content. The bot refuses politely with a short redirect rather than improvising answers outside its competence.
- Destructive Behavior Blocker. 15+ patterns cover SQL injection, XSS, hacking instructions, malware, credential theft. Refuses to roleplay these even with research-framing pretexts.
- Input Length Cap. Hard limit at 2,000 characters. Reduces prompt-stuffing surface and blocks the most common context-flooding attempts.
- Anti-Hallucination Threshold. Vector similarity score must clear 0.50 for a response to ground in retrieved sources. Below the threshold, the bot says it doesn't know rather than inventing an answer. Temperature locked at 0.2.
- Output Sanitizer. Widget renders LLM text via DOM construction
and
createElement, neverinnerHTML. Auto-detected URLs become real<a>elements through DOM APIs, so model output cannot inject markup into the page.
Four operational practices sit alongside the runtime defenses:
- Stateless. No chat history persisted, no cookies set by the widget.
- AI Disclaimer. Rendered on every answer, not just the first one.
- De-escalation Tone. Refusals stay friendly; redirect rather than scold.
- Versioned Releases.
x-bot-versionresponse header on every API call, git-tagged release commits, change log discipline.
The split between defenses and practices is the credibility move. A regex on every request is a defense — an attacker has to defeat it. A monthly review cadence is governance — useful, but not the same thing. Calling both "layers" inflates the count and obscures which controls actually do the blocking.
For the full specification, including what each defense does not catch, see docs/GUARDRAILS.md and docs/RESIDUAL_RISK.md.
Browser Widget
↓
Cloudflare Worker ← guardrails execute here
↓
Workers AI (embed) → Vectorize (search) → D1 (fetch content)
↓
Workers AI (generate)
↓
Answer + numbered citations + disclaimer
A single Worker handles every request. Pre-LLM filters run first; if any reject, the request never reaches the model. After generation, the output sanitizer runs and the response is shaped with citations and disclaimer before return.
See docs/ARCHITECTURE.md for the full request flow, data shapes, and component contracts.
| Layer | Technology |
|---|---|
| Runtime | Cloudflare Workers |
| Embeddings | BGE-Base v1.5 (768d) — Workers AI |
| Generation | Llama 3.1 8B Instruct — Workers AI |
| Vector store | Vectorize (cosine similarity) |
| Metadata + content | D1 (SQLite-compatible) |
| Widget | Vanilla HTML/CSS/JS, ~6KB |
Zero external APIs. Zero npm dependencies in the Worker. Compatible with any blog platform that supports footer code injection (Ghost, WordPress, Hugo, custom HTML).
| Resource | Free tier limit | Typical usage |
|---|---|---|
| Worker requests | 100,000 / day | < 100 / day |
| Workers AI neurons | 10,000 / day | ~ 500 / day |
| Vectorize vectors | 200,000 | 20–50 |
| D1 storage | 5 GB | < 1 MB |
| D1 reads/writes | 5M / month | < 1,000 |
€0 / month at typical blog-traffic scale. Structurally free — not a free trial, not a credit-card-required tier. Small Cloudflare account is the only requirement.
# Clone
git clone https://github.com/arusso-aboutcloud/aboutcloud-search.git
cd aboutcloud-search
# Authenticate
wrangler login
# Create resources (replace names with your own)
wrangler d1 create your-blog-db
wrangler vectorize create your-blog-index --dimensions=768 --metric=cosine
# Update wrangler.toml with the resource IDs that wrangler prints,
# then set your INDEX_SECRET (generate with: openssl rand -hex 32)
wrangler secret put INDEX_SECRET
# Deploy
wrangler deploy
# Index your content (see docs/HOWTO.md for the JSON schema)
curl -X POST https://your-worker.your-subdomain.workers.dev/index \
-H "X-Index-Secret: $YOUR_INDEX_SECRET" \
-H "Content-Type: application/json" \
-d @posts.json
# Test
curl -X POST https://your-worker.your-subdomain.workers.dev/chat \
-H "Content-Type: application/json" \
-d '{"message":"what does your blog say about topic X?"}'Full step-by-step in docs/HOWTO.md, including custom-domain setup, security headers, blog integration, and optional Cloudflare Access for admin endpoints.
The repository ships with a 47-case automated eval suite covering prompt injection, off-topic deflection, destructive-request blocking, hallucination prevention, and product-knowledge accuracy. An additional 10 adversarial cases — Unicode homoglyph attacks, multi-step jailbreaks, embedded payloads — are held out as a private set so improvements aren't fitted to the public benchmark.
Run against your own deployment:
cd evals
EVAL_TARGET_URL=https://your-worker.your-subdomain.workers.dev node run.mjsA pass-rate regression gates pull requests. Current public-set pass rate is documented in CHANGELOG.md per release.
| Document | Purpose |
|---|---|
| ARCHITECTURE.md | Request flow, data shapes, component contracts |
| COMPONENTS.md | Resource inventory and binding map |
| GUARDRAILS.md | Safety framework specification |
| RESIDUAL_RISK.md | What the system does not defend against |
| HOWTO.md | Step-by-step deployment guide |
| PUBLIC_PREP_SCRUB.md | Pre-release security review |
| CHANGELOG.md | Version history |
| SECURITY.md | Vulnerability reporting |
| CONTRIBUTING.md | PR process |
MIT. See LICENSE.
Built on Cloudflare Workers, Workers AI, Vectorize, and D1. Embedding model BGE-Base v1.5 by BAAI. Generation model Llama 3.1 8B Instruct by Meta.
Issues and PRs welcome. If you build something on top of this framework, a note would be appreciated.