OMAR
Field NotesCV
Writing a Semgrep Rule for Your Own Codebase
← All Notes
Security27 January 2026 · 6 min read

Writing a Semgrep Rule for Your Own Codebase

The generic ruleset finds generic bugs. The rule you write for your own mistake finds it forever.

The generic ruleset finds generic bugs, and after the first run you have fixed most of what it will ever tell you. The rule you write for your own mistake finds that mistake forever, in every project, including the one you have not started yet.

That is the part of static analysis that compounds.

Why the syntax matters

Semgrep rules look like the code they match, which means you can write one without learning an AST query language:

rules:
  - id: client-supplied-user-id
    pattern: |
      $DB.$TABLE.findMany({ where: { userId: $REQ.query.$X } })
    message: >
      Filtering by a client-supplied user id. Use the session identity
      (req.user.id) — this endpoint returns other users' records.
    languages: [javascript, typescript]
    severity: ERROR

Ten minutes, and the most common serious bug I find in small-team codebases can never reach main again.

The rules worth writing first

Look at your own bug history. The three or four mistakes you have actually made are worth more than a hundred rules for mistakes you have not.

Mine, roughly:

  • Any query filtered by an identifier taken from the request instead of the session
  • v-html or dangerouslySetInnerHTML with a non-constant argument
  • A fetch to a third-party API from client-side code with a key in scope
  • Math.random() anywhere near a token, code, or identifier
  • A new route file with no schema validation on the body

Each one is a rule of a few lines and each one has caught something real.

Running it usefully

In CI on the diff, not the whole repository. Scanning everything on every pull request produces a wall of pre-existing findings that people learn to ignore, which is how static analysis dies in a team.

Diff-scanning means the only findings you see are the ones you just introduced, and that is a signal people act on.

Locally, a pre-commit hook on changed files catches things before they are even pushed.

Managing false positives

They will happen. Handle them explicitly with an inline suppression that includes a reason:

// nosemgrep: client-supplied-user-id -- admin route, authorised above

A suppression with a justification is documentation. A blanket disabled rule is a rule you have deleted.

Resources

SecurityToolingsemgrep

Need this built properly?

I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.

Keep Reading