Security Scanning Limits: What No Tool Can Catch Today

Written by the Rafter Team

No security scanner—static, dynamic, AI-powered, or otherwise—can find every vulnerability in your application. Business logic flaws, insecure design decisions, race conditions under load, and novel attack patterns all live in the blind spots that automated analysis can't reach. Understanding these limits isn't an argument against scanning. It's the argument for defense in depth: scanning catches the 80% that's automatable so your team can focus human attention on the 20% that requires judgment.
Scanners are a floor, not a ceiling. Treating any tool as a complete security solution creates a false sense of safety that's more dangerous than having no scanner at all.
The Fundamental Limits of Automated Analysis
The limits of security scanning aren't engineering failures—they're mathematical certainties. Rice's theorem, a foundational result in computer science, proves that all non-trivial semantic properties of programs are undecidable. In plain terms: no algorithm can look at arbitrary code and determine with certainty whether it has a specific behavior, including whether it contains a vulnerability.
This means every scanner makes a tradeoff between two kinds of errors:
- Over-approximation (more false positives): Flag everything that might be a problem. Safe, but noisy. Developers start ignoring alerts.
- Under-approximation (more false negatives): Only flag what you're certain is a problem. Quiet, but misses real bugs.
No tool can eliminate both. Google's static analysis team documented this tension directly: their tools succeed at scale only because they aggressively minimize false positives—accepting that some real bugs slip through—because developers won't use noisy tools (Sadowski et al., "Lessons from Building Static Analysis Tools at Google," CACM, 2018).
The practical consequence: scanners are pattern matchers. They find known vulnerability patterns in code structure, data flow, or dependency manifests. Anything that doesn't match a known pattern—or that requires understanding what the code is supposed to do—falls outside their reach.
Business Logic Flaws
Business logic vulnerabilities are the single largest category of bugs that scanners fundamentally cannot detect. These flaws don't violate any technical rule. The code compiles, passes type checks, handles errors correctly, and follows every secure coding guideline. It just does the wrong thing.
Consider this endpoint for transferring credits between accounts:
# ✗ Vulnerable: negative transfer drains target account
@app.route("/transfer", methods=["POST"])
def transfer_credits():
sender_id = get_authenticated_user()
recipient_id = request.json["recipient_id"]
amount = request.json["amount"] # No validation on sign
db.execute(
"UPDATE accounts SET balance = balance - %s WHERE id = %s",
(amount, sender_id),
)
db.execute(
"UPDATE accounts SET balance = balance + %s WHERE id = %s",
(amount, recipient_id),
)
return jsonify({"status": "ok"})
A user who sends {"recipient_id": "victim", "amount": -500} doesn't transfer credits—they steal them. The sender's balance increases while the recipient's decreases. Every scanner on the market will pass this code clean. There's no injection, no broken authentication, no insecure dependency. The vulnerability exists entirely in the gap between what the code does and what the business intended.
# ✓ Secure: validates amount is positive
amount = request.json["amount"]
if not isinstance(amount, (int, float)) or amount <= 0:
return jsonify({"error": "Amount must be positive"}), 400
Other examples that scanners routinely miss:
- Self-referral codes: Users apply their own referral codes and collect bonuses meant for new customers
- Price manipulation: API accepts client-supplied prices instead of looking them up server-side
- Feature flag bypass: Admin endpoints accessible via direct API calls even when the UI hides them
OWASP defines business logic vulnerabilities as flaws that require understanding business rules to identify—something no automated tool possesses.
Design-Level Vulnerabilities
Some security problems aren't in the code at all—they're in the architecture. Line-level scanning can't catch decisions made at the whiteboard.
| Design Flaw | Why Scanners Miss It | Real Impact |
|---|---|---|
| Session tokens in localStorage | Valid JavaScript, no injection pattern | XSS in any dependency exfiltrates all sessions |
| Client-side authorization checks | Server code looks correct in isolation | Attackers bypass the UI and call APIs directly |
| No rate limiting on authentication | Auth logic itself is secure | Credential stuffing at scale goes undetected |
| Shared database credentials across services | Connection string is in env vars, not hardcoded | One compromised service owns the entire data layer |
| JWT with no expiration | Token is signed and validated correctly | Stolen tokens work forever |
These vulnerabilities compound. A JWT with no expiration stored in localStorage and protected by client-side auth checks creates a cascading failure: a single XSS vulnerability in any dependency gives an attacker a permanent session token with no server-side mechanism to revoke it.
Scanners analyze code in isolation. Architecture lives in the relationships between components—exactly the gaps that line-level tools can't see.
Runtime and Environment Issues
Scanners analyze source code. Your application runs in an environment with its own attack surface that no amount of code scanning addresses.
Infrastructure misconfiguration is the most common category:
- Cloud storage buckets with public read access
- Database instances exposed to the internet with default credentials
- Container images running as root with unnecessary capabilities
- IAM roles with
*:*permissions attached to compute instances - Environment variables containing secrets logged by default in crash reports
These issues live in Terraform files, Kubernetes manifests, cloud console settings, and CI/CD pipeline configurations—not in your application source code. A SAST tool scanning your Python or JavaScript won't flag an S3 bucket policy that grants s3:GetObject to *.
Race conditions add another dimension. Two requests hitting the same endpoint simultaneously can produce outcomes that no single-request scan would reveal: double-spending, inventory overselling, or TOCTOU (time-of-check-to-time-of-use) bugs in file operations. Consider a checkout flow that checks inventory, processes payment, then decrements stock—three separate database operations. Under concurrent load, two users can purchase the last item simultaneously because both pass the inventory check before either decrements. No static scanner models this concurrency. These are runtime behaviors that require dynamic testing under concurrent load to surface.
Secrets in deployment context are another blind spot. Your code might correctly read API keys from environment variables—a pattern every scanner approves—but those environment variables might be logged in plaintext by your container orchestrator, exposed in crash dumps, or accessible to every container in the pod. The vulnerability isn't in your code. It's in the deployment manifest that your SAST tool never sees.
Novel and Zero-Day Patterns
Scanners detect known patterns. By definition, they can't detect what hasn't been discovered yet.
The lifecycle of a new vulnerability class follows a predictable lag:
- Researcher discovers a novel attack technique
- CVE published, advisory goes public
- Scanner vendors write rules to detect the pattern
- Rules deployed to customer environments
- Developers scan and find the issue
Between steps 1 and 4, every application is exposed and no scanner will flag it. For some vulnerability classes, this window stretches from weeks to months. When Server-Side Request Forgery (SSRF) attacks gained prominence, it took scanner vendors years to develop reliable detection heuristics—and coverage is still incomplete for non-obvious SSRF patterns.
AI-powered scanners partially close this gap by reasoning about code semantically rather than matching fixed patterns. But they introduce their own blind spots: non-deterministic results across runs, hallucinated vulnerabilities, and inconsistent severity ratings. The AI layer adds coverage for novel patterns at the cost of reliability for known ones.
This lag also applies to AI-specific vulnerability classes. Prompt injection, tool-use abuse, and context window manipulation are still-evolving attack surfaces. Scanner coverage for these patterns is sparse and inconsistent, in part because the patterns themselves are still being defined by the security research community.
What Scanners Can't Catch: The Full Picture
| Vulnerability Class | Why Automated Tools Miss It | What Catches It |
|---|---|---|
| Business logic flaws | Requires understanding business rules, not code syntax | Threat modeling, manual code review, QA testing |
| Insecure design | Architecture decisions, not line-level patterns | Design review, threat modeling, security architecture review |
| Runtime/environment | Lives in infrastructure, not source code | Cloud security posture management, pen testing, config audits |
| Race conditions | Requires concurrent execution, not static analysis | Load testing, dynamic analysis under concurrency, chaos engineering |
| Novel attack patterns | Not in the rule database yet | Penetration testing, red teaming, bug bounties |
| Social engineering vectors | Human behavior, not code | Security awareness training, phishing simulations |
| Supply chain compromise | Malicious code in trusted dependencies | SCA with behavioral analysis, vendor audits, reproducible builds |
Covering the Blind Spots
The practices that catch what scanners miss share a common trait: they require human judgment applied systematically.
Threat modeling is the single highest-leverage practice. Before writing code, map your system's trust boundaries, data flows, and entry points. Identify what an attacker would target and how. OWASP's Threat Modeling Cheat Sheet provides a structured methodology. Threat modeling catches design-level vulnerabilities before they're built into the architecture.
Manual code review with security focus catches business logic flaws that automated tools miss. The reviewer needs domain knowledge—they need to know that transfer amounts should be positive, that referral codes shouldn't be self-applicable, that pricing should be server-authoritative. Pair security-focused review with automated scanning: let the scanner handle injection and dependency checks so human reviewers can focus on logic.
Penetration testing simulates real attacks against your running application. Pen testers find race conditions, environment misconfigurations, and chained vulnerabilities that no single tool or review catches in isolation.
Bug bounty programs provide continuous, adversarial testing from diverse perspectives. Researchers find novel attack patterns that internal teams and scanners miss because they approach the application with different mental models.
Monitoring and observability catch what everything else misses—after the fact, but before catastrophic damage. Anomaly detection on API usage patterns, authentication failures, and data access volumes provides the last line of defense.
Here's the minimum checklist:
- Threat model reviewed for every new feature or architecture change
- Security-focused code review on business logic and authorization
- Annual penetration test (quarterly for high-risk applications)
- Incident response plan tested with tabletop exercises
- Runtime monitoring with alerting on authentication anomalies
- Dependency review beyond CVE matching—evaluate maintainer trust and update cadence
How Rafter Helps—and Where It Doesn't
Rafter is an automated scanner. That means everything in this post applies to Rafter too. Rafter won't catch your business logic flaws, won't audit your cloud IAM policies, and won't find race conditions in your payment flow.
What Rafter does is maximize the automated floor. By combining static analysis with AI-powered contextual review, Rafter catches the classes of vulnerabilities that are automatable—injection flaws, hardcoded secrets, insecure dependencies, broken access control patterns, and the security anti-patterns that AI coding tools introduce at scale. With 45% of AI-generated code containing vulnerabilities (Veracode, 2025), automating the automatable isn't optional—it's the only way to keep pace with AI-assisted development velocity.
The goal is simple: let Rafter handle the 80% so your team's limited security review time goes to business logic, design review, and the judgment calls no tool can make for you. When Rafter finds a vulnerability, it generates fix prompts you can paste directly into your AI coding tool—Cursor, Lovable, or whatever you're building with. This closes the loop between detection and remediation without requiring your team to become security experts.
Rafter also generates natural language explanations of each finding, so when a developer sees a flagged issue, they understand why it's a problem and how to think about the fix—not just "line 47: SQL injection." That context builds security awareness over time, which helps with the 20% that tools can't catch.
Scan your repos at rafter.so and free up your team to focus on what actually requires human attention.
Conclusion
Perfect security scanning is mathematically impossible. Every scanner—SAST, DAST, SCA, AI-powered—operates within provable limits. Business logic flaws, insecure design, runtime issues, and novel attack patterns all live outside those limits.
This isn't a reason to skip scanning. It's a reason to treat scanning as one layer in a defense-in-depth strategy. Automate the automatable. Then invest human attention where it actually matters: threat modeling, security-focused code review, penetration testing, and monitoring.
The teams that get breached aren't the ones whose scanners have blind spots—every scanner does. They're the ones who assumed scanning alone was enough.