Skip to content
Conduit

mcp server

agentic-semgrep-rules

Semgrep rules for AI agent code: static analysis for LLM applications in Python, TypeScript and JavaScript. Finds model output reaching exec, shells, SQL, URLs, files and HTML, user input in system pr

basitalisandhu/agentic-semgrep-rulescommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 26, 2026

agentic-semgrep-rules: Semgrep rules for AI agent code

Semgrep rules for AI agent code: static analysis for LLM applications in Python, TypeScript and JavaScript that finds model output flowing into exec, shells, SQL, URLs, file paths and HTML (eval of model output, SSRF through tool URLs), user input written into system prompts, tools that let the model run anything, MCP server security checks (HTTP transports without authentication, servers bound to every interface), leaked provider keys, and unsafe model or config loading. For teams that ship LLM agents, MCP servers and tool-using assistants and want these mistakes caught in a pull request rather than found in an incident.

36 rules (23 Python, 13 TypeScript/JavaScript), every one with a tested fixture, CWE and OWASP LLM Top 10 (2025) mapping, and a message that says what is wrong and how to fix it.

pip install semgrep
semgrep --config https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml .

Output on an agent that runs model-written code (excerpt; the file is tests/python/code-execution/llm-output-to-exec-eval.py):

    agent.py
   ❯❯❱ python.code-execution.llm-output-to-exec-eval
          ❰❰ Blocking ❱❱
          Text returned by a language model flows into a call that executes it as Python. A prompt injection
          in any document, web page or tool result the model has read becomes arbitrary code execution in this
          process. Do not execute model output. [...]

           15┆ exec(code)
            ⋮┆----------------------------------------
           17┆ return eval(code.strip())
            ⋮┆----------------------------------------
           23┆ exec(resp.output_text)

On two large public agent codebases (the official MCP reference servers and the OpenAI Agents SDK, 807 files) the pack reports 10 findings, all of them real for the rule's definition; see docs/smoke-test.md.

When to use this

  • How do I catch eval of model output, or model output reaching a shell, in CI? The llm-output-to-* rules track text returned by the OpenAI, Anthropic, LiteLLM, Ollama, Google GenAI, Vercel AI and LangChain calls into exec, eval, subprocess, child_process and the other sinks in the tables below.
  • How do I find MCP servers exposed without auth? fastmcp-http-transport-without-auth, mcp-http-transport-without-auth and fastmcp-bind-all-interfaces report HTTP transports with no token verifier and servers listening on every interface.
  • Which Semgrep rules cover LangChain, the OpenAI Agents SDK, pydantic-ai, Semantic Kernel, FastMCP or the MCP TypeScript SDK? The permissions rules know those frameworks' tool decorators and the deserialization rules know LangChain's dangerous flags; the tables name each rule and the paragraph after them lists the recognised sources.
  • How do I stop an agent tool from turning into SSRF or path traversal? llm-output-to-fetch, llm-output-to-http-request and the *-tool-param-to-file-path rules report URLs and paths that the model chose.
  • How do I get agent security findings into GitHub code scanning? The composite action below runs the pack and uploads SARIF.

Why

Static analysers know that eval(user_input) is bad. They do not know that response.choices[0].message.content is attacker-controlled the moment the model has read a web page, a PDF or a tool result, or that a function decorated with @mcp.tool() receives arguments the model (and therefore a prompt injection) chose. This pack teaches Semgrep those sources and the agent-specific sinks and misconfigurations around them, with precision as the first design goal: a rule that is noisy on real code gets narrowed or removed.

What it finds

Model output reaches code or shell execution

RuleLanguageSeverityCWEOWASP LLM Top 10 (2025)Confidence
llm-output-to-child-processJS/TSERRORCWE-78LLM05, LLM06HIGH
llm-output-to-eval-functionJS/TSERRORCWE-95, CWE-94LLM05, LLM06HIGH
llm-output-to-exec-evalPythonERRORCWE-95, CWE-94LLM05, LLM06HIGH
llm-output-to-os-systemPythonERRORCWE-78LLM05, LLM06HIGH
llm-output-to-subprocessPythonERRORCWE-78LLM05, LLM06HIGH

Model output reaches injection sinks

RuleLanguageSeverityCWEOWASP LLM Top 10 (2025)Confidence
llm-output-to-fetchJS/TSWARNINGCWE-918LLM05, LLM06MEDIUM
llm-output-to-file-pathJS/TSWARNINGCWE-22LLM05, LLM06MEDIUM
llm-output-to-innerhtmlJS/TSWARNINGCWE-79LLM05MEDIUM
llm-output-to-sqlJS/TSERRORCWE-89LLM05, LLM06HIGH
llm-output-to-file-pathPythonWARNINGCWE-22LLM05, LLM06MEDIUM
llm-output-to-htmlPythonWARNINGCWE-79LLM05MEDIUM
llm-output-to-http-requestPythonWARNINGCWE-918LLM05, LLM06MEDIUM
llm-output-to-sqlPythonERRORCWE-89LLM05, LLM06HIGH

Prompt injection

RuleLanguageSeverityCWEOWASP LLM Top 10 (2025)Confidence
user-input-in-system-promptJS/TSWARNINGCWE-1427, CWE-74LLM01, LLM07MEDIUM
user-input-in-system-promptPythonWARNINGCWE-1427, CWE-74LLM01, LLM07MEDIUM

Over-broad tools and permissions

RuleLanguageSeverityCWEOWASP LLM Top 10 (2025)Confidence
mcp-tool-param-to-file-pathJS/TSWARNINGCWE-22LLM06, LLM05MEDIUM
mcp-tool-param-to-shellJS/TSERRORCWE-78LLM06, LLM05HIGH
agent-tool-param-to-file-pathPythonWARNINGCWE-22LLM06, LLM05MEDIUM
agent-tool-param-to-shellPythonERRORCWE-78LLM06, LLM05HIGH
langchain-allow-dangerous-codePythonWARNINGCWE-94LLM06HIGH
langchain-allow-dangerous-requestsPythonWARNINGCWE-918LLM06HIGH
langchain-dangerous-toolsPythonWARNINGCWE-250, CWE-94LLM06, LLM05HIGH

Exposed MCP servers

RuleLanguageSeverityCWEOWASP LLM Top 10 (2025)Confidence
mcp-http-transport-without-authJS/TSWARNINGCWE-306LLM06MEDIUM
fastmcp-bind-all-interfacesPythonWARNINGCWE-1327LLM06HIGH
fastmcp-http-transport-without-authPythonWARNINGCWE-306LLM06MEDIUM

Secrets

RuleLanguageSeverityCWEOWASP LLM Top 10 (2025)Confidence
api-key-in-command-line-argJS/TSWARNINGCWE-214LLM02MEDIUM
hardcoded-llm-api-keyJS/TSERRORCWE-798LLM02HIGH
llm-api-key-loggedJS/TSWARNINGCWE-532LLM02MEDIUM
api-key-in-command-line-argPythonWARNINGCWE-214LLM02MEDIUM
hardcoded-llm-api-keyPythonERRORCWE-798LLM02HIGH
llm-api-key-loggedPythonWARNINGCWE-532LLM02MEDIUM

Unsafe model and config loading

RuleLanguageSeverityCWEOWASP LLM Top 10 (2025)Confidence
langchain-allow-dangerous-deserializationPythonWARNINGCWE-502LLM03HIGH
pickle-load-model-filePythonWARNINGCWE-502LLM03, LLM04MEDIUM
torch-load-without-weights-onlyPythonWARNINGCWE-502LLM03, LLM04MEDIUM
transformers-trust-remote-codePythonWARNINGCWE-829, CWE-94LLM03HIGH
yaml-unsafe-loadPythonWARNINGCWE-502LLM03HIGH

Rule ids are shared between the Python and JavaScript directories when they detect the same problem; Semgrep prefixes ids with their path (rules.python.injection.llm-output-to-sql), so they stay distinct in output and SARIF. The single-file bundle agentic-semgrep-rules.yaml prefixes each id with its language and category (python.injection.llm-output-to-sql) for the same reason; python3 scripts/bundle.py regenerates it and CI fails when it is stale.

Sources recognised by the llm-output-to-* rules: the OpenAI (chat.completions.create, responses.create), Anthropic (messages.create), LiteLLM, Ollama and Google GenAI SDK calls, the Vercel AI SDK (generateText, streamText, generateObject), LangChain and LangGraph invoke/run/predict/stream on objects named like chains, agents, models or graphs, and response shapes that only model SDKs produce (.choices[0].message.content, .output_text, .content[0].text). Tool-parameter rules recognise FastMCP and the official MCP SDKs, LangChain @tool and DynamicStructuredTool, OpenAI Agents @function_tool, pydantic-ai @agent.tool_plain, Semantic Kernel @kernel_function and Vercel AI tool().

Install and run

Semgrep 1.179 or later.

pip install semgrep

# the single-file bundle, straight from GitHub (Semgrep accepts a URL only for a single file)
semgrep --config https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml .

# or the rules directory from a clone
git clone https://github.com/basitalisandhu/agentic-semgrep-rules
semgrep --config agentic-semgrep-rules/rules .

The rules are not in the Semgrep Registry yet (docs/registry.md explains the options); use the bundle URL or a clone. Each release also attaches the bundle and a tarball of the rules directory to vendor.

CI (any provider)

pip install semgrep==1.181.0
semgrep --config https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml \
  --metrics=off --error --severity ERROR .

--error makes the command exit non-zero when there are findings; drop --severity ERROR to also fail on WARNING-level rules.

GitHub Action

name: agent-security
on: [push, pull_request]
permissions:
  contents: read
  security-events: write
jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: basitalisandhu/agentic-semgrep-rules@v0   # or pin a release tag such as v0.2.0
        with:
          severity: ERROR        # fail the job at this level or above (INFO, WARNING, ERROR)
          upload-sarif: "true"   # publish all findings to the Security tab

Inputs: config (default: the bundled rules directory; a path, URL or registry reference), paths (default .), severity, upload-sarif, sarif-file, semgrep-version, extra-args. The action installs Semgrep, writes results.sarif, uploads it with github/codeql-action/upload-sarif@v4 and fails the job when findings at or above the threshold exist.

pre-commit

The hook runs the bundled rules on staged Python, TypeScript and JavaScript files. It needs semgrep on your PATH (pip install semgrep).

repos:
  - repo: https://github.com/basitalisandhu/agentic-semgrep-rules
    rev: v0.2.0
    hooks:
      - id: agentic-semgrep-rules

If you already run Semgrep's own hook, point it at the bundle instead:

repos:
  - repo: https://github.com/semgrep/pre-commit
    rev: v1.181.0
    hooks:
      - id: semgrep
        args: ["--config", "https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml", "--error", "--metrics=off"]

Example

@mcp.tool()
def run_command(command: str) -> str:
    """Run any shell command."""
    return subprocess.run(command, shell=True, capture_output=True, text=True).stdout
rules.python.permissions.agent-tool-param-to-shell
  Tool parameter 'command' is filled in by the model and reaches 'subprocess.run' as a shell
  command or executable. This is an "execute anything" tool: a prompt injection anywhere in the
  agent's context becomes OS command injection on the host running the tool. Replace the free-form
  command with a fixed executable and typed arguments, validate each argument against an allowlist,
  never use shell=True, run with least privilege and a timeout, and require human approval for
  anything with side effects.

Writing a rule

Every rule lives at rules/<language>/<category>/<rule-id>.yaml with a fixture at tests/<language>/<category>/<rule-id>.<ext> that marks true positives with # ruleid: <rule-id> (or // ruleid:) on the line above and negative cases with # ok: <rule-id>. Rules need metadata with category, subcategory, cwe, owasp, confidence, likelihood, impact, technology and references, and a message that names the problem and the fix. Then:

make test       # semgrep --test on the fixtures, plus an audit for findings on unannotated lines
make validate   # rule syntax and metadata lint, and a check that the single-file bundle is current
make bundle     # regenerate agentic-semgrep-rules.yaml after changing a rule
make smoke      # scan the fixtures and report per-rule counts (add SMOKE_PATHS=... for your own code)

docs/rule-writing.md explains the source and sink conventions, the Semgrep behaviours that bit us while building this pack (anchored metavariable-regex, exact: true on call sources, YAML quoting of object patterns) and the precision bar a rule has to clear. CONTRIBUTING.md has the PR checklist.

Precision

Every rule ships with positive and negative fixtures and the suite fails on any finding outside an annotated line. The pack was also run against a real Python codebase by the same author (agent-threat-model, 21 files) with zero findings; see docs/smoke-test.md. If a rule is noisy on your code, open an issue with the snippet: narrowing a rule is preferred over keeping a noisy one.

Frequently asked questions

Are there Semgrep rules for LLM applications and AI agents? Yes. This pack has 36 rules, 23 for Python and 13 for TypeScript and JavaScript, each with a tested fixture, a CWE, an OWASP LLM Top 10 (2025) mapping and a message that names the fix. They cover model output reaching exec, shells, SQL, HTTP requests, file paths and HTML; user input written into system prompts; tool parameters that reach a shell or a file path; LangChain dangerous-code and dangerous-requests flags; MCP servers without authentication or bound to all interfaces; hard-coded or logged provider keys; and unsafe model and config loading. Run it with semgrep --config https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml .

How do I catch eval of model output or unbounded tool permissions in CI? Pin Semgrep and run the bundle with --error: semgrep --config https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml --metrics=off --error --severity ERROR . exits non-zero when an ERROR-level rule fires (drop --severity ERROR to fail on warnings too). On GitHub, use the action basitalisandhu/agentic-semgrep-rules@v1, which installs Semgrep, writes results.sarif, uploads it to code scanning and fails the job at the severity you choose. A pre-commit hook for staged files is in the "Install and run" section above.

Which frameworks and SDKs do the rules understand? Sources: the OpenAI (chat.completions.create, responses.create), Anthropic (messages.create), LiteLLM, Ollama and Google GenAI SDK calls, the Vercel AI SDK (generateText, streamText, generateObject), LangChain and LangGraph invoke, run, predict and stream on chains, agents, models and graphs, and the response shapes only model SDKs produce (.choices[0].message.content, .output_text, .content[0].text). Tool-parameter rules recognise FastMCP and the official MCP SDKs, LangChain @tool and DynamicStructuredTool, OpenAI Agents @function_tool, pydantic-ai @agent.tool_plain, Semantic Kernel @kernel_function and Vercel AI tool(). Go and Java are on the roadmap.

How noisy are the rules on real code? Precision is the first design goal. Every rule ships with positive and negative fixtures, the suite fails on any finding outside an annotated line, and the pack was run against a real Python codebase by the same author (agent-threat-model, 21 files) with zero findings (docs/smoke-test.md). If a rule fires on your code wrongly, open an issue with the snippet: narrowing a rule is preferred over keeping a noisy one.

Can I use the rules commercially and contribute my own? Yes to both. The pack is MIT licensed, so it can be run in commercial CI and vendored into internal rule sets. A contribution is one rule at rules/<language>/<category>/<rule-id>.yaml with the required metadata block (category, subcategory, cwe, owasp, confidence, likelihood, impact, technology, references) and a fixture at tests/<language>/<category>/ that marks true positives with ruleid: and negatives with ok:; make test, make validate and make bundle must pass. docs/rule-writing.md explains the conventions and the Semgrep behaviours to watch for.

Roadmap

  • Semgrep Registry publication (see docs/registry.md) and an awesome-semgrep listing.
  • Go and Java coverage for the llm-output-to-* family.
  • Rules for agent memory and RAG stores (unsanitised retrieval text in prompts, pickle-backed vector stores).
  • Secrets in agent configuration files (.mcp.json, desktop-client MCP config files, .env committed next to agent code) via the generic language.
  • Autofix suggestions (fix:) for the flag-based rules.
  • Cross-file (interprocedural) variants when Semgrep's open-source engine supports them.

Related projects

More tools by the same author: https://github.com/basitalisandhu

  • ai-agent-incidents: open, structured dataset of publicly documented AI agent security incidents, mapped to OWASP and MITRE ATLAS, with a browsable site.
  • agent-threat-model: CLI that turns a YAML description of an agent system into a STRIDE + OWASP Agentic threat model, control checklist and Mermaid diagram.
  • agent-security-skills: Claude Code plugin and agentskills-compatible skill pack for agent security reviews: threat modelling, config audits, policy generation, incident lookup.

Licence

MIT, see LICENSE. Copyright 2026 Muhammad Basit Ali.

↑↓ navigate openesc close