Project Info
Inspiration
Companies are shipping chatbots everywhere, and almost all of them carry something sensitive in their prompt — an API key, an internal policy, a "verified staff only" rule. The dangerous flaw is never the obvious request; it's the plausible one — the reasonable-sounding exception that becomes the exact door an attacker walks through. I wanted a tool that thinks like that attacker, then does what no scanner does: closes the hole and proves the fix.
What it does
Point Gauntlet at a chatbot and give it a goal. It runs an autonomous, escalating attack campaign — five social-engineering tactics (impersonation, fake incidents, encoding tricks, fiction, prompt injection) across three escalation levels. An LLM judge scores how much actually leaked, a reflector decides whether to push harder or pivot, and the moment it breaks the bot it self-heals: rewrites the prompt to close the loophole, re-fires the exact winning attack, and confirms the bot now refuses. It works against an API bot or a real chat widget in a live browser.
How we built it
A five-agent loop — strategist → attacker → target → judge → reflector — orchestrated with LangGraph, every agent powered by Claude Opus 4.8. The key idea was separating control from creativity: a deterministic policy owns the decisions (escalate/pivot/stop, what's untried) so the loop always makes progress and terminates, while Claude owns the attack craft. Around it: a FastAPI backend streaming the session over SSE, a Redis + RedisVL vector store that remembers winning exploits as few-shot examples, Browserbase + Playwright for live-browser mode, Phoenix/OpenTelemetry tracing, and a Next.js dashboard.
Challenges we ran into
Python 3.14 broke OpenTelemetry — Phoenix's auto-instrumentation crashed with a cryptic generator error before our heal loop could run; i switched to manual spans and moved healing outside the tracing context. A stale production build cost me hours — the dashboard showed nothing because next start was serving a build compiled before the feature existed; no refresh could fix it. Streaming through a 30-second silence — the patcher's long LLM call dropped the SSE connection, so i added keepalives plus a guaranteed final fetch. Honest metrics — the scoreboard was a frozen snapshot; i rebuilt it to compute live from real run outcomes.
Accomplishments we're proud of
A red-teaming loop that's both creative and reliable — it never stalls, never repeats a failed approach, and always terminates. A semantic judge that catches paraphrased and encoded leaks, not just exact strings. And self-healing that's real, not cosmetic — it re-runs the actual winning attack against the patched bot and proves it holds. Plus a live dashboard that makes the whole attack legible to a non-expert in real time.
What we learned
The most dangerous prompt is never the obviously dangerous one — it's the reasonable exception. Semantic judging beats string matching decisively. LLM agents need a deterministic skeleton to be trustworthy. And a "fix" only means something if you re-test the original attack against it.
What's next
for Team Gauntlet Expand the tactic library and support multi-secret, multi-turn objectives; let teams point Gauntlet at their own bots and prompts directly; add regression mode (re-run every past exploit on each deploy as a CI gate); generate prioritized hardening reports; and grow the exploit memory into a shared, continuously-learning threat library across runs.
Gauntlet
Break your bot before someone else does.
Gauntlet attacks your chatbot like a real social engineer, shows you exactly how it got in, then patches the weakness and proves the fix works.
📖 The full project story (inspiration, what we learned, challenges) lives in ABOUT.md.
What it does
Point Gauntlet at a chatbot, give it a goal ("extract the secret access code"), and it runs an autonomous, escalating attack campaign:
- Attack — five social-engineering tactics (authority impersonation, urgency pretext, format/encoding tricks, hypothetical fiction, prompt injection) across three escalation levels, from naive to multi-turn and obfuscated.
- Judge — an LLM scores how much the bot actually leaked, semantically (catches paraphrased, partial, and encoded leaks — not just exact strings).
- Reflect — reads why the bot held or slipped and decides to escalate, pivot, or stop.
- Self-heal — on a breach, a patcher agent rewrites the system prompt to close the exact loophole, hot-swaps it live, re-fires the winning attack, and confirms the bot now refuses.
- Remember — every successful exploit is saved to a vector store so the attacker starts smarter next time.
Runs against a Claude-backed bot directly, or against a real chat widget in a live cloud browser.
Architecture
A five-agent escalation loop orchestrated with LangGraph:
strategist → attacker → target → judge → reflector ─┐
▲ │
└──────────── loop until done ───────────────────┘
│
(done) → finalize → self-heal
Key design idea: deterministic control + LLM creativity. The loop's decisions (escalate / pivot / stop, which tactic·level to try next) are governed by a deterministic policy that guarantees forward progress and termination; the attack content — personas, pretexts, wording — is authored by Claude (Opus 4.8).
Stack
| Layer | Tech |
|---|---|
| Agent orchestration | LangGraph, Claude Opus 4.8 |
| Backend / streaming | FastAPI, Server-Sent Events |
| Exploit memory | Redis + RedisVL (vector search) |
| Live-browser mode | Browserbase + Playwright |
| Observability | Phoenix / OpenTelemetry |
| Dashboard | Next.js + React |
| Module | Role |
|---|---|
| gauntlet/graph.py | LangGraph escalation loop |
| gauntlet/strategist.py | picks tactic + escalation level |
| gauntlet/attacker.py | crafts the adversarial message(s) |
| gauntlet/target.py · gauntlet/browser_target.py | bot under test (API or live widget) |
| gauntlet/judge.py | semantic leak scoring |
| gauntlet/reflector.py | escalate / pivot / stop |
| gauntlet/patcher.py | hardens the prompt on a breach |
| gauntlet/memory.py | Redis vector exploit library |
| gauntlet/api.py | FastAPI + SSE + self-heal loop |
| gauntlet/eval_harness.py · gauntlet/panel.py | 8-bot benchmark + metrics |
Running it
Prerequisites: Python 3.14, Node 18+, and a env file (not committed) with at least:
ANTHROPIC_API_KEY=...
# optional — enables the exploit memory and live metrics mirror
REDIS_URL=redis://localhost:6379
# optional — enables live-website (browser) mode
BROWSERBASE_API_KEY=...
BROWSERBASE_PROJECT_ID=...
# optional — Phoenix tracing
PHOENIX_API_KEY=...
PHOENIX_COLLECTOR_ENDPOINT=...
PHOENIX_PROJECT=gauntlet
Backend (auto-reloads on edit):
pip install -r requirements.txt
uvicorn gauntlet.api:app --reload --port 8000
Frontend:
cd gauntlet-ui
npm install
npm run dev # http://localhost:3000 (hot-reloads)
# or, for a production build:
npm run build && npm run start
⚠️ In production mode (
next start), source edits requirenpm run build+ a server restart before they appear —next devhot-reloads instead.
Evaluation harness (runs the full 8-bot panel and writes the baseline metrics):
python -m gauntlet.eval_harness # full panel
python -m gauntlet.eval_harness --bot acme_incident # single bot
Redis is optional — without it, the pipeline runs fine, just without the
exploit memory. For the vector memory you need redis-stack (RediSearch
module), e.g. docker run -p 6379:6379 redis/redis-stack-server:latest.
Note on safety
This is a defensive tool for testing bots you own. The panel's "secrets"
(e.g. ORCHID-7741-ZEBRA) are deliberately fake — they exist only so the judge
has a concrete string to detect. The red-team pipeline never sees a target's
system prompt; it only sees the replies.
Analysis
View
Metric
- 1
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- AnthropicIn code
- CSSIn code
- HTMLIn code
- Next.jsIn code
- PythonIn code
- ReactIn code
- Tailwind CSSIn code
- TypeScriptIn code
- FastAPIClaimed
- RedisClaimed
8 of 10 appear in the indexed code. 2 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
- Claude CodeCommits
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
263 KB
Source files
26
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
jayanth922/Uc-berkeley-ai-hackathon
33 files · 336 KB · @ 39563df
Structure
Interface
4 files · 12%Screens, components and styles rendered to the user.
Application logic
18 files · 55%Domain rules, services and shared utilities.
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- Python52%
- TypeScript26%
- HTML17%
- Markdown5%
- CSS1%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
gauntlet-ui/package.json
npm · 12- clsx
- next
- react
- react-dom
- tailwind-merge
- +7 more
requirements.txt
pypi · 9- anthropic
- arize-phoenix-otel
- arize-phoenix[evals]
- langgraph
- pandas
- playwright
- python-dotenv
- redisvl
- sentence-transformers
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
This project’s features have not been analysed yet.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.