Semgrep: custom rules for your own codebase's bad patterns
By Elias Lankinen12 min read
The bug that grep can't see
Somewhere in a large codebase right now, a developer is writing subprocess.call(cmd, shell=True). It compiles. The tests pass. The linter is silent. Six months later it becomes a command-injection vulnerability in a security advisory. The tragedy is that everyone on the team already knew that pattern was dangerous. The knowledge lived in a wiki page, in a Slack thread, in the head of a senior engineer who reviewed the pull request that introduced the safe wrapper in the first place. It just was not written down anywhere a machine could enforce it.
This is the gap that custom static analysis is meant to close, and it is a surprisingly wide one. Off-the-shelf security scanners are trained on the vulnerabilities everyone shares: SQL injection, cross-site scripting, hardcoded AWS keys. They know nothing about your LegacyPaymentClient that must never be called without a preceding verify_transaction, or the internal db.raw_query() helper that three teams have been told to stop using. Those are your codebase's bad patterns, and by definition no vendor ships a rule for them.
Semgrep, a static analysis tool whose name is a contraction of "semantic grep," exists largely to let teams write those rules themselves. Its central promise is that a rule can look almost exactly like the code it is trying to catch, which lowers the cost of encoding institutional knowledge from "hire a program-analysis PhD" to "spend an afternoon in a web editor." Whether that promise holds up, and what it costs you when it does, is worth examining in detail.
From a French research lab to a $722 million company
The idea underneath Semgrep is older than the company. Its lineage traces to program-analysis research and, more directly, to a tool called sgrep that engineer Yoann Padioleau built as part of pfff, a program-analysis library Facebook developed in 2009. Padioleau's background was in academic source-code transformation tools for C, the kind used to patch thousands of Linux kernel call sites at once, and sgrep brought that structural way of thinking to bug-finding at Facebook's scale.
The company itself started as r2c, founded in 2017 by three MIT graduates: Isaac Evans, Drew Dennison, and Luke O'Malley, according to Contrary Research's business breakdown. Evans and Dennison had been college roommates and then entrepreneurs-in-residence at Redpoint Ventures, and they spent their first two years struggling to build a developer-friendly security engine. The pivot came in 2019 when Evans recruited Padioleau. At an internal hackathon, Padioleau showed how sgrep could be extended to modern languages like Python; the team forked it from pfff, renamed it Semgrep, and released the open-source version at the end of 2020. The first stable release is dated February 6, 2020. The company rebranded from r2c to Semgrep, Inc. in April 2023.
The commercial trajectory has been steep. A $13 million Series A in 2020 was followed by a $53 million Series C in April 2023 at a roughly $395 million valuation, and then a $100 million Series D in February 2025 led by Menlo Ventures, which valued the company at about $722 million and brought total funding to $193 million. By 2024 the tool was running more than 100 million scans annually across a reported base of over 150 enterprise customers. One widely cited figure of roughly $15 million in 2024 revenue is an unverified third-party estimate rather than a company disclosure, so treat it as a rough indicator rather than a hard number.
How Semgrep reads code the way a compiler does
To understand why custom rules are tractable in Semgrep and painful in most other tools, you have to understand what it is matching against. Plain grep searches text. If you search for exec( you will find the dangerous calls, the harmless ones, the word "exec" inside a comment, a variable named codexec, and a string in a log message. Text has no idea what any of it means.
Semgrep parses your code into an abstract syntax tree, or AST: the same structured, hierarchical representation a compiler builds, where a function call is a distinct kind of node with a callee and a list of arguments, cleanly separated from strings and comments. Rather than searching text, Semgrep searches this tree. That single design choice is why a rule can ignore formatting, whitespace, and comment noise, and why the pattern exec(...) matches the call regardless of how the arguments are spelled or wrapped across lines.
The second building block is the metavariable, Semgrep's version of a regular-expression capture group. A metavariable is written with a dollar-sign prefix, like $X or $FUNC, and it matches any single code element of the right kind: a variable name, an argument, an expression. Crucially, metavariables unify, meaning the same name used twice must bind to the same thing. A pattern like $X == $X finds a comparison of something to itself, a common copy-paste bug, and it does so no matter what $X actually is.
Where Semgrep pulls decisively ahead of text search is type awareness. In statically typed languages it remembers the declared type of a variable and lets you match on it. Consider trying to catch a specific dangerous call. Without types, you would have to enumerate every way a value might be declared and passed:
patterns:
- pattern-either:
- pattern: findUser($X)
- pattern: String $Y = $X; ... findUser($Y);
That is verbose and still misses cases like field assignments. With a typed metavariable the whole thing collapses to one line that says "match findUser when its argument is a String, and ignore it otherwise":
- pattern: findUser((String $X))
Semgrep does this because during matching it parses both your pattern and your code into the same generic AST, assigns names and types, and then compares structure plus type constraints together. It supports over 30 languages this way, with the engine written in OCaml and the command-line interface in Python. The type support has real limits worth knowing: at the time Semgrep documented the feature it handled declared local variable types and simple literal inference, but not function return types, explicit casts, array-index results, or cross-file type resolution in the open-source engine.
Writing your first rule: a five-step method
Semgrep's own rule-writing methodology is refreshingly unglamorous, and it is the part most teams get wrong by skipping. The five steps are: brainstorm what you want to find, pin it down to a concrete code example, write an initial rule against a test file, iterate against real repositories to kill false positives and false negatives, then wire it into continuous integration. A finished rule is a small YAML document. Here is one that flags the command-injection pattern from the opening:
rules:
- id: subprocess-shell-true
patterns:
- pattern: subprocess.call(..., shell=True, ...)
message: >
Calling subprocess with shell=True can lead to command injection.
Pass a list of arguments and shell=False instead.
languages: [python]
severity: ERROR
The ... operator is doing quiet but important work here. It matches zero or more arguments, so the rule catches the call whether shell=True is the first, last, or only keyword argument. The other operators you reach for constantly are pattern-not, which subtracts known-safe cases from a match, pattern-inside, which restricts matches to a particular context such as inside a specific function, and pattern-either, which accepts any of several alternatives.
The single most useful piece of advice in that methodology is to be more specific, not less. Beginners write broad patterns that match everything and then drown in false positives; the discipline is to start from a real snippet of bad code you can point at and generalize only as far as your test cases force you to. The methodology explicitly recommends running a crude text search like ripgrep alongside your rule to hunt for the cases your pattern fails to catch, because false negatives are invisible by nature.
The rules only you can write
This is the heart of the matter. Generic security rules are a commodity; the rules that repay the effort are the ones that encode knowledge specific to your organization. Semgrep's documentation frames the discovery process as a set of questions to ask about your own code: What issues showed up in recent post-mortems? What changes do reviewers keep requesting? What recurs across the codebase? What invariants should always, or never, be true?
The answers tend to fall into a few families. Some rules ban a dangerous API in favor of a blessed one, like flagging React's dangerouslySetInnerHTML, or recommending Google's re2 engine over Python's standard re to head off regular-expression denial-of-service attacks. Some enforce a convention, like requiring an authentication decorator on every Flask route so that no endpoint ships unprotected. Some encode a genuinely local invariant, and the canonical example is exactly the one that generic tools can never know: requiring that verify_transaction(t) is called before make_transaction(t) in a legacy payments API. And some are migration aids that find every call site using a deprecated internal helper so a large refactor can be tracked to completion.
That last family points at one of Semgrep's most practical features: autofix. Add a fix key to a rule and Semgrep can rewrite the flagged code, reusing the metavariables it captured. An import migration is almost trivial:
rules:
- id: use-new-http-client
pattern: from legacy.http import $FUNC
fix: from platform.http import $FUNC
message: legacy.http is deprecated; use platform.http.
languages: [python]
severity: WARNING
Run this with --autofix and it rewrites every matching import across hundreds of files; run it with --autofix --dryrun first to see the diff without touching disk. Since 2022 the autofix engine has itself been AST-aware, which means it can perform substitutions that a naive find-and-replace would mangle. For a large-scale rename, replaced import path, or deprecated call being retired across a monorepo, this turns a week of tedious, error-prone edits into a reviewable pull request generated in minutes.
The payoff at scale is visible in adopters like Lyft, whose security team valued how quickly an engineer could pick rule-writing back up after weeks away because the syntax stays close to the code itself. One caveat about that case study, since numbers get quoted loosely: the widely cited "95% noise reduction" Lyft reported came from Semgrep's Supply Chain reachability analysis for dependency vulnerabilities, not from custom code rules, and it was during Log4Shell that the reachability tooling let them find and patch every affected instance fast. The custom-rules story is a productivity story, not a headline percentage.
When a pattern is not enough: taint mode
Some bad patterns cannot be described by what a single line of code looks like, because the danger is in where the data came from. A call to render_template(user_input) is fine if user_input was validated three functions ago and catastrophic if it flowed straight from an HTTP request. Matching the call site alone gives you either false positives or false negatives, with no way to split the difference.
For this Semgrep offers taint mode, a form of dataflow analysis you switch on with mode: taint. Instead of one pattern you declare four kinds: sources where untrusted data enters, such as a request parameter; sinks where it must never arrive unguarded, such as an eval or a raw SQL query; sanitizers that render data safe, such as an escaping function; and propagators that carry taint forward through assignments and function calls. Semgrep then tracks whether tainted data can actually flow from a source to a sink without passing through a sanitizer, and only flags the paths that can.
The important limitation in the free engine is that this tracking is intra-procedural: it follows data within a single function by default. Cross-file and cross-function tracking, which is what you need to trace a value from an API handler down through several service layers, lives in the commercial Pro engine. Taint mode reached beta in 2021 and has been optimized aggressively since, with the company reporting in 2026 that it had cut taint-analysis time by roughly 75%, a reminder that dataflow analysis is expensive enough that its performance is a product feature in its own right.
The trust problem: the 2024 license split
Anyone deciding to build their security tooling on top of Semgrep in 2026 has to reckon with what happened at the end of 2024, because it changed what "open source Semgrep" even means. In December 2024 the company announced that Semgrep OSS would be renamed Semgrep Community Edition, and, more consequentially, that its maintained rules would fall under a new Semgrep Rules License v1.0 that "limits their use to internal, non-competing, and non-SaaS contexts." Vendors building competing or SaaS products on those rules were given until January 31, 2025 to stop. The engine itself stayed under the LGPL 2.1 license, so this was not a wholesale relicensing. But alongside the rules change, certain internal fields in the JSON and SARIF output, the machine-readable formats other tools consume, were moved into the logged-in commercial engine, which affected downstream integrations that depended on them. Depending on who you ask, this was either a reasonable line drawn between a thriving community and a viable business, or a bait-and-switch against the ecosystem that helped popularize the tool. The ecosystem answered with a fork. A consortium of security vendors including Aikido, Endor Labs, Jit, and Orca Security launched Opengrep, an LGPL-2.1 fork of Semgrep Community Edition explicitly created to keep the free feature set free and to preserve the ability to build on it commercially. For a team weighing custom rules, this is not academic. Rules you write yourself are yours, but if your strategy leans on Semgrep's maintained rule packs, or on embedding the tool in a product you sell, the license terms now decide whether that is allowed at all. It is a live reminder that "we'll just build it on the open-source version" is a decision with a maintenance and licensing tail, not a free lunch.
What to watch
The deeper bet behind custom rules is a claim about where software knowledge should live. Every codebase accumulates rules that are true but unwritten: this helper is deprecated, that call needs a guard, this pattern caused an outage once and must never return. Today most of that lives in human reviewers, which means it degrades every time someone leaves and reappears only when someone catches a mistake. Turning it into a rule is a way of promoting tribal knowledge into infrastructure, and the reason Semgrep matters is that it made the cost of doing so low enough to be worth it for ordinary teams, not just the ones with a dedicated program-analysis group. The open question is what happens as large language models get good at both writing that code and reviewing it. Semgrep has already leaned into AI-assisted triage, and it is easy to imagine a model proposing custom rules from your own post-mortems, or replacing static rules with something fuzzier. The counter-argument is that a deterministic rule that fails a build is auditable and repeatable in a way a probabilistic reviewer is not, and regulators and security teams tend to like things they can point at. The most interesting thing to watch is not whether AI replaces rules like these, but whether the two converge: models that discover the patterns, and a Semgrep-style engine that enforces them the same way every single time. The command-injection call from the opening will keep getting written either way. The only question is what is standing there to catch it.
Sources
- Wikipedia, Semgrep, 2026.
- Semgrep, Writing Semgrep rules: a methodology, 2020.
- Semgrep, Semgrep: a static analysis journey, 2021.
- Contrary Research, Semgrep Business Breakdown & Founding Story, 2024.
- Semgrep, Type-awareness in semantic grep, 2020.
- Semgrep Docs, Rule ideas / rule structure, 2026.
- Semgrep, Powerfully autofixing code with Semgrep's new AST-based approach, 2022.
- Semgrep Docs, Taint analysis overview, 2026.
- Semgrep, How we cut Semgrep's taint analysis time by 75%, 2026.
- Semgrep, Lyft's Software Supply Chain with Custom Security Rules, 2023.
- Semgrep, Important updates to Semgrep OSS, 2024.
- Socket, Opengrep Emerges as Open Source Alternative Amid Semgrep Licensing Controversy, 2025.
- FinTech Global, Semgrep bags $100m in Series D to elevate AI-driven code security, 2025.
- Abstract syntax tree, Wikipedia, 2026.