Reachable vs. Theoretical: Triaging AI-Generated Security Bugs

The Code Fundi Team
Aug 19, 2026

TL;DR – Static security scanners treat every pattern that looks dangerous as a vulnerability. In AI‑generated code most of those patterns are unreachable from any attacker‑controlled input. By confirming a reachable call‑path you can turn a flood of false alarms into a focused remediation backlog.


The alert‑fatigue problem: scanners flag pattern matches, not exploitability

Security‑scanner alerts are traditionally pattern‑based: a regular expression or abstract‑syntax‑tree rule that matches code that could be vulnerable. The rule does not consider whether an attacker can actually supply data that reaches that code. The result is a steady stream of findings that:

  1. Appear in generated helper functions that are never called from a public endpoint.
  2. Reside behind compile‑time constants or feature flags that are disabled in production.

For AppSec teams in regulated domains, triaging thousands of such alerts each sprint consumes effort that could be spent on genuine risk reduction, and it inflates audit reports with noise.


What “reachable” actually means

Reachable = the existence of a runtime call‑path that starts at an untrusted entry point (e.g., an HTTP request body, a message queue payload, or a deserialized object) and ends at the line of code flagged by the scanner.

A reachable path must satisfy three constraints:

ConstraintWhat it checksTypical source
Untrusted sourceIs the data origin controllable by an external actor?Public API, web form, third‑party webhook
Control‑flow propagationDoes the data flow through variables, parameters, or object fields without being sanitized or validated?Data‑flow analysis of assignments, method arguments
Sensitive sinkDoes the final operation perform a security‑critical action (SQL execution, OS command, deserialization, etc.)?execute, eval, system, ORM query builder, etc.

Visually, imagine a directed graph where nodes are functions or methods and edges are call relationships. A reachable vulnerability is a path from a source node (untrusted) to a sink node (flagged code) that does not traverse a node that performs a definitive sanitization or guard.

Diagram description: A simple box‑and‑arrow diagram showing User Input → Controller.handleRequest → Service.process → DAO.query. The arrow from User Input to Controller.handleRequest is labeled “untrusted source”; the arrow from DAO.query to the database is labeled “sink”. If any intermediate node contains a call to sanitizeSQL(), the path is broken for reachability.


Worked example: theoretical vs. exploitable SQL pattern

Below are two snippets that a static scanner would flag with the same rule (String.format + SQL concatenation). Only one is reachable.

Example A – Theoretical (unreachable)

python
1 def _build_report_query(user_id: int) -> str:
2 # This helper is used only by an internal batch job, never exposed to HTTP.
3 return f"SELECT * FROM reports WHERE owner_id = {user_id}"
4
5 def _run_batch_job():
6 for uid in _internal_user_ids():
7 sql = _build_report_query(uid)
8 db.execute(sql) # <-- scanner flags this line
9
10 # No public endpoint calls _run_batch_job()

Why it’s unreachable

  • user_id originates from _internal_user_ids(), a function that reads from a trusted configuration store.
  • The whole call chain (_run_batch_job → _build_report_query) is never invoked by any HTTP handler, webhook, or external message.
  • Therefore, no attacker‑controlled data can reach the concatenated SQL.

Example B – Exploitable (reachable)

go
1 func handleSearch(w http.ResponseWriter, r *http.Request) {
2 // Untrusted source: query parameter supplied by the client
3 term := r.URL.Query().Get("q")
4
5 // Directly concatenated into SQL – no sanitization
6 sql := fmt.Sprintf("SELECT * FROM products WHERE name LIKE '%%%s%%'", term)
7 rows, err := db.Query(sql) // <-- scanner flags this line
8 if err != nil {
9 http.Error(w, "internal error", http.StatusInternalServerError)
10 return
11 }
12 // render rows …
13 }

Why it’s reachable

  • term is derived from r.URL.Query(), an attacker‑controlled request parameter.
  • The value is concatenated into the SQL string without escaping or using a prepared statement.
  • The handler handleSearch is registered with the HTTP router, making the path from untrusted source to SQL sink live in production.

Reachability annotation

Code lineSource?Sanitized?Sink?Reachable
term := r.URL.Query().Get(&quot;q&quot;)✅ Untrusted–––
fmt.Sprintf(..., term)–❌ No sanitization––
db.Query(sql)––✅ SQL execution✅ Yes

The contrast illustrates how a scanner’s pattern match alone cannot discriminate between noise and genuine risk.


A practical triage framework (tool‑agnostic)

The following checklist can be applied to any scanner output, regardless of the vendor. It splits the work into three passes that map cleanly onto existing CI/CD or ticket‑tracking pipelines.

1. Source classification (5 min per alert)

ActionCriteria
Identify entry pointIs the variable derived from request body, header, cookie, message payload, or deserialization?
Flag constantsIf the value comes from a compile‑time constant, environment variable, or internal config, mark as non‑untrusted.
Record resultTag the alert as Potentially Reachable or Likely Theoretical.

Tip: Automate this step with a simple regex‑based rule that looks for known untrusted APIs (req.getParameter, request.json, queue.receive, etc.). The rule should add a custom label in your scanner’s JSON output.

2. Control‑flow sanity check (15 min per alert)

ActionHow to verify
Trace the variable through assignmentsUse a static data‑flow tool (e.g., CodeQL, Semgrep) to generate a variable‑origin graph.
Look for sanitizersSearch for calls to known safe APIs (preparedStatement, htmlEscape, jsonSchemaValidate). If any appear on the path, mark the alert Mitigated.
Verify conditional guardsIf a guard (if isAdmin { … }) precedes the sink, assess whether the guard can be bypassed from the identified source.

Tip: Record the shortest unguarded path. If none exists, the alert can be closed as theoretical.

3. Business‑logic relevance (10 min per alert)

QuestionDesired answer
Does the reachable path affect a regulated data store (PII, PHI, financial transaction)?Yes → Prioritize.
Is the endpoint publicly exposed or behind authentication?Public → higher risk.
Does the surrounding module have a security review backlog?Yes → schedule remediation in the next sprint.

Consolidated triage table

Alert IDReachable?Sanitizer present?Regulated impact?Priority
S‑00123✅❌✅ (PHI)P1
S‑00124❌––Dismiss
S‑00125✅✅ (prepared stmt)✅ (PCI)P2 (review)

Outcome: Export the table to your ticketing system. Alerts marked Dismiss are closed automatically; the rest become actionable tickets.


Why AI‑generated code magnifies the problem

AI coding agents (e.g., Claude Code, Copilot, or custom LLM‑based assistants) operate by sampling patterns from the prompt and their training data. When an agent writes a new function, it often copies a template that contains a risky construct (e.g., string‑concatenated SQL). Because the same template can be emitted across dozens of files in a single commit, the scanner sees N × identical matches.

FactorManual codingAI‑augmented coding
Pattern reuseSporadic, developer‑drivenSystematic, per‑commit duplication
Code review burdenLimited to few filesHundreds of identical alerts
False‑positive ratio~30 % (typical)>70 % in AI‑generated churn

The amplification is not theoretical: a single prompt that asks for “a function to insert a user record” often yields multiple functions - controller, service, repository - each containing the same concatenated query. Without reachability analysis, teams waste time triaging clones that are never reachable from any public API.


How CodeFundi fits into the workflow

CodeFundi does not replace your existing static scanner. Instead, it supplies the reachability context that the triage framework above requires. By constructing a precise call‑graph and overlaying it with the scanner’s findings, CodeFundi can:

  • Auto‑flag alerts whose sinks lie on dead code paths (e.g., internal utilities not reachable from any untrusted source).
  • Highlight the minimal reachable path for alerts that survive the data‑flow check, giving engineers a concrete remediation target.

In practice, you would feed the scanner’s JSON output into CodeFundi, run a blast‑radius analysis, and let the platform annotate each finding with a “Reachable = Yes/No” flag. This step reduces the manual control‑flow sanity check from minutes per alert to seconds per batch, letting the triage framework focus on business impact.

See also: the same dependency mapping, applied to security, in our Blog #3 and learn what this means for audit and compliance in [Blog #22].


FAQ

What does “reachability” mean in vulnerability triage?
Reachability is the existence of a runtime call‑path from an attacker‑controlled entry point to the vulnerable code. If no such path exists, the vulnerability is theoretical and does not pose an exploitable risk.

How do I reduce false positives from AI code security scanners?
Apply a three‑stage triage: (1) label the source of data, (2) verify the control‑flow for sanitizers or guards, and (3) weigh the business impact. Complement this with a call‑graph tool like CodeFundi to automatically discard unreachable findings.

Are AI‑generated code vulnerabilities more common than manual code?
AI assistants tend to repeat insecure patterns across many files, so the absolute number of scanner alerts rises sharply. However, the proportion of reachable vulnerabilities remains comparable to manual code; the excess is mostly noise from duplicated, unreachable snippets.


Take the next step

If you’re ready to cut through scanner noise and focus on truly exploitable bugs, try our security‑focused blast‑radius demo on a public repository. It shows how reachability mapping can be overlaid on existing findings in minutes.

Start the security demo →


Frequently Asked Questions