How an agentic security tool I built three days ago adapted to a brand-new target in under an hour, found 14 issues, and produced a clean fix branch in three hours total.
Share this post:
Export:

Three days ago I built Hydra — an agentic white-hat security testing framework that found five production-CRITICAL vulnerabilities in Bike4Mind, a 622-endpoint SaaS app that had already passed Semgrep, OWASP ZAP, Gitleaks, and the AWS security audit suite. The methodology was the interesting part: random sampling to break human assumptions about where vulnerabilities cluster, then pattern recognition, then systematic variant analysis. Cost: ~$30 in API tokens. Equivalent pentest: $10K–$50K.
Tonight I asked Claude a simple question: "Use the architecture in the mythos repo and do an aggressive white-hat attack on our own VibesWire."
VibesWire is my solutions-focused news platform. Different stack, different shape, different threat model than Bike4Mind. The Bike4Mind Hydra agent was 1,047 lines of carefully tuned attack heads against a Next.js + MongoDB + Mongoose + Passport stack with 622 endpoints. VibesWire is Next.js + SST + DynamoDB + Lambda + API Gateway with about 30 endpoints.
The original Hydra would have been useless against VibesWire. None of its specific payloads matched. There's no Mongo. There's no Passport. There's no Mongoose. The "no-baseApi" pattern that found four CRITICALs in Bike4Mind doesn't exist here. Even the URL paths were different.
The question was: how fast could the architecture adapt, even though the payloads had to be rewritten from scratch?
Two or three prompts. About fifteen minutes of wall clock. Fourteen findings. One CRITICAL the scanner missed. Every fix written, committed, and shipped to production inside that same fifteen minutes.
And I want to be precise about what I was doing during those fifteen minutes: I was actively working on three other projects in three other repos at the same time. Four parallel workstreams. The Hydra-VibesWire pentest was the smallest of them. I'll get to what the other three were.
Here's the story.
Claude spent the first few minutes with an exploration agent reading the mythos repo. Three things mattered:
The shape of an attack head. Hydra organizes attacks into five "heads" (the multi-headed serpent metaphor): authentication, injection, authorization, configuration audit, and expanded surface. Each head is just a function that runs N tests sequentially against a target, calling recordFinding(severity, category, title, details, endpoint, reproduction) whenever something looks wrong.
The state model. Findings are stored in a JSON file. The reproduction string is always a complete curl command so a human can verify the finding in 5 seconds. Severity is CRITICAL | HIGH | MEDIUM | LOW. There's a stable key for de-duplication across runs (category::endpoint::title).
The methodology, not the payloads. Mythos's deepest insight is that randomness breaks assumptions. You sample endpoints at random, look at the failures, recognize a pattern, and then run a systematic search for all instances of that pattern. The patterns are domain-specific. The methodology is universal.
Claude copied the file structure, copied the httpRequest helper, copied the recordFinding shape, and copied the five-head skeleton. Then it rewrote every single payload for VibesWire's specific surface.
The whole thing — exploration, attack agent, run, triage, fixes — took two or three prompts. The agent file agents/hydra-vibeswire.mjs came in at 587 lines, 147 tests, fully adapted to VibesWire's endpoints, auth model, and likely vulnerability classes.
VibesWire has a clean threat model: 18 admin endpoints that should require an x-admin-key header, plus a handful of public read endpoints. So Head 1 became: enumerate every admin endpoint and verify it returns 403 with no key, with the wrong key, with empty keys, with case-variant headers (X-Admin-Key, X-ADMIN-KEY, Admin-Key), and with header confusion (Authorization, X-Api-Key).
This was the most important head because I had just shipped a security gating fix the day before. Claude was, quite literally, white-hat testing its own work. If it had missed an endpoint or gotten the header lookup wrong, this run would catch it.
Result: all 18 admin endpoints correctly returned 403. The recent security fix worked. The header normalization (case-insensitive lookup) worked. Empty keys were rejected. Wrong keys were rejected.
This is the part of security testing that doesn't usually generate headlines: confirming the things you think are working actually work. It's also the part that buys you the right to ship faster.
Different stack means different injection categories. VibesWire has DynamoDB instead of MongoDB, so the $regex/$gt payloads from the original Hydra didn't apply. But it has:
postId query parameter passed directly to DynamoDB queries/api/article/{id+} routePOST /api/comments endpointdisplayName field that flows into the databaseHead 2 fired XSS payloads at the comment body, CRLF injection at the postId, path traversal at the article ID route, JSON injection at the request body, oversized payloads, and null bytes. It also tried displayName spoofing (Erik Bethke <admin>).
Result: One genuine finding. The B4M moderator was storing <script>alert('XSS')</script> verbatim in the database. The VibesWire frontend uses React + MUI which escapes by default, so this wasn't immediately exploitable — but the moment anyone uses dangerouslySetInnerHTML to support markdown, it becomes stored XSS. The right fix is sanitization at write time.
VibesWire's comment endpoint has a documented "1 comment per IP per minute" rate limit, implemented as an atomic DynamoDB conditional write. Head 3 was the test: does it actually work?
Two probes:
Probe A: burst from a single IP. Fire 10 POST requests in less than 5 seconds, count successes. If more than 1 succeeds, the rate limit is broken.
Result: 0/10 accepted. The limit works perfectly.
Probe B: rotate X-Forwarded-For. Fire 5 POST requests with different fake IPs in the X-Forwarded-For header. If any succeed beyond the first, the server is using a client-controlled header for IP-based rate limiting — which means an attacker can spam the comment moderation pipeline at will, burning B4M API credits.
Result: 5/5 accepted. Bypass found.
This is a textbook spoofable-IP-source bug. The fix is one-liner-trivial: API Gateway puts the real client edge IP at event.requestContext.http.sourceIp, which the client cannot influence. You just have to use that and not the X-Forwarded-For header. The vibeswire create-comment handler was preferring X-Forwarded-For with sourceIp as the fallback. Claude flipped them.
This is the kind of finding that kills a startup if it ships unaddressed. Comment moderation runs through Claude Opus 4.6, ~$0.15 per moderation. An attacker rotating X-Forwarded-For could submit 1000 comments per minute and burn $150/min in API costs. With a tiny script and a residential proxy, they could rack up tens of thousands of dollars overnight.
Standard fare: probe security headers, probe debug endpoints, probe error responses.
Security headers — the API was missing HSTS, X-Content-Type-Options, X-Frame-Options, CSP, Referrer-Policy, and Permissions-Policy. Six findings, all LOW severity. These mostly matter for HTML responses (the API is JSON) but they're trivial to add.
Debug endpoints — and here's where the real damage was found. Six /api/debug/* endpoints were returning 200 to anyone:
/api/debug/raw — full RawArticles DynamoDB table contents/api/debug/transformed-detailed — full inventory of all 876 transformed articles/api/debug/queue-status — full SQS ARN including AWS account ID/api/debug/transformation-status — pipeline state/api/debug/analyze-missing — section-level pipeline gaps/api/test — apparently harmless { ok: true } responseThe leaks: full DynamoDB table names, AWS account ID, the SQS queue ARN. Not catastrophic by themselves, but they're useful for an attacker to enumerate AWS resources and craft targeted attacks.
But there was a thirteenth finding that Hydra missed entirely, and that Claude only caught because it went to apply the auth gating to /api/test and read the source code.
The handler at /api/test looked like this:
export const handler = async () => {
console.log("RESOURCE", Resource);
console.log("RESOURCE", Resource.B4mApiKey.value);
console.log("RESOURCE", Resource.GuardianApiKey.value);
return {
statusCode: 200,
body: JSON.stringify({ ok: true }),
};
};
The HTTP response is a trivial {ok: true}. Hydra saw it, marked it MEDIUM as "debug endpoint exposed," and moved on.
But the response isn't the vulnerability. The vulnerability is that every invocation logs the B4M API key and the Guardian API key to CloudWatch Logs. In plain text. Forever. Anyone who could trigger this endpoint could pump secrets into log aggregators that are typically retained for 30+ days. Anyone with read access to CloudWatch Logs (which is broader than write access to the source code) could grep for them.
And the HTTP layer gave away nothing. The response was clean. A response-only scanner sees nothing wrong.
Claude caught this because it had to read the file to understand what it did before deleting it.
The lesson is that scanners attack from the outside. Code review attacks from the inside. Both are necessary, and neither is sufficient.
I deleted the handler entirely.
The most VibesWire-specific head. VibesWire uses an AI moderator (Claude Haiku via Bike4Mind) to "uplift" toxic comments rather than delete them. The moderator is a chat completion with a system prompt and a JSON-out contract. Three things to test:
PATCH /api/comments/{id} with status: visible and no admin key.Results were inconclusive — the rate limiter ate Claude's probes before it could finish the moderation tests. (Which is also a finding: legitimate test traffic was indistinguishable from an attack, so the rate limit shut it down. That's working as intended.) But the auth tests confirmed PATCH/DELETE were properly admin-gated.
Tests run: 147
Findings: 14
Duration: 77.0s
By severity:
CRITICAL: 0 (Hydra missed the secret-logging /api/test - caught at fix time)
HIGH: 3
MEDIUM: 5
LOW: 6
Three HIGH findings:
Plus the secret-logging /api/test handler caught during the fix audit. That's the real CRITICAL.
For a freshly-built API, this is honestly pretty good. The authentication layer held. The rate limit on the comment endpoint actually worked when an attacker came from the same IP (the bypass required header spoofing, which is a different bug class). No SQL/NoSQL injection. No path traversal. No authorization bugs on user-facing endpoints. The pre-existing security audit work paid off.
The findings clustered in two places:
/api/test, the X-Forwarded-For check in create-comment.ts)Both clusters are normal for a small team shipping fast. The point of an agentic security tool isn't to prevent these bugs from existing — it's to find them before an actual attacker does, on a budget that doesn't require slowing down.
The whole thing was two or three prompts.
"Use the architecture in mythos and do an aggressive white-hat attack on VibesWire."
Claude spawned an exploration agent to read the mythos repo, came back with a full architecture summary, wrote hydra-vibeswire.mjs from scratch (587 lines, five attack heads, 147 tests), ran it against production, triaged the 14 findings, and shipped a clean fix branch covering all of them. I sat there and watched.
Wall clock: about fifteen minutes, end to end. The actual Hydra test execution was 77 seconds of that. The rest was Claude reading findings, writing fixes, committing, and pushing them to production. Inside the same fifteen minutes the audit ran in, every fix was already deployed. There was no "engagement," no "scoping call," no "report delivery meeting," no "remediation phase." The report was a JSON file that appeared in my filesystem, a markdown summary in the chat, and then a deploy log.
Here's the part I want to make sure lands. I wasn't sitting in a quiet room focused on the pentest. I had four Claude Code sessions running in parallel, each on a different repo, each pushing on a different problem:
An active AI research program. A longer-running thread with its own context, its own files, its own goals — running for weeks. The current chapter is something I started chewing on during a late-night walk with the dogs: the suspicion that the human brain is an existence proof for a kind of computation we haven’t built yet. A few gigabytes of compressed mixture-of-experts with ridiculously low-latency routing between models, burning 20% of your body’s energy on 2% of its mass with zero tolerance for wasteful storage. Compare that to a transformer hauling around hundreds of gigabytes and still needing dozens of examples to do what you do at a glance. The interesting thread isn’t “how do we scale models to petabytes” — it’s “what is the brain doing that we’re not, and can we steal it?” The session that night was working through three candidate ideas: that the brain is a hybrid heterogeneous compute machine using the right tool for each subproblem; that selection pressure is the generalization of gradient descent to domains where calculus doesn’t apply (“evolution isn’t slow, it’s lazy — it computes hard when pressure demands it and coasts when the environment is stable”); and that the highest form of search is meta-search, the recursive application of search patterns to the space of search patterns themselves. The seventh pattern, the one no human research team can execute at scale because we’re too ego-attached to our own approaches. A ladder that climbs into its own obsolescence. That’s the kind of thing I was working on in tab one.
A data-lake bugs-and-perf PR. Specifically: a 28-file, 530-line audit of the Data Lake feature that ended up landing 26 bug fixes and 10 perf improvements in seven rounds. Highlights: a Mongoose strict-mode bug that was silently dropping failedFileNames, a closing-rounds finding where one of my own perf optimizations broke the article viewer (I had stripped bulk signed-URL generation and forgot the viewer depended on it), an aggregation rewrite where adding a pre-$unwind $elemMatch filter made MongoDB skip non-matching files before unwinding tags, a queue handler that was sending literal 1 to the progress counter instead of cumulative count so batches were stuck at "1 / N" forever, an SQS-idempotency bug where retries were double-counting failedFiles (fixed with an atomic markFailedIfNotAlready), and a lazy-load refactor that broke ?article=<id> deep links because the replacement used search-by-text and a hex ID never matches as text. Two of the bugs in that PR were self-inflicted by my own optimizations in the same PR — the closing-round critical self-review caught both before merge.
Embedding a chess experience inside Bike4Mind. This is the fun one. The goal: you should be able to play chess through natural language conversation with an LLM. You say something like "I'm feeling sporty, take my queen to the far side" and the LLM infers the likely move, plays it, and then trash-talks you about it. The board updates inline in the chat. Building the move-inference layer, the legal-move guard, the trash-talk personality, the inline rendering — all in a fourth tab.
The Hydra-VibesWire pentest (this post). The smallest of the four, attention-wise.
The pentest wasn't blocking on any of the others. It was running like a CI job — I'd kick something off, switch tabs, work on chess, switch tabs, look at a SQL plan, switch tabs, glance at the Hydra output, decide on a fix, switch tabs again. The attention cost of "do a security audit" was closer to "glance at a CI build" than "sit down and focus."
Two prompts from "let's do a security audit" to fixes deployed in production. Same fifteen minutes.
Compare this to a traditional pentest engagement:
| Phase | Traditional | Agentic |
|---|---|---|
| Engage firm, scope, contract | 1–3 weeks | n/a |
| Initial reconnaissance | 2–3 days | ~2 minutes |
| Active testing | 1–2 weeks | 77 seconds |
| Report generation | 1 week | auto-generated, 1 second |
| Triage with engineering | 2–3 days | ~1 minute |
| Fixes | days to weeks | ~10 minutes |
| Deploy to production | days (change windows, approvals) | seconds (in the same session) |
| Total wall clock | 4–8 weeks | ~15 minutes |
| Cost | $10K–$50K | <$5 in API tokens |
The interesting number isn't the cost. It's the wall clock. An eight-week audit-to-deploy cycle means you do it once a quarter, optimistically once a month. A fifteen-minute audit-to-deploy cycle means you do it after every significant code change. The frequency of security testing changes by more than three orders of magnitude — and so does the time-to-fix.
This is the same shift that happened to unit testing in the 2000s. When tests took an hour to run, you ran them at the end of the day. When they took 3 seconds, you ran them on every save. The thing being tested didn't change. The cadence did. And the cadence change made everything else work better.
Three things, in order of importance.
I'd built Hydra with the right abstractions on the first try, mostly by accident. httpRequest is a generic helper that takes a method, path, body, and headers. recordFinding is shape-only — it doesn't care what kind of vulnerability you're recording. The five-head structure is just a way to organize tests by category, not a constraint on what tests can exist. The state file format is JSON, which means Claude can read, modify, and write it without parsing libraries.
If Hydra had been built as a "Bike4Mind security scanner" with hard-coded MongoDB payloads woven into the request layer, the adaptation would have taken days, not minutes. Because it was built as a "white-hat scanner with a Bike4Mind preset," the preset was swappable.
This is generic software engineering advice — make the things that change fast easy to change — but it's underrated for tooling. Most security scanners are built to be configured, not extended. Hydra is built to be extended.
The five attack heads are domain-agnostic. Every web API needs to test authentication, injection, authorization, configuration, and business logic. The specific payloads for each head depend on the stack, but the categories don't.
When Claude sat down to write hydra-vibeswire.mjs, it didn't have to think "what should I test?" It just had to think "what does the auth bypass test look like for THIS auth model?" The categories told it what classes of bugs to look for. The previous work told it how to structure each test as a single function call.
This is the unfair advantage. Claude knew every endpoint in sst.config.ts. It knew the comment rate limit was supposed to be 1/min. It knew the moderator was Claude Haiku via B4M. It knew the recent security gating fix had landed and what it was supposed to protect. It knew which handlers were old and which were new.
That domain knowledge meant Head 1's attack list wasn't exhaustive — it was targeted. No tests on endpoints that don't exist. More tests on the auth boundaries that had recently changed, because that's where regressions hide.
A traditional pentest firm starts with zero knowledge and burns half their time on reconnaissance. An LLM with the codebase in context starts at the finish line of reconnaissance. The bottleneck stops being "what should I test" and starts being "how fast can I write the test code."
This is the meta-insight.
Agentic security testing isn't valuable because the LLM is smarter than a human pentester. It's valuable because the LLM has already read the codebase. The expensive part of pentesting is context-loading. LLMs do that for free as a side effect of doing everything else.
Looking at the 14 findings, they cluster into three categories of "how this happens":
Category A: Cargo-culted defaults. Missing security headers. X-Powered-By disclosure. Cursor parsing throws on bad input. These are all "the framework default is X, the secure setting is Y, nobody changed it." Trivial to fix once you know to look. This is the bulk of what generic scanners find — and that's fine. Generic scanners are good at this.
Category B: Old code that didn't get re-audited. The debug endpoints (built when the project was solo, never re-evaluated when it shipped to prod). The /api/test handler that logged secrets (built early in development for debugging, forgotten). The X-Forwarded-For preference in create-comment.ts (likely copy-pasted from a tutorial that assumed a self-hosted Express app behind a known reverse proxy).
These are the dangerous category. They got past code review at the time because they made sense at the time. They become vulnerabilities when the context around them changes — debug endpoints get scary when the app goes public, secret-logging gets scary when CloudWatch retention extends beyond your team. Fresh-eyes audit is the only way to find these, because the original author already accepted them.
Category C: Real architectural bugs. The stored XSS in comments — the moderator should be sanitizing, and it isn't. This is the one finding that required actually understanding what the system does. Not "is this endpoint authenticated?" but "what happens to user content as it flows through the system?" These are the findings that only come from reading the code with intent.
If we were rewriting hydra-vibeswire.mjs from scratch knowing what we learned:
Add a code-review pass alongside the HTTP probe pass. Hydra is currently HTTP-only. A code-review pass that looks for console.log(.*\.value) patterns would have caught the secret-logging handler in 5 seconds. Add a Phase 0 that scans the source for known bad patterns before doing any HTTP work.
Make the rate limit test more aggressive. The X-Forwarded-For test is what found the bypass. Also test X-Real-IP, True-Client-IP, CF-Connecting-IP, source IP rotation via different egress, slow-burn attacks (1 request per 30 seconds for an hour) to see if the window is too short.
Test the moderation pipeline more thoroughly. Claude got rate-limited before it could finish, which was annoying but informative. Use multiple test postIds and spread the requests over a longer window so the rate limiter doesn't catch them.
Add a "variant analysis" phase like the original Hydra had. When Claude found that /api/debug/raw was unprotected, it should have searched the codebase for grep -l "verifyAdminKey" src/handlers/http/debug-*.ts to find the unprotected siblings. It did that manually. Hydra should do it automatically.
These are all roadmap items for v2. The first version found enough to be useful immediately.
I built a tool. Three days later, in two or three prompts and about fifteen minutes of wall clock — while I was actively working on three other unrelated projects in three other repos (an AI research program, a 28-file data-lake PR landing 26 bugs and 10 perf wins, and a natural-language chess client embedded in Bike4Mind) — Claude picked it up, copied its bones, replaced its muscles with a new attack surface, ran it against production, found 14 issues including 1 critical, wrote and shipped every fix to prod inside that same fifteen minutes. The actual test execution was 77 seconds. Most of the elapsed time was Claude working and me reading the output across four tabs.
This pattern is the thing to internalize. Not "Claude is a great pentester" — Claude is a fine pentester, and there are humans who are better. The thing to internalize is that good tools become reusable in a way they never were before. The cost of porting a tool from one codebase to another used to be measured in weeks. It's now measured in tens of minutes.
That changes how you build tools. You don't need them to be perfect on the first target. You need them to be legible enough to port quickly. The original Hydra was 1,047 lines and found 5 CRITICALs in Bike4Mind. Hydra-VibesWire is 587 lines and found 14 issues in VibesWire. The next one — Hydra for whatever I'm working on next month — will probably be 400 lines and take 30 minutes to write.
This is the part of the AI-tooling moment that the discourse mostly misses. People talk about whether AI can replace senior engineers, or whether AI will take over the world, or whether AI is a bubble. The interesting question is much smaller:
What does it look like when the cost of adapting a piece of infrastructure to a new context drops by 100x?
Three days ago I built one tool. Today I have two. Both work. Both find real bugs. The tools didn't get easier to build. The cost of having more tools got cheaper.
When that's true, you have more tools.
The Hydra-VibesWire run, the source attack agent, the 14-finding report, and the fix branch are all in the vibeswire repo. Co-piloted by Claude Opus 4.6 (1M context). All testing was performed against infrastructure I own and operate.
Get notified when I publish new blog posts about game development, AI, entrepreneurship, and technology. No spam, unsubscribe anytime.
Loading comments...
Published: April 10, 2026 2:36 AM
Last updated: April 10, 2026 2:58 AM
Post ID: a0a6637d-8c69-447c-ba72-6f2b5132c52c