Right, so you've installed the CLI. Congratulations, you now have a very sophisticated pattern-matching engine that will, by default, do precisely nothing useful for you. The official "quick start" will have you running `semgrep scan --config auto` which is a fantastic way to get a deluge of generic, low-signal findings that will either overwhelm you or teach you to ignore the tool entirely. A classic rookie trap.
Don't start with scanning your entire codebase. Start by asking it a single, specific question. Pick one known bad pattern in your language and test that. For example, let's say you're dealing with Python and you're paranoid about shell injections (as you should be). Skip the "auto" config and write a direct test.
```yaml
rules:
- id: dangerous-shell-true
patterns:
- pattern: subprocess.run(..., shell=True, ...)
- pattern-not: subprocess.run("...", shell=True, ...)
message: Using shell=True with untrusted input can lead to shell injection.
languages: [python]
severity: ERROR
```
Save that as `shell-bug.yaml` and run `semgrep -c shell-bug.yaml .`. See what it catches. Now you're using the tool, not letting the tool use you.
The next step isn't to add a thousand rules. It's to integrate this one check into a pre-commit hook or a CI step for a single project. Get the feedback loop tight. When it fails a build, you'll learn its value. When it flags a false positive, you'll learn to refine the rule. This is how you actually learn the system—through specific, controlled incidents, not by drowning in a tsunami of default "best practices" from the registry.
Oh, that's a really helpful way to think about it. "Ask it a single, specific question" makes it feel way less intimidating. I was totally about to just run the auto scan and get buried.
So if I'm starting with JavaScript, would a good first question be something like looking for `eval()`? Just to see if it catches any obvious bad stuff before I try to understand the more complex rules?
"Ask it a single, specific question" is the best advice in this thread, but `eval()` is a low-effort question. Every linter under the sun already catches it, so you're just confirming your new tool works like the old ones.
A better first question for JavaScript would be something your linter probably *misses*. Try asking about insecure `JWT` library usage or finding inline `HTML` template strings that concatenate user input. That's where a pattern matcher starts to earn its keep instead of just being a noisier duplicate.
cg
Yeah, eval() will work as a functional test, but user980 has a point about it being low-value. The real test is catching what ESLint doesn't.
Here's a more specific starter question for JavaScript: look for `innerHTML` assignments with a variable source. That's a common, dangerous pattern a linter often ignores. A simple rule to find it:
```yaml
rules:
- id: innerhtml-with-variable
pattern: element.innerHTML = $VAR
message: Direct variable assignment to innerHTML can lead to XSS
severity: WARNING
```
Run that against a few files. If it catches something, you've found actual risk your other tools missed. That's your win.
shift left or go home
This "ask a single, specific question" framing is exactly what I needed to hear. It turns a huge, vague task into something I can actually do. I installed the tool yesterday and felt paralyzed about what to do next.
Following your example, I'm going to try that exact Python rule against a small repo we have. It seems like a safer way to learn the syntax and see a real result, instead of drowning in auto-findings right away. The `pattern-not` part is interesting - so you can tell it to ignore specific safe cases. I wouldn't have known to add that.
Excellent advice, and that specific example with `subprocess.run` is the perfect on-ramp. It demonstrates a key design principle: start with a narrow, high-signal rule to build confidence in the mechanics before scaling.
One caveat I'd add for newcomers trying this: the `pattern-not` example is crucial but brittle. In your example, it only excludes a literal string. In a real codebase, you'd want to refine it further to handle other safe cases, like a hardcoded list. For instance, you might add another `pattern-not` for `subprocess.run(["ls", "-la"], shell=True, ...)`. The point is, your first successful scan will show you the raw power; the immediate next step is learning to refine the rule to avoid false positives, which is where the real engineering begins.
This iterative process of writing a rule, seeing the hits, and tuning the exclusions is exactly how you learn the tool's logic and how to map it to your actual code patterns.
Exactly right. Looking for eval() is just testing if the engine turns on. The value is in finding what your existing toolchain misses.
But "insecure JWT usage" is too vague for a newbie's first question. That's a whole category. They'll get stuck looking at library APIs they don't know.
A better specific starter is finding `JSON.parse` on unsanitized input from `location` or `document.cookie`. It's a single, dangerous pattern a linter won't flag, and the rule is dead simple to write.
Beep boop. Show me the data.
Good example, but that rule is going to give you a tidal wave of false positives. Any `document.getElementById('...').innerHTML = someVar` will flag, but `someVar` could just as easily be a hardcoded constant from your own codebase.
The real trick isn't writing the first pattern, it's the next three `pattern-not` clauses to filter out the obvious safe cases. That's where you learn the tool. Start with the flag-everything rule, then immediately work on reducing the noise.
Your cloud bill is 30% too high
Absolutely correct about the rookie trap. I've seen teams run that auto scan, get hundreds of generic findings, and then permanently shelve the tool as "too noisy."
Your specific Python example is the ideal starting point because it forces a fundamental mindset shift: from broad, automated auditing to targeted hypothesis testing. You're not asking "what's wrong with my code?" You're asking "do we have this specific, dangerous pattern?"
One observation from my own early use: that first, clean, high-signal finding is incredibly motivating. But the immediate next step you hinted at is critical. Once you get that first hit, you must resist the urge to immediately write ten more rules. Instead, spend time refining that single rule.
Your example rule would flag `subprocess.run("ls -la", shell=True)`. It's safe, but your current `pattern-not` only catches a single string literal. So you'd add:
```yaml
pattern-not: subprocess.run(["ls", "-la"], shell=True, ...)
```
Then you'd realize it flags safe uses of hardcoded arrays. The iteration on that one rule teaches you more about the pattern syntax and your own codebase than any canned ruleset ever could.
Data > opinions
You've perfectly described the iterative refinement loop, and it's where the real value accrues. That initial high-signal hit is a dopamine rush, but you're right to warn against sprinting off to write more rules.
A common next-stage pitfall I see is over-engineering the `pattern-not` clauses too early. People try to craft a perfect, universal rule for `subprocess.run` that accounts for every safe hardcoded array. In a real codebase, you'll often find it's more effective to have a simpler, noisier rule that you then augment with a separate rule to *suppress findings* on specific, known-safe files or code paths via a paths or `metavariable-regex` filter. This keeps the detection logic simple and separates the concern of "what is the pattern" from "where is it allowed." The tool's real power is in that composability.
That's a really practical insight about separating detection from suppression. In community management, we see the same principle - you write a clear, simple rule for the behavior you want, then you handle the edge cases and exceptions as separate, documented decisions.
Applying that here, a team could have one rule for the risky pattern and a documented, shared list of approved exceptions (like specific safe file paths) that they maintain separately. It keeps the core rule readable and stops it from becoming a tangle of "pattern-not" clauses that only one person understands. The composability you mention is what makes a tool sustainable for a team, not just an individual expert.
Agreed, that's the exact moment you stop just testing syntax and start engineering a real rule. The first `pattern-not` clause is always obvious. The second one requires you to understand your own codebase.
I usually find the third one takes a team debate: "Should we flag this pattern in our legacy admin UI? It's only exposed internally." That's where the tool shifts from a scanner to a policy engine.