I scanned Express, Flask, Rails, Gin, and Laravel for security issues
What happens when you point a fast security scanner at the most popular web frameworks? 369 findings across 799 files in under a second.
What happens when you point a security scanner at the source code of the frameworks everyone uses?
I ran foxguard against the latest HEAD of Express, Flask, Gin, Laravel, Rails, Spring Petclinic, and a Next.js example. All seven repos scanned in 0.86 seconds total.
Results
| Repository | Files | Time | Findings |
|---|---|---|---|
| Express.js | 141 | 0.15s | 60 |
| Flask | 83 | 0.12s | 40 |
| Gin | 99 | 0.08s | 26 |
| Laravel | 29 | 0.01s | 1 |
| Rails (actionpack) | 356 | 0.30s | 173 |
| Spring Petclinic | 47 | 0.02s | 4 |
| Next.js (with-supabase) | 44 | 0.03s | 0 |
Not every finding is a vulnerability. Framework source code is supposed to handle dangerous primitives — that’s what frameworks do. But several patterns are worth looking at.
Express: hardcoded secrets in examples
Express ships example applications with hardcoded session secrets:
// examples/session/index.js
secret: 'keyboard cat'
// examples/cookie-sessions/index.js
app.use(cookieSession({ secret: 'manny is cool' }));
These aren’t vulnerabilities in Express itself. But developers copy-paste examples as starting points. “keyboard cat” has probably made it into more production apps than anyone wants to admit.
The downloads example also has a path traversal vector — req.params.file comes from a wildcard URL route and gets passed to res.download() with a root option, but the wildcard accepts .. segments.
Flask: exec() and SHA-1
Flask’s Config.from_pyfile() uses exec() to load Python config files:
# src/flask/config.py
exec(compile(config_file.read(), filename, "exec"), d.__dict__)
This is by design — Python config files are Python code. But if an attacker can write to the config path, this is code execution. It’s a known trade-off, not a bug.
More interesting: Flask uses SHA-1 in its session cookie signing fallback:
# src/flask/sessions.py
return hashlib.sha1(string)
SHA-1 is cryptographically broken. FIPS-compliant environments will reject this. It’s in the fallback path, not the default, but it’s still there.
Rails: Marshal.load and metaprogramming
Rails actionpack produced the most findings (173), which reflects Ruby’s metaprogramming-heavy style.
The genuinely security-relevant finding: Marshal.load for cookie deserialization. Marshal.load can instantiate arbitrary Ruby objects, enabling remote code execution if cookie data is tampered with. Rails mitigates this with signed/encrypted cookies, but the underlying primitive is dangerous by nature.
Rails also uses eval and instance_eval extensively for route generation and dynamic method creation. foxguard intentionally skips class_eval and module_eval — those are standard Ruby patterns, not security issues.
Gin: missing trusted proxies
Gin’s convenience package (ginS) creates a default engine without calling SetTrustedProxies():
// ginS/gins.go
return gin.Default()
Any application using ginS trusts all proxies by default, allowing IP spoofing via X-Forwarded-For. Most Gin users configure this themselves, but the default is permissive.
The clean ones
Next.js with-supabase: zero findings. Modern server components with Supabase auth helpers have no obvious anti-patterns.
Spring Petclinic: 4 findings, all in test code (RestTemplate with dynamic URLs). The production code is clean — Spring’s defaults are good.
Laravel: 1 finding — a require with a variable path in the maintenance mode entry point. Low risk since the base path is hardcoded.
What I learned
Example code is a real attack surface. Developers copy examples. “keyboard cat” in Express examples has probably shipped to production thousands of times.
Frameworks use dangerous primitives on purpose. Flask’s exec(), Rails’ Marshal.load, and eval are intentional design choices. The security question isn’t whether the primitive exists, but whether the surrounding assumptions hold.
Modern frameworks have better defaults. Next.js and Spring Petclinic came back clean. Newer frameworks learned from the mistakes of older ones.
Fast scanning changes behavior. All seven repos scanned in under a second. When security checks are this fast, you actually run them. That’s the whole point.
foxguard is an open-source security scanner written in Rust. 100+ built-in rules, 10 languages, sub-second scans. Try it: npx foxguard .