examples

Two small, runnable examples. Copy the files, run the command, get the same result.

semantic matching

A rule is a pattern that looks like the code it finds. Opengrep matches it against the syntax tree, not the text, so import aliases, line breaks, argument order and values held in variables don't get in the way.

shell-true.yaml
rules:  - id: subprocess-shell-true    languages: [python]    severity: ERROR    message: shell=True runs the command through a shell (command injection risk)    pattern: subprocess.$FUNC(..., shell=True, ...)
deploy.py
import subprocessfrom subprocess import run as shdef deploy(branch):    subprocess.call("git checkout " + branch, shell=True)    sh(f"make release BRANCH={branch}", shell=True)    use_shell = True    subprocess.Popen(        ["make", "clean"],        cwd="/srv/app",        shell=use_shell,    )    subprocess.run(["make", "test"], shell=False)
output
$ opengrep scan -q --output-enclosing-context -f shell-true.yaml deploy.py
deploy.py

  error  subprocess-shell-true
  shell=True runs the command through a shell (command injection risk)

    in: function deploy
     6: subprocess.call("git checkout " + branch, shell=True)

    in: function deploy
     8: sh(f"make release BRANCH={branch}", shell=True)

    in: function deploy
    11: subprocess.Popen(
    12:     ["make", "clean"],
    13:     cwd="/srv/app",
    14:     shell=use_shell,
    15: )

cross-function taint analysis

A taint rule says where untrusted data comes from (sources), where it must never end up (sinks) and what makes it safe (sanitizers). With the rule option taint_intrafile, Opengrep follows the data through function calls, return values, lambdas and higher-order functions.

command-injection.yaml
rules:  - id: command-injection    mode: taint    languages: [javascript]    severity: ERROR    message: User input reaches a shell command    options:      taint_intrafile: true    pattern-sources:      - pattern: req.query      - pattern: req.body    pattern-sanitizers:      - pattern: shellescape(...)    pattern-sinks:      - pattern: exec(...)
server.js
const express = require("express");const { exec } = require("child_process");const shellescape = require("shell-escape");const app = express();function tarCommand(dir) {  return "tar czf /backups/latest.tgz " + dir;}function escape(dir) {  return shellescape([dir]);}function backup(req, res) {  exec(tarCommand(req.query.dir));}function backupAll(req, res) {  req.body.dirs.map(tarCommand)    .forEach((cmd) => exec(cmd));}function backupSafe(req, res) {  exec(tarCommand(escape(req.query.dir)));}app.get("/backup", backup);app.post("/backup-all", backupAll);app.get("/backup-safe", backupSafe);
output
$ opengrep scan -q --dataflow-traces \
    --output-enclosing-context -f command-injection.yaml server.js
server.js

  error  command-injection
  User input reaches a shell command

    in: function backup
    16: exec(tarCommand(req.query.dir));
        │
        ├─ source           16: exec(tarCommand(req.query.dir));
        ├─ through call     16: exec(tarCommand(req.query.dir));
        │  ├─ param             7: function tarCommand(dir) {
        │  └─ return            8: return "tar czf /backups/latest.tgz " + dir;
        └─ sink             16: exec(tarCommand(req.query.dir));

    in: function backupAll
    21: .forEach((cmd) => exec(cmd));
        │
        ├─ source           20: req.body.dirs.map(tarCommand)
        ├─ through call     21: .forEach((cmd) => exec(cmd));
        └─ sink             21: .forEach((cmd) => exec(cmd));

flags

The commands above use these flags: