AI code security scanning tools need a new gate
AI code security scanning tools need fast pull request gates, deep analysis, tested baselines, and CI controls that coding agents cannot weaken.

Table of Contents
Code generated by AI changes the economics of static analysis. It can add a week of human typing in an afternoon, repeat the same unsafe pattern across five services, and make a plausible fix that satisfies tests while weakening authorization. A quarterly scan cannot keep up with that change rate.
The answer is not an "AI security" badge on a familiar scanner. Teams need AI code security scanning tools that run at the right moments, understand data flow, enforce local rules, and leave a result that blocks a merge when policy says it should. I treat generated code as untrusted contribution until a human review, static analysis, and the relevant tests agree.
This is still SAST, but the operating model has changed. The scanner must sit outside the coding agent, scan the diff quickly, scan the whole repository deeply, and make suppression harder than fixing the code. If the same agent writes the change, reviews it, and decides whether its own warning matters, you have built one fallible approval path with three labels.
Generated code changes volume, variance, and trust
Generated code is not automatically less secure than handwritten code. It is less predictable because its quality depends on the model, repository context, prompt, available examples, and constraints supplied for that particular run. Two prompts that describe the same endpoint can produce different authorization checks, query construction, error handling, and logging.
Volume makes that variance expensive. A developer who writes one handler slowly may notice that the authorization check happens after the database read. An agent can reproduce that ordering across every generated handler before anyone opens the pull request. Reviewers then face a clean, consistent diff, and consistency creates false confidence.
Four failure patterns appear repeatedly in generated changes:
- The code validates identity but skips authorization for the specific object, so an authenticated user can read another tenant's record.
- The code builds a shell command or query with a value that passed type validation but was not escaped for that context.
- The code copies an obsolete internal example, including a weak cryptographic choice or an overly broad CORS rule.
- The code catches an exception and logs the complete request, which can put tokens or personal data into telemetry.
Traditional SAST already knows parts of this problem. OWASP's Source Code Analysis Tools guidance says static analysis scales, runs repeatedly, and can locate defects such as SQL injection. The same guidance is blunt about false positives, configuration blind spots, limits around access control, and projects that cannot be built. Generated code magnifies those weaknesses; it does not erase them.
Authorship labels do not solve the trust problem. A commit may mix agent output, generated migrations, copied snippets, and human edits. Metadata also disappears through rebases and squashes. Apply the strict policy for new code to every change. If you can reliably tag pull requests created by agents, use the tag for measurement, never as the only trigger for security analysis.
Be skeptical of scanners marketed mainly around the ability to recognize AI output. Provenance can help a manager compare review load or defect rates, but it does not expose the vulnerability. Detection quality still depends on language parsing, framework models, flow analysis, and rules that match how the application handles trust boundaries. A tool that identifies generated code perfectly but misses a request value reaching a shell is measuring the wrong thing.
The useful AI feature is a shorter path from a credible finding to a reviewed fix. A scanner may explain the path, suggest a patch, or hand structured context to a coding agent. That saves time only if the original rule remains the authority and CI rescans the resulting commit. Ask vendors to demonstrate this closed loop on your repository: inject a defect, receive the finding, generate a repair, rerun analysis, and preserve the evidence. Do not accept a polished explanation as proof that the repaired data flow is safe.
SAST and an LLM review answer different questions
A scanner follows repeatable rules and data flow models; an LLM review interprets intent and surrounding design. You need both, but only one should make deterministic merge decisions.
SAST is good at asking whether untrusted input can reach a dangerous sink, whether code calls a prohibited function, or whether a known insecure pattern occurs on a changed line. It produces a stable rule identifier, a source location, and usually a structured result. Run the same commit twice with the same scanner version and configuration, and you should get the same finding set.
An LLM can notice a missing tenant boundary, a misleading abstraction, or a business rule that no generic scanner knows. It can also explain a finding in language a developer understands. Its answer may change with context ordering, model updates, or a slightly different prompt. That makes it useful review evidence and a poor sole gate.
The distinction that teams often blur is detection versus adjudication. A scanner detects a pattern or flow. A person decides whether the path is reachable, whether the sanitizer is sufficient, and what change preserves intended behavior. An LLM can help assemble that evidence, but it should not silently dismiss a severe alert or write its own suppression.
Keep the scanner independent from the coding agent. Give the agent the findings after the scan and let it propose a patch, then run the scanner again on the patched commit. Store policy in the repository or the scanning platform, with changes reviewed through normal ownership rules. A prompt such as "fix all findings" is an instruction to attempt remediation, not permission to change the rules until the build turns green.
SAST also does not replace software composition analysis, secret scanning, infrastructure checks, dynamic testing, or a review focused on authorization. A source scanner can identify a call pattern in your code. It cannot tell you that a deployed route bypasses a gateway rule if that configuration lives elsewhere, and it will not find a vulnerable transitive package unless the product includes a separate dependency analyzer.
The useful comparison starts with analysis depth
Tool lists usually compare language counts and dashboard features. Those columns matter, but they do not tell you whether the tool can trace your request object through framework helpers into a query builder, or whether a developer can encode one local ban before lunch.
This comparison focuses on the job each tool does best. Product plans and supported languages change, so verify current coverage against the exact languages, frameworks, repository host, and deployment model you use.
- GitHub CodeQL fits deep analysis for supported languages in teams centered on GitHub. It builds a database from code, runs queries over it, and publishes code scanning alerts. Setup for compiled languages can require a working build, and unsupported languages need another scanner.
- Semgrep fits fast pull request checks and local policy rules. Structural patterns and taint rules are easy to keep near the code, while
semgrep cisupports flows that scan changes. The open source and commercial engines differ in analysis depth, especially when data crosses files. - Snyk Code fits a managed workflow across the IDE, repository, CLI, and CI. Pull request checks and
snyk code testput findings in the development path. Review account requirements, repository integration, data handling, and plan limits explicitly. - SonarQube fits one quality policy across maintainability, reliability, and security. Pull request analysis applies a quality gate to new code and can report status to the repository. Confirm that security depth, edition features, server operation, and handling of new code match your governance model.
CodeQL is my first candidate when deep data flow analysis matters and the code lives in GitHub. GitHub's documentation describes two stages: create a database that represents the code, then run queries against it. Its supported set includes C and C++, C#, Go, Java and Kotlin, JavaScript and TypeScript, Python, Ruby, Rust, Swift, and GitHub Actions workflows. For compiled languages, inspect the extraction logs. A green workflow that built only one small subproject did not analyze the repository you thought it did.
Semgrep is the practical choice when speed and custom rules decide whether developers keep the gate enabled. Its rule model distinguishes structural search from taint analysis with sources, sinks, propagators, and sanitizers. The official glossary also makes an important boundary explicit: the open source engine and commercial engine do not have identical analysis across files. Test the edition you plan to run on a real vulnerability that crosses files, not a demo contained in one file.
Snyk Code fits teams that want a managed path through IDEs, source control, the CLI, and CI. The CLI contract is useful for automation: snyk code test returns 0 when the scan succeeds with no findings, 1 when findings require action, 2 for a scan failure, and 3 when it detects no supported projects. Treat 2 and 3 as pipeline failures. Otherwise, a broken scanner can look like clean code.
SonarQube fits organizations that want security findings inside a wider quality gate for new code. Its pull request documentation says PR analysis reports issues introduced by the change, while analysis of the default branch can surface findings that a PR view misses. That nuance is a reason to schedule a full default branch scan, not a defect you can wish away with a stricter PR threshold.
No comparison can choose for you. Put one known vulnerability from your framework, one flow across files, one custom prohibited pattern, and one clean but unusual implementation into a test repository. Measure detection, noise, runtime, triage clarity, and whether CI fails correctly when the scanner itself breaks.
Three scanning lanes keep feedback fast and coverage honest
Run a fast diff scan on every pull request, a deeper analysis before merge or on the merge queue, and a full scan of the default branch on a schedule. Those lanes solve different timing problems, so collapsing them into one job either slows every change or leaves blind spots.
The pull request lane should finish quickly enough that developers wait for it. Use scanning limited to changes where the product supports it, plus secret scanning and a small set of custom rules with high confidence. Fail on newly introduced findings that meet your severity and confidence policy. Do not fail a new pull request because of 900 inherited alerts in untouched files; that teaches developers that the gate has nothing to do with their work.
The deep lane runs analysis that needs builds, context across files, or more expensive query suites. Put it in the merge queue if its runtime is acceptable. If it takes much longer, run it after every merge and stop deployment when it finds an issue that blocks release. The scheduled lane scans the complete default branch with pinned scanner versions and current rules, because new rules can expose old code without any source change.
One GitHub Actions layout can separate fast Semgrep feedback from a deeper CodeQL job:
name: code-security
on:
pull_request:
push:
branches: [main]
schedule:
- cron: "17 3 * * 2"
permissions:
contents: read
security-events: write
jobs:
semgrep:
runs-on: ubuntu-latest
container: semgrep/semgrep
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: semgrep ci
codeql:
runs-on: ubuntu-latest
strategy:
matrix:
language: [javascript-typescript, python]
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- uses: github/codeql-action/analyze@v3
This is a starting shape, not ready made infrastructure. Pin external actions by commit digest under a serious software supply chain policy. Add an explicit build between CodeQL initialization and analysis when the selected compiled language or repository layout needs it. GitHub documents automatic and manual modes, plus modes that need no build for some languages; confirm that the chosen mode extracts the files shipped to production.
Branch protection must require both jobs. A workflow that posts annotations but is not a required check is a comment system, not a gate. Protect the workflow and rule directories with code owners, restrict who can change required checks, and review any pull request that weakens permissions, triggers, paths, or severity thresholds as a security-policy change.
Baselines control debt without licensing new defects
A baseline records accepted existing findings so the team can block regressions without pretending the backlog is clean. It is a migration tool, not a blanket ignore file.
Start with a full scan of the default branch at a fixed scanner and rule version. Triage findings that block release immediately. Record the rest with an owner, reason, scope, and expiration date in whatever suppression mechanism the tool supports. Then configure the pull request gate to fail on new findings relative to that reviewed state.
Severity alone is a weak policy. A "medium" command injection on a worker exposed to the internet may deserve an immediate block, while a "high" result in dead example code may need removal rather than emergency response. Combine severity with reachability, exposure, confidence, and the asset's role. Keep the policy small enough that a developer can predict the result before pushing.
Suppressions need friction. Require a rule identifier, a narrow code location, a reason that explains why the path is safe, an approver, and an expiry. Do not accept "false positive" as the whole reason. If the same pattern receives repeated suppressions, fix the rule, model the framework, or replace the check. Repeated local exceptions usually mean the central policy is wrong.
Generated fixes deserve the same baseline rule as generated features: they may remove the visible match while leaving the vulnerability. An agent might wrap a dangerous value in a helper that the scanner treats as a sanitizer, even though the helper only trims whitespace. Review the sanitizer implementation, add a regression test with an input shaped like an attack, and rerun the full flow analysis.
Track four operational measures: new findings per merged change, median time to a first actionable result, suppressions that expire unresolved, and scanner failures or timeouts. Do not reward teams for a falling alert count by itself. They can achieve that result by narrowing paths, skipping builds, or disabling rules.
Custom rules turn your incidents into permanent checks
Generic rules find generic mistakes; local rules stop your team from repeating its own expensive mistakes. Generated code makes these rules more useful because agents learn from repository examples, including bad ones.
Write a custom rule when the unsafe pattern has a stable syntax or data flow and a clear alternative. Good candidates include direct use of a dangerous internal API, routes that omit the standard authorization wrapper, logging of a sensitive object type, or construction of SQL outside the approved query layer. Keep judgments about business logic with reviewers when syntax cannot express the policy without noise.
This Semgrep rule illustrates a narrow ban on passing an HTTP request value directly into Node.js command execution:
rules:
- id: request-data-to-child-process
message: Request data reaches a shell command. Use an argument array and validate each value.
severity: ERROR
languages: [javascript, typescript]
mode: taint
pattern-sources:
- pattern: $REQ.$FIELD
pattern-sinks:
- pattern-either:
- pattern: child_process.exec(...)
- pattern: child_process.execSync(...)
Do not trust a rule because it looks plausible. Create a small test file with one vulnerable case, one safe constant, one sanitized path, and one path that crosses functions. Run the exact engine and edition used in CI. The case that crosses functions will tell you whether your assumed analysis boundary matches the product you bought.
Store custom rules and their tests beside the application or in a versioned central policy repository. Pin the policy revision in CI. Assign an owner who understands both the vulnerability and the matcher; otherwise rules decay into mysterious build failures that developers learn to bypass.
Feed confirmed review findings back into this library. If an LLM reviewer finds a missing authorization wrapper twice, do not add a longer prompt asking it to remember next time. Encode the invariant in a rule, a framework abstraction, or a test that the pipeline can enforce on every commit.
The dangerous failure is a green scan with missing coverage
A green result proves only that the configured scanner completed its configured work. It does not prove that it examined the code you deploy.
Consider a monorepo with a TypeScript API and a Java billing service. The team enables CodeQL, accepts automatic language detection, and sees successful checks. The Java service uses a custom build command that depends on a generated source step. Autobuild skips that module, but the workflow still analyzes TypeScript and uploads results. A generated Java endpoint later concatenates a report field controlled by the user into a query. The pull request remains green because the vulnerable service never entered the analysis database.
The fix starts with evidence, not another query pack. Read the extractor and build logs, confirm both languages appear, and compare analyzed source roots with the deployment artifact. Add a deliberately vulnerable canary in a test branch for each critical language and framework, then verify that CI blocks it. Remove the canary after the test; do not leave exploitable sample code on the default branch.
Coverage can disappear in quieter ways. A shallow checkout can break a diff baseline. Path filters can exclude generated server files. Forked pull requests may lack the token or permissions needed to upload results. A matrix job can be marked optional. A scanner can return an infrastructure error that a shell wrapper converts to success.
Make those states visible. Fail closed on scan errors for protected branches, publish the analyzed languages and file counts, and alert when the count drops sharply. Save SARIF or the vendor's native result as a build artifact according to your retention and data policy. Test the gate every quarter by introducing controlled patterns that each required scanner must catch.
This is also why I oppose the popular advice to scan only changes labeled as AI output. The label is easy to lose and says nothing about risk. Scan every change, then use authorship metadata to compare defect patterns and decide where training or stronger generation constraints would pay off.
Tool selection is an engineering trial, not a feature vote
Choose the smallest combination that catches your representative defects, fits the feedback budget, and can be operated by the team you actually have. Buying overlapping dashboards without an owner creates more queues, not more security.
Run a trial lasting two weeks on one active repository. Seed or identify cases for injection, authorization, sensitive logging, unsafe configuration, and one flow specific to your framework. Include clean variants that resemble each defect so you can measure noise. Record setup time, median pull request runtime, true findings, false findings, analysis failures, and how much context a developer receives at the line of code.
Give extra weight to policy ownership. Semgrep often wins when a small team needs to write precise local checks quickly. CodeQL often wins when supported languages, GitHub integration, and deeper queryable data flow matter more than simple rule writing. Snyk Code earns its place when the managed developer workflow and a broader security program reduce operational burden. SonarQube earns it when one enforced policy for new code across quality and security already matches how the organization works.
Data handling belongs in the trial. Determine whether source code leaves your runner, what metadata reaches the service, where results live, who can read them, and how deletion works. Review the exact deployment option and contract rather than relying on a generic "enterprise" answer. Generated code may include customer logic, unreleased product behavior, or a secret that should never have been generated; scanning cannot become another uncontrolled disclosure path.
Cost needs a full denominator. Include licenses, runner minutes, build maintenance, triage, rule ownership, and the delay imposed on pull requests. A cheap scanner that developers ignore is expensive. A sophisticated scanner that nobody can tune is an alert subscription.
During a Team & AI Audit at oleg.is, I map this gate to the team's actual generation workflow and delivery bottlenecks, because scanner selection without workflow evidence is guesswork. The same review should name the person who owns policy, the response expectation for a blocked merge, and the process for expired exceptions.
Do not roll out to every repository after a polished demo. Prove detection and failure behavior on the trial repository, document the operating contract, and then expand by risk. The first production gate should be boring: a known set of checks, a known owner, a predictable runtime, and a build that fails loudly when analysis disappears.
Generated code is safe only when the gate is independent
The useful reimagining of SAST is organizational, not cosmetic. Code generation compresses implementation time, so security feedback must move into the same pull request and remain independent of the system that wrote the code.
Give the coding agent narrow repository permissions. Do not give it authority to merge, edit branch protection, change scanner policy, or approve its own suppressions. Let it read a finding, propose a fix, and run local checks. A human reviewer and protected CI still decide whether that fix enters the default branch.
Keep prompts and agent instructions under review too. An instruction file can tell an agent to skip tests, prefer a deprecated helper, or add ignore comments around noisy rules. Your scanner may catch the resulting pattern, but it will not explain why the same defect returns. Treat agent configuration as production tooling with code ownership and change history.
The practical standard is simple to state and demanding to maintain: every change gets fast deterministic checks, risky changes get deeper review, the complete branch gets recurring analysis, and no agent that writes code can weaken those controls. When that standard slows delivery, tune the rule or fix the build. Do not hand the exception decision back to the generator.
The next generated pull request should tell you whether your setup works. Inspect which files the scanner actually analyzed, introduce one controlled failing pattern on a test branch, and watch the required check block the merge. If you cannot produce that evidence, the green badge is decoration.
Frequently Asked Questions
Is generated code less secure than handwritten code?
Not by definition. Its risk comes from higher change volume, variable output, and reviewers trusting plausible code too quickly. Apply the same security gate to all new code, then measure whether agent-created changes produce distinct defect patterns.
Can an AI code reviewer replace a SAST tool?
No. An AI reviewer can reason about intent and explain a defect, but its judgment is not deterministic enough to be the only merge gate. Use it alongside repeatable scanner rules and human adjudication.
Which SAST tool is best for code generated by AI?
There is no universal winner. CodeQL favors deep analysis in supported GitHub workflows, Semgrep favors fast custom policy, Snyk Code favors a managed developer workflow, and SonarQube favors unified governance for new code. Test them against defects from your own languages and frameworks.
Should SAST block every pull request?
A fast SAST check with high confidence should be required on every pull request. Deeper scans can run in a merge queue or after merge if runtime demands it, but a protected branch must fail when analysis errors or disappears.
How do you handle thousands of existing SAST findings?
Create a reviewed baseline, fix issues that block release, and block newly introduced findings. Give every suppression an owner, narrow scope, reason, and expiry so inherited debt does not become permanent permission for new defects.
Does SAST find vulnerable open source dependencies?
SAST analyzes source patterns and data flow, while software composition analysis examines dependency manifests and versions. Some products bundle both, but the checks remain different. Run SCA separately and make both results visible in CI.
How often should a team run a full code security scan?
Run checks on changed code for every pull request and full scans on the default branch after merges or on a schedule. A weekly scheduled scan is a reasonable starting point when new rules arrive between code changes, but repository risk and scan cost should set the final cadence.
How can I tell whether CodeQL analyzed all my code?
Inspect language detection, extractor logs, build output, and analyzed source roots. Test each critical language and framework on a temporary branch with a controlled vulnerable pattern that must block CI. A successful workflow alone does not prove coverage.
Are custom SAST rules worth maintaining?
Yes, when they encode a repeated incident or a stable internal invariant. Give each rule tests and an owner, and remove or repair rules that need constant suppression. A vague business judgment rarely belongs in a syntax matcher.
Should coding agents be allowed to suppress scanner findings?
They may propose a suppression with evidence, but they should not approve or merge it. Keep policy changes, ignore files, and branch protection under separate human ownership. Otherwise the generator can quietly redefine success.


