Semgrep: how to review code when a static analysis engine finds patterns before your team opens the diff
Semgrep is a pattern-matching static analysis engine that scans code for security vulnerabilities and code quality issues before code reaches production. It runs in CI/CD pipelines, integrates into GitHub and GitLab as a PR bot, posts findings as inline comments, and can block merges when high-severity issues are present. Semgrep Assistant — the AI layer — reads findings and produces plain-language explanations of what the vulnerability is, why it matters, and how to fix it. It also triages false positives automatically, reducing alert fatigue for teams running large rule sets.
That workflow — automated pre-scan, AI-assisted explanation, inline PR comment findings before any human opens the diff — creates a specific set of review dynamics. The scan runs before review. The AI explains before the reviewer forms an independent read. The findings are in the PR thread before the code is fully understood. Understanding how this shapes judgment does not make Semgrep less valuable; Semgrep is one of the most effective tools for catching pattern-matchable vulnerabilities at scale. The traps are in how the output interacts with the review that happens after it.
The three Semgrep review traps
1. Rule coverage as security coverage
Semgrep finds code that matches rules. The Semgrep Registry contains thousands of community-maintained and Semgrep-authored rules covering known vulnerability patterns: SQL injection, cross-site scripting, server-side request forgery, hardcoded credentials, insecure deserialization, path traversal, weak cryptography, dangerous subprocess calls. When none of those patterns appear in your code, the scan returns zero findings. Zero findings is then interpreted — often implicitly, without deliberate reasoning — as “no security issues.”
The gap is everything that falls outside pattern-matching scope. The most common class of vulnerability in production systems — broken access control, including insecure direct object reference (IDOR), privilege escalation, and authorization bypass — does not produce a Semgrep finding on correct-looking code. The code that implements getDocument(id) and returns the document regardless of whether the requesting user owns it looks syntactically identical to code that checks ownership first. The difference is the business logic of the missing check, not the pattern of the code that’s present. Semgrep cannot detect an authorization check that isn’t there.
The same applies to state machine errors, incorrect invariant maintenance across transactions, race conditions in asynchronous code, and logic errors where the wrong condition is checked. None of these produce pattern matches because their “pattern” is indistinguishable from correct code at the syntactic level. Only the semantics make them wrong, and semantics require understanding the application’s intended behavior — not matching a rule.
The fix: before treating a clean Semgrep run as a security clearance, name two vulnerability classes Semgrep could not have detected in this specific PR. For most PRs touching user data, those classes are authorization correctness and business logic errors. Identify the code paths that handle user-controlled data and verify authorization explicitly as a separate pass, independent of what Semgrep found or did not find.
2. Custom rule confidence
Semgrep’s rule language makes it accessible to write rules targeting your specific stack and codebase conventions. If your team has a deprecated internal API that must not be called, there is a rule for that. If a particular pattern of string interpolation in your templating engine creates injection risk, a custom rule catches it. Building a custom rule set feels like investing in coverage that the generic registry cannot provide — coverage tuned to your actual codebase.
The trap is that rules cover exactly what you thought to write rules for. Each rule represents a specific pattern that someone on your team, at a specific point in time, decided was worth catching. The vulnerabilities that surprise teams at production are precisely the ones nobody thought to write a rule for: the deprecated API added to the project last month after the rule set was last reviewed, the injection variant using a different library from the one the rule targets, the third usage of a dangerous pattern that the rule writers only knew about two instances of. The custom rule set is a map of your team’s security knowledge at the time it was last maintained, not the current state of the codebase.
Rule sets also accumulate their own technical debt. Rules targeting framework APIs that were updated two versions ago may fire on code that is no longer dangerous or miss code that now is. Rules that worked correctly when the exclusion list was written may flood with false positives after a codebase restructuring. Teams that experience high false positive rates stop reading Semgrep findings carefully, which means even accurate findings get dismissed during the habituation period.
The fix: treat the custom rule set as a targeted detector for specific high-priority patterns, not a comprehensive scanner. Review the rule set itself quarterly the same way you review other security controls — are the excluded paths still correct, are the targeted APIs still the dangerous ones, did last quarter’s security incidents suggest patterns that need new rules. A maintained rule set gives accurate signal; an unmaintained one gives noise that trains reviewers to ignore findings.
3. Semgrep Assistant explanation as fix prescription
Semgrep Assistant reads a finding and produces a plain-language explanation: what the vulnerability pattern is, why code matching this pattern is dangerous, and what a correct remediation looks like. The explanation is accurate. It accurately describes the vulnerability class, the attack scenario that makes it dangerous, and the general fix. What it does not describe is the vulnerability in your specific application.
The gap is context. A Semgrep finding on a SQL query might correctly identify an injection risk — string concatenation is present in the query construction path. Semgrep Assistant accurately explains SQL injection and proposes parameterized queries. But the finding is in an internal reporting tool accessible only to database administrators, behind two authentication layers, not reachable from any external interface. The pattern is real; the risk may be low in your threat model. The uniform severity assigned at the rule level does not account for where in your application the pattern appears.
The inverse problem matters more: a finding in a high-traffic public API endpoint handling user-controlled input represents far greater urgency than a finding in an internal admin endpoint. Both produce findings with the same rule severity. Semgrep Assistant’s explanation is identical for both because the explanation describes the pattern, not the context in which the pattern appears. Triage that routes by rule severity rather than application context misallocates effort — spending more time on low-risk internal findings than on high-risk public-facing ones because the rule assigned them equal priority.
Semgrep Assistant cannot evaluate reachability from external users, whether the affected code path requires authentication, whether other controls in the application already mitigate the risk, or whether your data model means the vulnerable path is unreachable in practice. The fix: use the explanation to understand the vulnerability class, then evaluate priority and remediation against your application’s actual trust model. The question to answer before triaging each finding is not “how dangerous is this pattern?” but “how dangerous is this pattern at this location in my application, reachable by which users, with which existing controls in place?”
Using Semgrep findings without mistaking pattern detection for comprehensive review
The three traps share a common structure: each involves a measurement that is accurate about what it measures and silent about what it does not cover. Semgrep measures rule matches accurately — and says nothing about authorization logic or business logic errors. Custom rules measure the patterns you wrote rules for — and say nothing about the patterns you did not think of. Semgrep Assistant explains the vulnerability pattern accurately — and says nothing about your application’s trust model or the risk context of the finding’s location.
Semgrep is one of the most effective tools in the SAST category precisely because it is excellent within its scope. The traps arise when that scope is not made explicit. Using Semgrep well means running it for what it is — a high-accuracy detector of pattern-matchable vulnerabilities within your defined rule set — while maintaining an explicit review pass for the gaps that fall outside pattern detection: authorization correctness, logic errors, and context-specific risk assessment that no rule set can automate.
Related reading: SonarQube AI Code Assurance on the same quality-gate-pass-as-review trap in a different SAST context, with AI Code Assurance layered on top of the traditional rule engine. Snyk Code on a security-focused scanner with a different integration model and the traps specific to supply-chain vulnerability context bleeding into code review. GitHub Copilot Autofix on the alert-count-zero-as-security-clean trap and the gap between what code scanning covers and what a PR actually requires. CodeRabbit on a PR-layer AI reviewer with different scope from SAST tools. How to review AI-generated code for the base checklist that applies after the automated tools have run their pass.
Semgrep found the pattern. ZenCode asks whether you checked the trust model.
ZenCode surfaces one concrete review question before you accept — the check that rule coverage, custom rules, and Semgrep Assistant explanations cannot answer for your specific application context.
Try ZenCode free