Securing Source Code with LLMs: The Complete Find-and-Fix Guide

What is the primary bottleneck when using Large Language Models to secure code, and how do we solve it?

The capabilities of large language models are advancing rapidly, but unevenly. Through extensive work with security teams to find and fix vulnerabilities in their own codebases and open-source software, a clear pattern has emerged: discovery is now straightforward to parallelize, and the bottleneck has shifted to verification, triage, and patching. To illustrate this discrepancy, during a dedicated scan of open-source software, 1,596 vulnerabilities were disclosed by May 22, 2026. To our knowledge, only 97 of these had been patched.

This massive gap between discovery and resolution highlights that finding vulnerabilities is only the first step. This guide walks through how to work with advanced models to build a threat model, discover vulnerabilities in a codebase, and then rigorously verify, triage, and patch them. By focusing on the entire lifecycle rather than just the initial scan, security teams can achieve measurable, lasting improvements in their code security.

Coding and Security Concept
图片来源:Unsplash

The Find-and-Fix Loop: A New Paradigm for Code Security

How do you build a sustainable code security scanning process that avoids the trap of discovering too many vulnerabilities to actually fix?

Teams that successfully find and fix the most vulnerabilities have converged on a variation of existing best practices. This workflow has been distilled into a sequential, six-step loop:

  1. Threat Model: Decide what counts as a vulnerability before you start scanning.
  2. Sandbox: Build a sandbox environment to isolate agents and prove exploitability.
  3. Discovery: Have models look for vulnerabilities in your source code.
  4. Verification: Independently confirm which findings are actually exploitable.
  5. Triage: Deduplicate findings, assign severity, and prioritize what needs fixing.
  6. Patching: Apply the fix, confirm the vulnerability is nullified, and search for variants.

The first two steps—building a threat model and a sandbox—serve as the setup phase. These are typically executed once per codebase and revisited only when the underlying system undergoes significant changes. The subsequent four steps form the repeating loop that runs against the source code: discover, verify, triage, and patch.

Author’s Reflection: This structured six-step workflow represents a fundamental shift in security operations. The work is transitioning from “manually hunting for bugs” to “managing and optimizing an automated pipeline.” The first run on a codebase typically yields the highest volume of findings. Subsequent runs tend to uncover fewer—though often more complex—vulnerabilities, as the simpler ones are addressed in prior iterations. However, teams should never expect the nth run to produce zero new findings. Models are inherently stochastic, and large codebases harbor a long tail of vulnerabilities that continue to trickle in even when the code itself remains unchanged.

During the first iteration with a new codebase, it is highly recommended to run the loop multiple times. The decision of when to stop should be based on the number of net-new findings and your specific risk tolerance for that system. After that initial deep dive, continue to scan either periodically or whenever the code undergoes meaningful changes.

Step 1: Threat Modeling — Defining What Actually Counts as a Vulnerability

How do you build an accurate threat model that prevents the model from generating massive amounts of false positives?

The most common cause of false positives is not a failure of the model’s logic, but a lack of understanding regarding your trust boundaries. A model might flag code as vulnerable because it assumes a client could send corrupted values or an attacker could control a configuration file, even though these inputs are entirely trusted in your specific environment. Conversely, the model might assume an internet-facing service is internal-only, leading to an under-reporting of true vulnerabilities. In both scenarios, the model is wrong about the threat model, not the code.

Real-World Scenario: One team noticed a distinct pattern across their findings: the model performed best on systems with well-documented threat models, system design documents, requirements, and constraints. When the threat model was explicitly defined, the model’s findings “were exploitable 90 percent of the time.”

The Two-Step Threat Modeling Process

You can collaborate with an AI model to build a threat model effectively using a two-step approach:

First, bootstrap from existing code, documentation, and vulnerability history. Feed the model exactly what you would hand a new security engineer on their first day: architecture documents, wikis, entry points, git history, and records of past vulnerabilities. This helps overcome the nearly impossible challenge of inferring implicit knowledge, historical trade-offs, and design decisions from code alone. Then, ask the model to create a comprehensive threat model that includes system context, assets, entry points, and trust boundaries. Finally, have the model cluster past bugs and list the relevant vulnerability classes. It is crucial that the threat model explicitly documents what vulnerabilities you do and don’t care about, and the reasoning behind those decisions.

Real-World Scenario: One team reviewed hundreds of past CVEs and security-fix commits, distilling them into “bug-shape” hints. They then asked the model two questions: Was the fix complete? Was it applied everywhere else? They found three exploitable issues in a single hour. As they noted: “‘What have people exploited in the past’ is sometimes a much easier cheat-code towards success than ‘find me vulnerabilities in this codebase.'”

Second, have the model interview someone who knows the system intimately. Consider utilizing Shostack’s four questions: What are we building? What can go wrong? What are we doing about it? Did we do a good job? Running the bootstrap step first ensures the interviewee is not starting from scratch. Instead of spending hours researching and building a threat model from zero, they can refine a draft. While this interview step is optional, it adds vital context that the model simply cannot extract from code or documentation, dramatically improving the accuracy of the threat model.

Critical Practices for Threat Modeling

Several specific practices can make a substantial difference in the quality of your threat model:

  • Consider your dependencies’ existing security policies. Many open-source projects publish their own security policies. For example, vLLM’s security documentation, SQLite’s “Defense Against the Dark Arts” guide, and ImageMagick’s security policy. Your threat model should incorporate these directly rather than attempting to rebuild a policy from scratch.
  • Explicitly name what is trusted. If your system trusts configuration files or authenticated clients, document that trust in the threat model. These assumptions are what separate non-exploitable code quirks from actual exploits.
  • Include a THREAT_MODEL.md file directly with the code. Keep it in the repository and update it as the code changes. The discovery agent can then read this file before searching, allowing it to skip known non-issues.

The threat model is utilized in two distinct phases. During discovery, it acts as the scope—partitioning the code, prioritizing targets, and skipping out-of-scope components. This is invaluable for large codebases that cannot be scanned entirely in one pass. During triage, it serves as a filter—after scanning broadly, the threat model is used to calibrate severity accurately to your specific system and environment.

Real-World Scenario: One team scanning a large project experienced a 40% false positive rate and dug into the root cause. The findings were reproducible, and the proofs of concept (PoCs) demonstrated exploitability. However, the development team who owned the code dismissed them as false positives because the bugs did not fit the project’s actual threat model. Another team’s Chief Information Security Officer summarized this perfectly: “[The model has] good context of the code, but not good context of us.”

Step 2: Sandbox — Running Agents Safely and Proving Exploitability

How do you build a sandbox environment that protects production systems while effectively proving that a discovered vulnerability is actually exploitable?

A sandbox serves two vital, distinct purposes in this workflow.

The first purpose is to protect your systems. To enable models to run safely and autonomously, a robust isolation layer is mandatory. Without it, an agent may overshoot its target and execute unexpected actions.

Real-World Scenario: One team incorrectly told the model it had no network access—when it actually did. The model discovered it could fetch data from GitHub anyway. Another team observed an agent answering a GitHub issue mid-scan. Neither action was malicious, but both clearly demonstrated the need to enforce constraints strictly through code and configuration, rather than relying on prompt instructions.

The isolation must match your threat model. Standard containers are fine for the discovery agent that is only reading code. However, the target application and its PoCs must be run in a microVM (like Firecracker) or a full VM with egress strictly locked down, ensuring nothing can reach your production systems. Credentials—such as ~/.aws, ~/.ssh, and .env files—must never be available to the agent.

Network access should only be granted during the setup phase. Pull dependencies, build the code, install tools, deploy the target, and run existing tests to confirm everything operates correctly. Then, take a snapshot of the environment and completely remove its network access. During scanning, allow traffic only to the model API, routed through a local proxy. Load this clean snapshot at the start of every run so that each scan begins from an identical, pristine state.

The second purpose of the sandbox is to prove exploitability. During static scanning, a model reads code and hypothesizes what might break, but it cannot test if a specific code path is reachable or if a compensating control exists elsewhere. As a result, the model might flag unexploitable code-correctness bugs that you do not actually care about. When teams built a sandbox where the agent could compile code, run tests, and detonate a proof of concept, the rate of non-exploitable findings dropped significantly.

Real-World Scenario: An offensive-security team built a harness that provided the agent with a test bed, governed by a simple verification rule: it is only a true positive if the agent can build a proof of concept and successfully run it on the test bed. Their assessment after six weeks of operation was that “the biggest efficacy lever has been giving the model test beds, live systems, and running the PoCs.”

Building a Faithful Sandbox Environment

When constructing sandboxes, pin as much as humanly possible so every run uses the exact same code in the exact same environment. This includes image tags, commit SHAs, dependencies, and build commands. Cache a local copy so the build requires zero network access, and aim for the container to be durable so multiple testing loops can simply load it instantly.

Real-World Scenario: One team’s scan flagged a vulnerability that turned out to be a complete artifact of the agent downloading an older version of a library instead of the version actually deployed in production. This was caught by an engineer who read the transcript and spotted that a different dependency was being downloaded. They now build Docker containers with dependencies pinned to match production exactly, ensuring the discovery agent and the verification agent operate on the identical artifacts an attacker would target.

It is critical to build sandboxes that are highly faithful to production. Excluding dependencies (like a message queue or datastore) can lead to under-reporting bugs that exist in the live environment. Conversely, ignoring production defenses (like a Web Application Firewall or an authentication gateway) leads to the model reporting unexploitable findings that your production environment already mitigates.

If building a fully representative sandbox is impractical due to cloud dependencies, data stores, or other complexities, you can start with the discovery step instead. You do not necessarily need to run PoCs in a sandbox from day one. Frontier models are highly capable of finding vulnerabilities through pure source code analysis. The trade-off occurs in the verification phase: without a running target, you cannot prove findings with a PoC, so you must budget significantly more time for manual or static verification. You can invest in the sandbox later, once the sheer volume of findings justifies the engineering effort.

Author’s Reflection: The construction of a sandbox environment is frequently overlooked, yet it serves as the bedrock of the entire security loop. Without a proper sandbox, you either risk your production systems or fail to effectively validate the vulnerabilities you find. This highlights a crucial reality: the success of automated security tools relies just as heavily on infrastructure investment as it does on the underlying algorithms.

Server Isolation Concept
图片来源:Unsplash

Step 3: Discovery — Providing Rich Context, Shorter Prompts, and Useful Tools

How do you optimize the discovery phase so the model efficiently and accurately identifies vulnerabilities without hitting diminishing returns?

Give the discovery agent access to context that it can load on demand, such as the threat model, architecture documents, and the results of past scans. When the agent deeply understands your trust boundaries and how the system is actually deployed, it becomes much better at identifying vulnerabilities specific to your environment.

Counterintuitively, frontier models benefit from increasingly simple prompts during the discovery phase. Highly prescriptive prompts actually make discovery worse—long checklists tend to stifle the model’s creativity and result in fewer novel bug discoveries. Here are several prompting strategies that have proven effective:

  • Provide the goal and context. Indicate the “why” and the “what”—why you are scanning, what a meaningful finding looks like, and what system is being scanned. Leave the “how to scan for vulnerabilities” entirely to the model. Frontier models are increasingly proficient at security tasks, and being overly prescriptive narrows the scope of what they attempt.
  • Try asking for a specific vulnerability class. If you want to focus on a specific type of vulnerability—perhaps guided by prior CVEs or the specific programming language of the codebase—state that clearly. Describe the vulnerability class, what it does, and where it tends to live, so the model can recognize it within your code.
  • Define the output. Request a structured report with predefined fields, ordered so the model’s reasoning builds logically upon each field. Example fields include rationale, finding, impact, and severity. Crucially, include an “escape hatch” so the model can exit early for weak findings rather than forcing a report.

Provide the model with standard tools to search and read the codebase, such as grep and glob. Also, allow the model to use security-specific tools your team already uses, such as SAST scanners or fuzzers. Ask the model what tools are needed for a specific task and make them available. Finally, let the model build its own tools as needed; recent frontier models are remarkably good at writing the exact tools they need to solve a problem.

Real-World Scenario: In addition to source code, one penetration testing team gave the discovery agent tools to send HTTP requests, check the responses, and query traffic logs. As a result, the agent did not need to guess whether a code path could be reached. It could test each candidate against the running application dynamically as it went, improving their true-positive rate to nearly 100 percent.

Optimizing the Discovery Strategy

Have the model perform an initial pass over the system to partition the search space—perhaps by attack surface, endpoint, or component. Then, feed those distinct partitions to parallel discovery agents so they do not converge on the exact same shallow bugs. Finally, run a system-level pass that takes the partition-level findings as context to search for deeper, cross-component vulnerabilities.

Real-World Scenario: Teams that attempted to brute-force discovery quickly hit severe diminishing returns. As one team reported: “We initially tried to just horizontally scale and send more agents, but saw limiting returns.” Another team increased the number of focus areas and parallel agents and got “tons of issues”—most of which were exact duplicates of each other.

If you have a sandbox to run the target, ask the discovery agent to build a proof of concept for the finding, such as a script, a crashing input, or a failing test. Building the PoC helps the agent iterate and pin down the exact mechanics of the finding, and the resulting artifact gives the verification agent concrete evidence to evaluate. Findings that the agent cannot reproduce should still be reported, but they must be flagged as unproven to maintain high recall.

Step 4: Verification — Filtering Out Non-Exploitable Findings

How do you effectively separate true, exploitable vulnerabilities from false positives without the model censoring its own discoveries?

Discovery optimizes for recall; verification optimizes for precision. In other words, discovery should find as many vulnerabilities as possible—even seemingly unlikely ones—while verification should ruthlessly exclude findings that are not actually exploitable. When a single agent attempts to do both in the same step, it tends to self-censor, excluding exploitable true positives that a separate verification step would have confirmed. This is a lesson learned the hard way: asking discovery agents to also verify their own findings led directly to them filtering out real vulnerabilities.

The verifier agent must be completely independent from the discovery agent. Run the verifier in a fresh container without a shared filesystem or conversation history. If the verifier is exposed to the discovery agent’s internal reasoning, it may simply agree with the initial assessment rather than rigorously testing the claim. Therefore, give the verifier only two things: the proof of concept or written finding, and the codebase itself. This forces the verifier to independently search for mitigations the finder might have missed, such as upstream validation, authentication gates, type constraints, or unreachable code paths.

If a single verification pass still allows too many unexploitable findings through, try running multiple independent verifiers. They can consider different angles or run using different models. Then, take a majority vote. It is also highly effective to have a separate “judge” agent to make the final decision between the discovery and verification agents’ conflicting results.

Prompt the verification agent specifically to disprove the discovery agent’s findings. Instruct the verifier to assume each finding is a false positive and actively search for reasons why the finding is wrong. Include clear, strict criteria that the verifier agent can use to determine if a finding is a true positive. This adversarial approach matters most when the discovery agent’s output does not include a working PoC. The goal is to exclude as many non-exploitable findings as possible to drastically reduce the effort required in manual reviews.

Real-World Scenario: Across multiple teams, adding an adversarial verifier roughly halved the rate of non-exploitable findings from the discovery phase. Requiring that verifier to also build a proof of concept confirming the exploit brought the false positive rate down to near zero. Together, these two steps helped to reduce the downstream triage and patching load significantly.

If you are able to sufficiently reproduce your production environment in a sandbox, prompt the verifier agent to build and execute a fully reproducible proof of concept. If the PoC works, you can conclusively determine the finding is exploitable. Note carefully that the inverse is not true: a failure to produce a working PoC is not definitive proof of a false positive.

Real-World Scenario: One team scanning open-source packages built a verification step that perfectly closed the loop: scan the package, generate a proof of concept, and then deploy a mock application that uses the package and actively triggers the PoC. Their conclusion was straightforward: “Validation is the biggest holdup and the PoC is the validation.”

Author’s Reflection: The separation of discovery and verification is not merely a technical workflow choice; it reflects the distinct cognitive modes required for “creation” and “criticism.” When the same agent attempts to be simultaneously creative in finding bugs and critical in verifying them, it often falls into patterns of self-doubt or unwarranted confidence. This principle of separating generation from evaluation can be applied to almost any complex AI-assisted workflow.

Step 5: Triage — Deduplicating by Root Cause and Ranking by Impact

How do you efficiently process hundreds of findings without overwhelming development teams with alert fatigue?

While verification confirms that a finding is technically exploitable, triage assesses the actual patching priority. In the past, when discovery required significant manual effort, the engineer who found the bug also triaged it. Now, with models capable of finding a hundred candidate vulnerabilities before lunch, triage has become the primary operational bottleneck.

Proper triage is essential to prevent alert fatigue. If you submit too many bugs that are duplicated or have an artificially inflated severity, product engineers will simply stop reading them—even the ones that require immediate patching. Open-source maintainers are especially vulnerable to being overwhelmed by untriaged findings, as they receive reports from many different users and automated systems relying on their software.

Real-World Scenario: Multiple teams shared the exact same lesson: if they send product engineers a massive pile of findings where the majority are non-exploitable or poorly contextualized, the engineers lose trust in the reports and give up reviewing them. Successful teams prioritize critical and high findings to avoid overwhelming downstream engineers. Other teams found a significant win by pointing the model at their existing backlog—open findings from prior scanners, prior models, and bug-bounty intake—and cleared hundreds of stale items in a matter of days.

Strategies for Effective Deduplication

To deduplicate findings accurately, you must look past surface symptoms to the root cause. Scanners frequently flag one bug at multiple call sites or report multiple symptoms stemming from a single root cause. A highly practical approach involves two passes:

First, execute a cheap, deterministic pass to group obvious duplicates: same file, same category, and vulnerability line numbers within ten lines of each other. Then, have a model apply qualitative rules to everything that remains:

  • Treat as duplicate: The same root cause worded differently; the same vulnerability reported at multiple call sites; a missing global protection (like an authentication check) reported repeatedly per endpoint; or a cause and its direct consequence flagged within the same execution path.
  • Treat as distinct: Different vulnerability classes in the same file; different variables reaching different sinks; two independent bugs inside one helper function; or the same missing check on two separate endpoints where each requires its own individual fix.

If your harness generates PoCs and patches for each finding, another highly effective deduplication method is to check if the patch for one finding also disarms the PoCs of other findings.

The Severity Assessment Rubric

After deduplication, rate the severity of each finding based on six critical factors:

  • Reachability: Can an attacker reach this code from a real, external entry point, or is it only reachable from internal code and endpoints?
  • Attacker Control: Does untrusted input reach the sink intact, or does something upstream sanitize or strictly constrain it?
  • Preconditions: What conditions must be met for the bug to trigger? Does it require a non-default setting, a specific feature flag, or a narrow time window the attacker must hit?
  • Authentication: Can an unauthenticated attacker trigger it, or does it require a logged-in user or an administrator?
  • Read vs. Write: Can the attacker only read data, or can they also modify or delete it?
  • Blast Radius: If the PoC fires successfully, who is affected? Is it one user or all users, one tenant or the entire platform, userland or the kernel?

To turn this rubric into a reliable score, have the model write out its detailed answer to each of these questions before assigning a severity level. Going through the evidence first prevents the model from anchoring on the bug class (e.g., thinking “SQL injection, so critical”) and then inflating the severity to match that assumption. As a practical starting point: zero preconditions with unauthenticated remote access is critical or high severity. One or two preconditions, or an authenticated path, is medium. Three or more preconditions, or local-only access, is low. These thresholds should be adjusted to fit your specific system.

Models frequently inflate severity simply because they lack sufficient context. They may not know what inputs an attacker actually controls, or they cannot see compensating controls that exist outside the codebase. As an example of the former, a SQL injection is critical if triggered by an unauthenticated web request, but a complete non-issue if triggered by an admin-only configuration file. For the latter, upstream Web Application Firewalls or authentication gateways that prevent exploitation may not be visible from the source code alone.

The solution is to provide the threat model during the triage phase. This tells the model exactly which types of vulnerabilities you do and don’t care about in your specific system context. For example, clarifying that “we trust authenticated clients” can simplify or eliminate an entire class of critical findings.

Real-World Scenario: One team found that the model is often overconfident unless grounded in verifiable evidence or given more context on whether something is expected as part of the threat model. Their fix was elegantly simple: give the triage agent the exact same threat model that the discovery agent uses.

Data Analysis and Triage
图片来源:Unsplash

Step 6: Patching — Closing the Loop and Improving Context for the Next Cycle

How do you ensure that generated patches actually fix the root cause without breaking existing system dependencies?

Patching is where you close the loop and actually fix the vulnerabilities. It also serves to improve the threat model based on verified findings—updating trust boundaries or highlighting components that require more scrutiny—and feeds past findings back into the context for the next scan. Each cycle hardens the codebase and makes the next scan significantly better informed.

Before applying a patch, write a new test that specifically fails with the existing, vulnerable code. Then, implement the fix and confirm that the new test now passes without breaking any other existing functionality. This is, fundamentally, test-driven development applied to security. If you do not add a test, the fix can silently regress in the future, and it becomes exceptionally difficult to retroactively prove the bug was ever real.

Real-World Scenario: One penetration tester found that their generated patches were highly inconsistent—some good, some bad—until the harness was updated to tell the model to validate patches by re-running the proof of concept against the patched code. By giving the model concrete feedback to iterate against, patch quality jumped dramatically, saving substantial time on human review.

Fixing the Root Cause and Searching for Variants

Models have a tendency to narrowly address findings at a specific call site rather than fixing the underlying root cause. Simply prompting the model to explicitly identify and fix the root cause can be highly effective. Once the root cause is addressed, have the model look for variants at two distinct levels:

  1. Same pattern: Are there other call sites or copies of the exact same buggy code elsewhere in the codebase?
  2. Same class: A codebase with one SQL injection vulnerability very frequently contains more SQL injection vulnerabilities.

Update the threat model with the validated findings and patches to formally close the loop.

Before shipping any patch, run an adversarial check. Have a fresh discovery agent probe the patched code as an attacker would, confirming that the patch is comprehensive and doesn’t introduce new attack surfaces. Then, critically evaluate the generated patch to simplify it. Minimal patches are substantially easier to review and far less likely to introduce new bugs. Prompt specifically for the smallest possible change that fixes the root cause—absolutely no refactoring, no drive-by cleanups, and no code reformatting.

Real-World Scenario: One team identified their most common patch failure mode: “The recommended patches tend to be as restrictive as possible, to the point that they would break connections with other services. It would address the issue, but break the dependencies that allow the service to work in the first place.”

The Patch Validation Ladder

You can validate every patch against a structured ladder of checks, starting with the cheapest and fastest:

  1. Build: The patched code compiles successfully, and the new security tests pass.
  2. Try to Reproduce: The original PoC must stop working. This catches ineffective or superficial patches.
  3. Check for Regressions: The original application test suite still passes entirely. This catches broken or over-restrictive patches.
  4. Re-attack: A fresh discovery agent runs a targeted adversarial check against the patched code. This catches incomplete patches that only address a single symptom.

Finally, while the model can write the patch, a human must still own it. Generated patches fail in highly predictable ways: fixing the symptom instead of the root cause, blocking legitimate user input, or removing access to a dependent service. The ultimate goal is to validate each patch as thoroughly as possible so that human review requires minimal effort. The objective is to help the development team focus their limited time on the nuances the model might be unaware of—such as incoming feature changes or team-specific code style—requiring only minimal updates to the generated patches.

Author’s Reflection: The patching phase is ultimately where the most crucial human-AI collaboration occurs. Models excel at generating patch code, but the subtle understanding of business logic, complex system dependencies, and code maintainability still demands human judgment. This reinforces the reality that AI security tools are not designed to replace human security experts, but rather to amplify their capabilities, freeing them from repetitive manual labor so they can focus on the decisions that genuinely require human wisdom.

Practical Summary and Action Checklist

How can you start using large language models to secure your code today without falling into common implementation traps?

Quickstart Steps

  1. Clone the reference harness: Acquire the defending-code-reference-harness repository and run the quickstart command. It guides you through an interactive workflow—from threat modeling to scanning to triage—using a demonstration target.
  2. Select an initial target: Choose a single service or package. Bootstrap a threat model from the existing code and documentation, and conduct the interview process.
  3. Invest in a sandbox: Dedicate the time to build a sandbox of your environment. This represents the largest upfront investment but is critical for long-term success.
  4. Execute the first scan: Run the scan. Verify the findings using an independent agent. Triage the results based on your specific criteria and manually review everything rated high and above.
  5. Patch and iterate: Apply the patches. Then, establish a cadence to re-scan periodically.

Essential Resources

  • Claude Security: A managed product for agentic vulnerability detection and patching.
  • Defending Code Reference Harness: A companion repository containing skills for interactive workflows and a demonstration harness for autonomous runs.
  • Claude Code Security Review Action: A GitHub action that integrates a model as a security reviewer on every pull request.
  • Threat Intelligence Enrichment Agent: A cookbook for building an agent that enriches indicators of compromise against threat intelligence feeds.
  • Vulnerability Detection Agent: A cookbook for building an agent that constructs a threat model, scans for vulnerabilities, and triages findings into a structured report.

Common Pitfalls to Avoid

  • Do not expect zero findings: Models are stochastic, and large codebases will always have a long tail of vulnerabilities.
  • Do not skip verification: Discovery and verification must be strictly separated, or the model will self-censor true positives.
  • Do not ignore the threat model: Scanning without an accurate threat model will generate massive volumes of false positives.
  • Do not blindly trust generated patches: Always validate that patches do not break existing functionality or sever system dependencies.

One-Page Summary

The Problem: When using large language models for code security, discovering vulnerabilities has become easy, but verifying, triaging, and patching them has become the primary bottleneck.

The Solution: A six-step “Find-and-Fix” loop.

  1. Threat Model: Define trust boundaries and what constitutes a vulnerability to minimize false positives.
  2. Sandbox: Isolate the execution environment and verify actual exploitability.
  3. Discovery: Provide rich context and tools, using surprisingly simple prompts.
  4. Verification: Operate entirely independently from discovery to filter out non-exploitable findings.
  5. Triage: Deduplicate by root cause and rank severity based on reachability, attacker control, preconditions, authentication, read/write access, and blast radius.
  6. Patching: Use a test-driven approach, fix the root cause, search for variants, and strictly validate the patch.

Key Insights:

  • The first scan yields the most findings; subsequent runs yield fewer but more complex vulnerabilities.
  • Models perform best on systems with well-documented threat models, achieving up to a 90% exploitable finding rate.
  • Adding an adversarial verifier halves non-exploitable findings; requiring a PoC drops the false positive rate to near zero.
  • Minimal, targeted patches are always easier to review and less likely to introduce new bugs than massive refactors.

Implementation Advice: Invest heavily in the threat model and sandbox upfront. Allocate your budget and time to the pipeline after the scan, rather than just funding more scanning.

Frequently Asked Questions

Q1: How do I reduce the false positive rate when using large language models for vulnerability discovery?
A: Build an accurate threat model that explicitly defines trust boundaries and what actually constitutes a vulnerability. Use an independent verification agent that is instructed to assume all findings are false positives. Require the verifier to build a working proof of concept, and evaluate severity based on actual factors like reachability and attacker control, rather than just the vulnerability class.

Q2: Is it mandatory to build a sandbox environment before I can start scanning code with a model?
A: No, it is not strictly mandatory. Frontier models are highly capable of finding vulnerabilities through static source code analysis alone. The trade-off is that without a sandbox, you cannot execute proofs of concept during the verification phase. You will need to budget significantly more time for static verification, and you can choose to invest in building a sandbox later once the volume of findings justifies the engineering effort.

Q3: Why must discovery and verification be performed by separate, independent agents?
A: When a single agent attempts to both discover and verify vulnerabilities simultaneously, it tends to self-censor. It will often filter out exploitable true positives because it is second-guessing its own work. Separating the two processes ensures the discovery phase maintains high recall (finding everything possible) while the verification phase maintains high precision (confirming only what is real).

Q4: How should I accurately assess the severity of a discovered vulnerability?
A: Evaluate the finding based on six specific factors before assigning a severity label: reachability from an external entry point, the level of attacker control over the input, the preconditions required to trigger the bug, the authentication level required, whether the exploit allows read or write access, and the blast radius of the exploit. Evaluating these factors first prevents the model from artificially inflating severity based solely on the bug category.

Q5: What are the most common failure modes for AI-generated security patches?
A: There are three primary failure modes: fixing the symptom at a specific call site rather than addressing the underlying root cause; blocking legitimate user input by making the patch overly restrictive; and severing connections to dependent services. The solution is to use test-driven development, verify the patch against the original test suite, and prompt the model explicitly for the smallest possible change.

Q6: How often should I scan my codebase after the initial deep scan?
A: After the first intensive iteration, you should continue to scan either on a regular periodic schedule or whenever the code undergoes a meaningful change. You should never expect subsequent scans to yield zero new findings; because models are stochastic and large codebases have long tails of vulnerabilities, new issues will continue to trickle in even if the code remains unchanged.

Q7: What is the most effective way to deduplicate a massive list of vulnerability findings?
A: Use a two-pass approach. First, apply a cheap, deterministic pass to group obvious duplicates based on matching files, categories, and line numbers within ten lines of each other. Second, use a model to apply qualitative rules to determine if remaining findings share the exact same root cause (and are therefore duplicates) or represent distinct vulnerabilities requiring separate fixes.

Q8: What critical information must be included in a threat model for AI security scanning?
A: A robust threat model must include system context, assets, entry points, and trust boundaries. It must explicitly name what inputs and systems are trusted (such as configuration files or authenticated clients). It should document the specific vulnerability classes you care about and those you do not, along with the reasoning. Finally, it should incorporate the existing security policies of your dependencies rather than attempting to rebuild them from scratch.