Semgrep SAST skill for Codex

A ruleset you trust, gated on new bugs. This skill runs Semgrep to find injection, SSRF, secrets and unsafe deserialization in source, then does the work that makes SAST stick: triaging the first noisy run, suppressing false positives precisely, writing tested custom rules for project-specific footguns, and gating CI on new findings via a baseline so a backlog never disables the check.

  • Security
  • $semgrep-sast
  • 117 lines
  • 3 files
  • ~1,150 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- semgrep-sast

Installs to ~/.agents/skills/semgrep-sast/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $semgrep-sast, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Runs Semgrep static analysis to find security bugs in source — injection, SSRF, hardcoded secrets, unsafe deserialization, weak crypto — tunes the ruleset to cut false positives, writes custom rules for project-specific patterns, and gates CI on new findings only. Use when asked to set up SAST, add Semgrep to CI, write a custom rule, or triage a noisy Semgrep report. Not for dependency CVEs (SCA).

Use it when

  • Setting up static analysis for security bugs in source
  • Adding Semgrep to CI without blocking on an existing backlog
  • Writing or tuning custom rules for project-specific patterns

Use something else when

How to check the result

Re-run after fixes to confirm findings clear with no new ones, and prove each custom rule with a --test fixture that fires on the bad pattern and stays silent on the good one.

The mistake it prevents

Shipping the untriaged first run as a report, or gating CI on the whole backlog so the check gets disabled within a day.

~/.agents/skills/semgrep-sast/SKILL.md

--- name: semgrep-sast description: Runs Semgrep static analysis to find security bugs in source — injection, SSRF, hardcoded secrets, unsafe deserialization, weak crypto — tunes the… license: MIT ---

Semgrep is valuable when its output is trusted — a small ruleset that fires on real bugs, custom rules for your own footguns, and a CI gate that blocks only new findings. A report no one reads because it is 90% noise is worse than no scan. Tune relentlessly.

Inspect first

Read AGENTS.md and detect languages. Semgrep ships curated rulesets; start narrow, not with everything. Semgrep downloads rules from the registry by default (needs network) or runs fully offline with --config pointing at a local rules dir — note which you use, since Codex’s sandbox may block the registry.

Run

# Curated security rules for auto-detected languages (registry)
semgrep --config=p/security-audit --sarif -o semgrep.sarif .
# Owasp-focused + secrets
semgrep --config=p/owasp-top-ten --config=p/secrets .
# Offline: only your own rules
semgrep --config=./.semgrep/ .

Useful rule packs: p/security-audit, p/owasp-top-ten, p/secrets, p/ci, and language packs (p/javascript, p/python, p/golang). p/default is broad and noisy — prefer targeted packs.

Triage and tune

The first run is always noisy. Work the list down:

  1. Confirm or dismiss each finding by reading the code — is untrusted input reaching the sink, or is the match a false positive (sanitized, constant, test code)?
  2. Suppress precisely, never globally:
    • Inline: // nosemgrep: <rule-id> on the line, with a comment saying why.
    • Path: add a paths: exclude in the rule or --exclude='tests/**' for whole trees.
    • Config: a .semgrepignore for generated/vendored code.
  3. Drop rules that do not fit your stack rather than suppressing every instance. Keep the ruleset small enough that a clean run means something.

Record a one-line reason for every suppression so the next person does not re-litigate it.

Custom rules — Semgrep’s real power

Generic rules miss project-specific footguns (a required auth decorator, a banned function, a tenant filter). Write a rule once and it enforces the pattern forever. Minimal rule:

# .semgrep/no-raw-sql.yml
rules:
  - id: no-raw-string-sql
    languages: [python]
    severity: ERROR
    message: Build queries with parameters, not string interpolation (SQLi risk).
    patterns:
      - pattern: $CURSOR.execute(f"...")
      - pattern-not: $CURSOR.execute("...", ...)

Semgrep patterns match structure, not text: ... is a wildcard, $X a metavariable. Develop rules fast with semgrep --config .semgrep/no-raw-sql.yml --test against a *.py/*.yml fixture pair. For the pattern operators (pattern-either, pattern-inside, metavariable-pattern, taint mode with pattern-sources/pattern-sinks) and worked examples, read references/custom-rules.md when a generic rule is too broad or too narrow.

CI gate — block new findings, not the backlog

A SAST gate that fails on 300 pre-existing findings gets disabled on day one. Gate on the diff:

# Fail only on findings introduced by this PR
semgrep ci --baseline-commit "$(git merge-base origin/main HEAD)"

semgrep ci understands baselines and SARIF upload. Set severity so only ERROR-level new findings block; WARNING can report without failing. Hand off workflow mechanics to github-actions-workflows and secret handling to secrets-in-ci.

Verification

  • Re-run after fixes: the finding is gone and no new ones appeared.
  • For each custom rule, prove it with a --test fixture: it fires on the bad pattern and stays silent on the good one. A rule with no test will rot.
  • The CI gate fails on a PR that introduces an ERROR and passes on a clean diff.

Output contract

## Semgrep SAST — <repo> (<date>)
Rulesets: <packs + custom>. Mode: <registry | offline>.

### Findings (confirmed)
| Rule | Severity | Location | Issue | Fix |
|------|----------|----------|-------|-----|
| sqli-interpolation | ERROR | db/user.py:88 | f-string SQL | parameterize |

### Suppressed (with reason)
- <rule@path> — false positive: input is a constant enum

### Custom rules added: <ids>. CI gate: baseline on main, fail on new ERROR.

Hard rules

  • Tune before you ship a report; an untriaged dump is noise, not analysis.
  • Suppress precisely with a reason; never disable a rule class to silence one false positive.
  • Every custom rule needs a passing --test fixture before it gates CI.
  • Semgrep finds source bugs, not vulnerable dependencies — hand off to dependency-scanning.

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Semgrep SAST"
  short_description: "A ruleset you trust, gated on new bugs"
  default_prompt: "$semgrep-sast Set up Semgrep security scanning for this TypeScript repo"
references/custom-rules.md100 lines, 3.2 KB
# Semgrep custom rules — operators and worked examples

Read this when a generic ruleset is too broad or too narrow and you need to express a
project-specific pattern. Semgrep matches code structure, not raw text, so these rules
survive reformatting and variable renames.

## Pattern primitives

| Syntax | Meaning |
|---|---|
| `...` | Ellipsis — matches any sequence (args, statements, members) |
| `$X`, `$FUNC` | Metavariable — binds to one node; the same name must match the same code |
| `"..."` | Any string literal |
| `$X.$METHOD(...)` | Any method call on `$X` |

## Rule operators

| Key | Use |
|---|---|
| `pattern` | Must match |
| `pattern-not` | Exclude matches of this (carve out the safe form) |
| `pattern-either` | OR across several patterns |
| `pattern-inside` | Only match when nested inside this context (e.g. inside a handler) |
| `pattern-not-inside` | Exclude matches inside this context (e.g. inside a test) |
| `metavariable-pattern` | Constrain a metavariable to match a sub-pattern |
| `metavariable-regex` | Constrain a metavariable by regex |
| `focus-metavariable` | Report only the span of this metavariable |

## Example 1 — require an auth decorator on routes

```yaml
rules:
  - id: route-missing-auth
    languages: [python]
    severity: ERROR
    message: Route handler is missing the @require_auth decorator.
    patterns:
      - pattern: |
          @app.$METHOD("...")
          def $HANDLER(...):
              ...
      - pattern-not: |
          @app.$METHOD("...")
          @require_auth
          def $HANDLER(...):
              ...
```

## Example 2 — ban a dangerous function

```yaml
rules:
  - id: no-child-process-exec
    languages: [javascript, typescript]
    severity: ERROR
    message: Use execFile with an argument array, not exec with a string (command injection).
    pattern-either:
      - pattern: require('child_process').exec(...)
      - pattern: cp.exec(...)
```

## Example 3 — taint mode (source → sink)

Taint mode tracks data flow from an untrusted source to a dangerous sink and reports only
when they connect, which cuts false positives sharply.

```yaml
rules:
  - id: taint-sql-injection
    languages: [python]
    severity: ERROR
    message: Untrusted request data flows into a raw SQL execute().
    mode: taint
    pattern-sources:
      - pattern: request.$ANY
    pattern-sinks:
      - pattern: $CURSOR.execute($QUERY)
    pattern-sanitizers:
      - pattern: sanitize(...)
```

## Testing a rule

Put a fixture next to the rule: `no-raw-sql.yml` + `no-raw-sql.py`. Mark expected hits with
a `# ruleid: <id>` comment on the offending line and `# ok: <id>` on a safe line, then:

```bash
semgrep --test --config .semgrep/
```

The test passes only when the rule fires exactly on the `ruleid` lines and stays silent on
the `ok` lines. A rule without a passing test should not gate CI — it will drift into noise
or silence.

## Keeping the set healthy

- One rule, one footgun. Narrow rules are easier to trust and tune.
- Prefer `pattern-not` to carve out the safe form over suppressing every instance later.
- Review custom rules when the framework or its API changes; a rule matching an old API
  silently stops protecting you.

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc