Semgrep setup: why code analysis doesn't catch vulnerabilities
Typical picture: Semgrep setup formally done — run in build pipeline exists, report comes, but little benefit. Either everything triggers, including fixtures and documentation examples, or rules so few they are silent on real problems. Team quickly starts considering such checks a formality.
First root — the rule set itself. Ready-made configurations don't know your conventions: how you handle secrets, what wrappers over requests you use, where untrusted input is even allowed.
Analyzer rules create noise exactly where project context is not accounted for, and the author of the change gets used to closing a finding without reading it.
Second root — thresholds and handling of findings. If every remark blocks build, exceptions appear in config; if nothing blocks, code checks are ignored. Workable scheme — fail on new findings in changed lines and keep accumulated debt as separate queue with clear owner.
Third root — place of check in process. When security analysis in build pipeline runs only on main branch or once a day, feedback comes hours after change and no longer connects with it in author's mind. Static code analysis doesn't work not because tool is weak, but because it's run for a checkbox.
Next we break down how this is closed step by step: delineation of rules by responsibility zones, thresholds on changes, regular triage of findings, and maintaining configuration in up-to-date state.
Scan speed, report noise, rule coverage
A full Semgrep run over entire repository history takes tens of minutes — and developer pays this price on every change check.
We configure analysis only on changed code: affected and dependent files are scanned, rules are taken from cache, run is a mandatory step before branch merge. Change check fits in a couple of minutes and doesn't knock engineer out of task context.
Noise is main reason teams disable scanner. Share of false positives falls from configuration, not from "magic": precise patterns instead of broad expressions, explicit severity thresholds for blocking levels, exceptions for generated code and test data.
Separately we maintain finding baseline: build fails only on new defects, while inherited ones go to backlog with an owner.
Public rule set covers typical patterns, but knows nothing about internal libraries and custom data access wrappers. Therefore we write our own rules: mandatory permission checks before query, input sanitization functions, ban on building queries from user input.
Analysis rule coverage grows from standard profile to a map of "which class of defects and where exactly we catch."
We measure effect by finding flow: how many confirmed, how many rejected as false, which defect classes don't reach release. Query injections, unsafe deserialization, keys and tokens hardcoded in code stop surfacing during penetration testing — they are blocked at merge.
Scan modes: build pipeline, local run, schedule
Modes differ not by rule set, but by trigger point and volume of code checked. Same rules give different response time and different coverage depending on whether we scan entire repository or only delta. Below is what limits each option and at which tier it is appropriate.
| Mode | When appropriate | What limits it |
|---|---|---|
| Full branch scan in build pipeline | Protected branches and merge requests into main | Time grows with repository size: on a large monorepo this is tens of minutes, so we keep it only on key branches |
| Check only changed files | Every commit in working branches | Doesn't see problems in code that didn't change, and requires correct comparison base when history is rewritten |
| Local check before push | Pre-commit hook and manual run before pushing changes | Depends on discipline and rule version on developer's machine: divergence from server and bypassing the check |
| Scheduled run | Nightly full run of main and long-lived branches | Report arrives with delay and doesn't block merge; needs delivery channel and regular triage of findings |
These modes don't compete, but fold into tiers: fast feedback locally, blocking check of changes in pipeline, regular full run on schedule. Developer gets feedback before pushing code, and infrastructure gets guarantee that coverage doesn't degrade.
Switching between modes is a one-config edit, not rewriting rules: rule set and finding thresholds remain shared.
Semgrep setup: order of work from audit to handover
We build order of work so analysis starts bringing benefit before it becomes mandatory build step. First inventory and launch in soft mode, and only after triaging findings is a rule moved to blocking. Otherwise team gets hundreds of findings on first day and stops looking at report at all.
- Audit of current rules. Export what is connected now: which sets are active, what actually triggers, what duplicates linters and formatters, which checks are noisy on legitimate code.
- Analysis launch scheme. Determine entry points — on every change, on schedule on main branch, before version release — and what is cached between runs so triage doesn't lengthen build.
- Project-specific setup. Disable noisy checks, raise severity thresholds, write some rules for internal conventions: secrets handling, incoming data parsing, calls to external services.
- Check on change history. Run configuration over past changes, not only fresh code: this shows real volume of findings and which rules produce false positives on your codebase.
- Warning mode instead of blocking. First passes don't stop anything: report is collected, findings are triaged, false positive counter goes down. Blocking is enabled when share of reliable findings justifies it.
- Triage protocol. Agree who reviews report and within what time, how a false positive is marked, and how a rule gets into exceptions — with justification, not silently.
- Handover. Rules are stored in repository next to code, launch scheme is described, team has procedure for editing and clear entry point.
Artifacts: rule configurations, schemes, finding triage protocol
After work completes, repository retains not a one-time setup, but a working analysis workflow that customer's team develops without us. All artifacts are versioned with code and go through review like normal changes — then it's visible who edited checks and why.
- Sets of custom rules for our stack, split by languages and responsibility zones: mandatory for merge and informational. Each rule comes with an example "triggered — what to do" and a test on a test set.
- Exception and suppression policy: where suppression is placed (in code or in configuration), who approves it, mandatory reason, owner, and lifetime. Exceptions without owner go to a separate report.
- Finding triage protocol: which criticality levels block merge, which go to queue, who makes decision "fix / suppress / accept risk" and how justification is recorded.
- Documentation for running checks: local run before commit, branch report, reproducing a finding, duration of full pass.
- Build integration configuration: steps, dependency caching, parallel jobs, fail-on-new-findings mode.
- Baseline snapshot of current findings — enabling checks doesn't block team's work on first day.
- Workflow metrics: share of triaged findings, number of false positives per rule, dynamics by criticality.
We hand over the set with brief training: customer engineers run it themselves, and we stay in touch for rule development.
Case: false positive share and check time before and after
Starting point
Project is a monorepo with a dozen services and shared pipeline; code analysis was enabled on default rules. After every change, report brought hundreds of remarks, of which units were confirmed: false positive share held at 71%.
Triaging report before merge took on average 45 minutes, engineers started skipping checks and looking only at build status — protection formally worked, but caught nothing.
What changed
Semgrep was configured for repository structure, not the other way around. Rules were split into sets: critical ones (secret leaks, injections, unsafe deserialization) block merge, the rest go to report without stopping pipeline.
Custom rules described internal data access wrappers, and test directories and generated code were moved to exceptions. Added comparison with base branch — only changed lines are checked, not whole file.
Result
False positive share fell from 71% to 9%, and report was read in full again, not selectively. Change check time decreased from 45 minutes to 7, while no critical finding was lost: they are caught at check stage, not in prod.
After a few weeks team stopped arguing about noise and started developing rules — a new set for a service is added in one pass.
How stock rules differ from custom ones and when both are needed
Ready-made Semgrep set covers typical vulnerability classes: injections, weak cryptography, secrets in repository, dangerous calls of popular libraries. Findings appear from first run — rules rely on general language signatures, not on your code structure.
Custom project rules describe architecture specifics: mandatory call of wrapper instead of direct database query, permission check in handler, ban on internal module with known weakness. They are tied to names of your entities, so another project won't pick them up.
| Set | When suitable | Limitations |
|---|---|---|
| Stock | First run, external dependencies, typical errors | Doesn't see internal conventions, noisy on constructs safe for you |
| Custom | Your own data access layer, mandatory permission checks | Requires maintenance: after refactoring, patterns need rechecking |
| Both together | Product with custom architecture and external dependencies | Prioritization needed, otherwise finding flow mixes and triage stretches |
Combining sets gives full picture: stock covers external perimeter, custom covers team's internal agreements.
Prioritization goes by finding source: trigger of your own rule almost always means violation of your own code convention, while stock warning more often requires context check.
The boundary is a simple question: will the rule trigger in an unfamiliar project in the same language? If yes — it's stock set; if it relies on your modules — custom. Both together cover general background and project architecture specifics.
Limitations and typical questions before implementation
Code analysis at build stage doesn't replace dynamic testing and manual review: it sees data flow inside files and modules, but is powerless against access rights, environment configuration, and logic that manifests only under load.
Therefore we embed checks as one of the layers — alongside dependency control and secret search. Below are doubts that customer's team raises most often before work starts.
A separate topic is maintenance. Your rules in Semgrep live like code: they have an owner, tests on trigger examples, and a run when engine is updated. Without this, in a couple of months the set turns into a dump that is disabled entirely — along with useful checks.
Will false positives paralyze the team's work?
We remove three causes of noise: build a baseline from current repository state and don't block what existed before; enable checks only on changed lines; set a criticality threshold for blocking, the rest goes to report.
Suppression is documented with annotation, justification, and review deadline. We calculate false positive share at weekly triage sessions — it's a measurable value, not a feeling.
What happens when rules or engine are updated?
Sets are versioned with code; a new revision is first run on reference branch and compared with previous result by number of findings. If difference is explainable — we enable it; if not — rule is refined. First run after update goes in warning mode, build doesn't fail.
Can it be implemented in stages, without stopping releases?
Yes, and this is preferred path: one or two services in observation mode, then blocking on changed lines, then expansion to remaining repositories. We fix stage order in advance so team sees when checks will start affecting build and has time to triage accumulated findings.
How we'll start work on your repository
So that work on repository starts in days, not weeks, we need a minimal set of inputs from you and one short business conversation. The list below is what we request during task discussion; it also becomes framework for check implementation plan.
- Access to code: repository or its mirror with commit history — without it, baseline snapshot cannot be built and new findings cannot be separated from those that have long lived in codebase. For monorepo — breakdown by directories and module owners.
- Access to build systems: right to create a separate service user and add analysis step, but without rights to change release logic.
- Integration points: what should trigger the check — commit, merge request, nightly run over full history. This determines where we set blocking thresholds and where only report.
- Existing rules and exceptions: which linters and analyzers already work, where suppressions are stored, who approves them. Duplicating existing checks makes no sense.
- Priorities by finding classes: which categories are critical for you, what goes to ignore (tests, generated code, third-party vendor directories). This directly affects volume of custom rules.
- Triage owner on customer side: one engineer who triages findings and decides what goes to work and what goes to exception.
After this we return with estimate and plan: stages, volume of custom rules for Semgrep, target noise level, and metrics by which we'll verify result. Describe the task — we'll start with repository audit.







