There are now more than ten security scanners specifically targeting vibe-coded apps. That happened in about three months.
We got curious. So we cloned three of them and ran AINAScan on the source code.
Here's what we found — including the two things AINAScan got wrong.
The setup
We scanned:
- vibe-audit (ApacheWang) — CLI scanner for Cursor/Bolt/Lovable output
- vibescan (Armur-Ai) — SAST+DAST pipeline, 15 languages, MCP support
- VibeSecurity (abenstirling) — hackathon winner, Go scanner wrapped in FastAPI
Three real tools, all open source, all actively maintained. The intent wasn't to rank them — it was to see whether the same patterns that show up in vibe-coded apps also show up in tools built to catch them.
They do.
What AINAScan caught ✅
FAKE_ASYNC in the scanner itself
VibeSecurity/scanner.py, line 50:
async def scan(self) -> dict[str, Any]:
"""Run security scan using Go executable"""
result = subprocess.run( # no await
[self.executable_path, self.url],
capture_output=True,
text=True,
timeout=60, # blocks for up to 60 seconds
)
return json.loads(result.stdout)
async def with subprocess.run() inside and no await. This blocks the entire FastAPI event loop for up to 60 seconds per scan request. Every other request queues behind it. The hackathon demo probably had one user at a time — this wouldn't surface until real traffic hit it.
This is exactly the pattern we see most often in AI-generated backends. The model learned to write async def but didn't learn why.
SENSITIVE_LOG_LEAK in the auth module
VibeSecurity/auth.py, line 26:
if "uid" not in decoded_token:
print(f"UID missing from token: {json.dumps(decoded_token)}")
decoded_token is the full Firebase JWT payload — uid, email, provider, any custom claims. All of it goes to stdout on every failed auth attempt. In production, that's your application logs, potentially forwarded to a log aggregation service, potentially accessible to multiple people.
It's a one-line fix. It also ships in the auth module of a security tool.
What AINAScan got wrong ⚠️❌
We're publishing this part because we think it's more useful than pretending the tool is perfect.
False positive: STUB_SKELETON on a Click group function
vibe-audit/cli.py:
@click.group()
@click.version_option(__version__, prog_name="vibe-audit")
def cli():
"""Security scanner for AI-generated code."""
pass
AINAScan flagged this as STUB_SKELETON — a vibe-coding pattern where a function has a meaningful name but no implementation.
It isn't. pass is the standard body for Click CLI group functions. The framework handles everything; the function itself needs nothing. AINAScan didn't know the difference.
We shipped a fix: @click.group() and @click.command() decorated functions are now exempt from STUB_SKELETON.
False negative: unauthenticated debug endpoints
VibeSecurity/routes/debug.py:
@router.get("/api/debug/all-scans")
async def all_scans_debug(request: Request):
# returns all users' scan history + emails
# no authentication dependency
...
There are four endpoints under /api/debug/ — none of them require authentication. /api/debug/all-scans returns every user's scan history and email address from Firestore. Anyone who finds the URL can call it.
AINAScan missed this entirely. It caught the FAKE_ASYNC and the log leak, but it doesn't yet analyze whether sensitive route paths have auth dependencies wired up. That's an interoperational check — cross-referencing the route path against the function signature — and it wasn't implemented.
We shipped a fix: New UNAUTH_SENSITIVE_ROUTE rule now flags routes with paths containing debug, admin, repair, or internal that have no Depends(auth_func) parameter. Running the updated scanner on the same file returns four BLOCK findings, one per endpoint.
Results
| Tool | Real bugs caught | FP | FN |
|---|---|---|---|
| vibe-audit | 0 BLOCK (some WARN) |
STUB_SKELETON on CLI |
— |
| vibescan | test data file only (intentional) | — | — |
| VibeSecurity |
FAKE_ASYNC, SENSITIVE_LOG_LEAK
|
— | unauthenticated debug routes |
vibescan's vulnerable.py had nine BLOCK findings — but that file lives in testdata/ and is intentionally full of vulnerabilities. Their actual source code was clean.
The pattern
All three tools were built quickly, by developers who clearly know what they're doing. And all three have at least one instance of the exact patterns they're designed to catch.
That's not a criticism. It's the point. Vibe-coding patterns aren't a sign of carelessness — they're what fast, AI-assisted development produces by default. The async declaration without semantics, the log statement that made sense in development, the debug route that never got locked down. These appear in mature codebases written by experienced engineers.
The right response isn't to move slower. It's to add a scan step before you ship.
Try it
# scan before you deploy
curl -X POST https://pleasing-transformation-production-90c2.up.railway.app/v1/scan \
-H "X-API-Key: vg_free_test" \
-F "file=@routes/debug.py"
Free key (vg_free_test) covers 50 files/day. Source: github.com/Moonsehwan/aina-scan
What's the most surprising bug you've found in a tool you were already using? Drop it in the comments.
Top comments (0)