{"id":"d4f4aba7-3e5f-4881-9235-36f3f0163ab4","slug":"agentic-semgrep-rules","kind":"mcp","name":"agentic-semgrep-rules","summary":"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","owner":"basitalisandhu","repoUrl":"https://github.com/basitalisandhu/agentic-semgrep-rules","license":"MIT","licenseTier":"permissive","trust":"community","stars":1,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-10-04T07:05:17.000Z","addedAt":"2026-10-21T09:49:02.109Z","logoUrl":"https://avatars.githubusercontent.com/u/62793038?u=b072eeeaea556a8aaa9df1f7e2ebf98cc2e68795&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["developer-tools","databases","browser-web"],"tags":["agent-security","agentic-ai","ai-agents","ai-security","code-scanning","devsecops","github-actions","hacktoberfest"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-26T07:33:20.944Z","sample":false,"url":"https://conduits.dev/tools/agentic-semgrep-rules","language":"Python","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# agentic-semgrep-rules: Semgrep rules for AI agent code\n\nSemgrep 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.\n\n36 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.\n\n```sh\npip install semgrep\nsemgrep --config https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml .\n```\n\nOutput on an agent that runs model-written code (excerpt; the file is [`tests/python/code-execution/llm-output-to-exec-eval.py`](tests/python/code-execution/llm-output-to-exec-eval.py)):\n\n```text\n    agent.py\n   ❯❯❱ python.code-execution.llm-output-to-exec-eval\n          ❰❰ Blocking ❱❱\n          Text returned by a language model flows into a call that executes it as Python. A prompt injection\n          in any document, web page or tool result the model has read becomes arbitrary code execution in this\n          process. Do not execute model output. [...]\n\n           15┆ exec(code)\n            ⋮┆----------------------------------------\n           17┆ return eval(code.strip())\n            ⋮┆----------------------------------------\n           23┆ exec(resp.output_text)\n```\n\nOn 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](docs/smoke-test.md).\n\n## When to use this\n\n- **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.\n- **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.\n- **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.\n- **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.\n- **How do I get agent security findings into GitHub code scanning?** The composite action below runs the pack and uploads SARIF.\n\n## Why\n\nStatic 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.\n\n## What it finds\n\n**Model output reaches code or shell execution**\n\n| Rule | Language | Severity | CWE | OWASP LLM Top 10 (2025) | Confidence |\n|---|---|---|---|---|---|\n| [`llm-output-to-child-process`](rules/javascript/code-execution/llm-output-to-child-process.yaml) | JS/TS | ERROR | CWE-78 | LLM05, LLM06 | HIGH |\n| [`llm-output-to-eval-function`](rules/javascript/code-execution/llm-output-to-eval-function.yaml) | JS/TS | ERROR | CWE-95, CWE-94 | LLM05, LLM06 | HIGH |\n| [`llm-output-to-exec-eval`](rules/python/code-execution/llm-output-to-exec-eval.yaml) | Python | ERROR | CWE-95, CWE-94 | LLM05, LLM06 | HIGH |\n| [`llm-output-to-os-system`](rules/python/code-execution/llm-output-to-os-system.yaml) | Python | ERROR | CWE-78 | LLM05, LLM06 | HIGH |\n| [`llm-output-to-subprocess`](rules/python/code-execution/llm-output-to-subprocess.yaml) | Python | ERROR | CWE-78 | LLM05, LLM06 | HIGH |\n\n**Model output reaches injection sinks**\n\n| Rule | Language | Severity | CWE | OWASP LLM Top 10 (2025) | Confidence |\n|---|---|---|---|---|---|\n| [`llm-output-to-fetch`](rules/javascript/injection/llm-output-to-fetch.yaml) | JS/TS | WARNING | CWE-918 | LLM05, LLM06 | MEDIUM |\n| [`llm-output-to-file-path`](rules/javascript/injection/llm-output-to-file-path.yaml) | JS/TS | WARNING | CWE-22 | LLM05, LLM06 | MEDIUM |\n| [`llm-output-to-innerhtml`](rules/javascript/injection/llm-output-to-innerhtml.yaml) | JS/TS | WARNING | CWE-79 | LLM05 | MEDIUM |\n| [`llm-output-to-sql`](rules/javascript/injection/llm-output-to-sql.yaml) | JS/TS | ERROR | CWE-89 | LLM05, LLM06 | HIGH |\n| [`llm-output-to-file-path`](rules/python/injection/llm-output-to-file-path.yaml) | Python | WARNING | CWE-22 | LLM05, LLM06 | MEDIUM |\n| [`llm-output-to-html`](rules/python/injection/llm-output-to-html.yaml) | Python | WARNING | CWE-79 | LLM05 | MEDIUM |\n| [`llm-output-to-http-request`](rules/python/injection/llm-output-to-http-request.yaml) | Python | WARNING | CWE-918 | LLM05, LLM06 | MEDIUM |\n| [`llm-output-to-sql`](rules/python/injection/llm-output-to-sql.yaml) | Python | ERROR | CWE-89 | LLM05, LLM06 | HIGH |\n\n**Prompt injection**\n\n| Rule | Language | Severity | CWE | OWASP LLM Top 10 (2025) | Confidence |\n|---|---|---|---|---|---|\n| [`user-input-in-system-prompt`](rules/javascript/prompt-injection/user-input-in-system-prompt.yaml) | JS/TS | WARNING | CWE-1427, CWE-74 | LLM01, LLM07 | MEDIUM |\n| [`user-input-in-system-prompt`](rules/python/prompt-injection/user-input-in-system-prompt.yaml) | Python | WARNING | CWE-1427, CWE-74 | LLM01, LLM07 | MEDIUM |\n\n**Over-broad tools and permissions**\n\n| Rule | Language | Severity | CWE | OWASP LLM Top 10 (2025) | Confidence |\n|---|---|---|---|---|---|\n| [`mcp-tool-param-to-file-path`](rules/javascript/permissions/mcp-tool-param-to-file-path.yaml) | JS/TS | WARNING | CWE-22 | LLM06, LLM05 | MEDIUM |\n| [`mcp-tool-param-to-shell`](rules/javascript/permissions/mcp-tool-param-to-shell.yaml) | JS/TS | ERROR | CWE-78 | LLM06, LLM05 | HIGH |\n| [`agent-tool-param-to-file-path`](rules/python/permissions/agent-tool-param-to-file-path.yaml) | Python | WARNING | CWE-22 | LLM06, LLM05 | MEDIUM |\n| [`agent-tool-param-to-shell`](rules/python/permissions/agent-tool-param-to-shell.yaml) | Python | ERROR | CWE-78 | LLM06, LLM05 | HIGH |\n| [`langchain-allow-dangerous-code`](rules/python/permissions/langchain-allow-dangerous-code.yaml) | Python | WARNING | CWE-94 | LLM06 | HIGH |\n| [`langchain-allow-dangerous-requests`](rules/python/permissions/langchain-allow-dangerous-requests.yaml) | Python | WARNING | CWE-918 | LLM06 | HIGH |\n| [`langchain-dangerous-tools`](rules/python/permissions/langchain-dangerous-tools.yaml) | Python | WARNING | CWE-250, CWE-94 | LLM06, LLM05 | HIGH |\n\n**Exposed MCP servers**\n\n| Rule | Language | Severity | CWE | OWASP LLM Top 10 (2025) | Confidence |\n|---|---|---|---|---|---|\n| [`mcp-http-transport-without-auth`](rules/javascript/mcp/mcp-http-transport-without-auth.yaml) | JS/TS | WARNING | CWE-306 | LLM06 | MEDIUM |\n| [`fastmcp-bind-all-interfaces`](rules/python/mcp/fastmcp-bind-all-interfaces.yaml) | Python | WARNING | CWE-1327 | LLM06 | HIGH |\n| [`fastmcp-http-transport-without-auth`](rules/python/mcp/fastmcp-http-transport-without-auth.yaml) | Python | WARNING | CWE-306 | LLM06 | MEDIUM |\n\n**Secrets**\n\n| Rule | Language | Severity | CWE | OWASP LLM Top 10 (2025) | Confidence |\n|---|---|---|---|---|---|\n| [`api-key-in-command-line-arg`](rules/javascript/secrets/api-key-in-command-line-arg.yaml) | JS/TS | WARNING | CWE-214 | LLM02 | MEDIUM |\n| [`hardcoded-llm-api-key`](rules/javascript/secrets/hardcoded-llm-api-key.yaml) | JS/TS | ERROR | CWE-798 | LLM02 | HIGH |\n| [`llm-api-key-logged`](rules/javascript/secrets/llm-api-key-logged.yaml) | JS/TS | WARNING | CWE-532 | LLM02 | MEDIUM |\n| [`api-key-in-command-line-arg`](rules/python/secrets/api-key-in-command-line-arg.yaml) | Python | WARNING | CWE-214 | LLM02 | MEDIUM |\n| [`hardcoded-llm-api-key`](rules/python/secrets/hardcoded-llm-api-key.yaml) | Python | ERROR | CWE-798 | LLM02 | HIGH |\n| [`llm-api-key-logged`](rules/python/secrets/llm-api-key-logged.yaml) | Python | WARNING | CWE-532 | LLM02 | MEDIUM |\n\n**Unsafe model and config loading**\n\n| Rule | Language | Severity | CWE | OWASP LLM Top 10 (2025) | Confidence |\n|---|---|---|---|---|---|\n| [`langchain-allow-dangerous-deserialization`](rules/python/deserialization/langchain-allow-dangerous-deserialization.yaml) | Python | WARNING | CWE-502 | LLM03 | HIGH |\n| [`pickle-load-model-file`](rules/python/deserialization/pickle-load-model-file.yaml) | Python | WARNING | CWE-502 | LLM03, LLM04 | MEDIUM |\n| [`torch-load-without-weights-only`](rules/python/deserialization/torch-load-without-weights-only.yaml) | Python | WARNING | CWE-502 | LLM03, LLM04 | MEDIUM |\n| [`transformers-trust-remote-code`](rules/python/deserialization/transformers-trust-remote-code.yaml) | Python | WARNING | CWE-829, CWE-94 | LLM03 | HIGH |\n| [`yaml-unsafe-load`](rules/python/deserialization/yaml-unsafe-load.yaml) | Python | WARNING | CWE-502 | LLM03 | HIGH |\n\nRule 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`](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.\n\nSources 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()`.\n\n## Install and run\n\nSemgrep 1.179 or later.\n\n```sh\npip install semgrep\n\n# the single-file bundle, straight from GitHub (Semgrep accepts a URL only for a single file)\nsemgrep --config https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml .\n\n# or the rules directory from a clone\ngit clone https://github.com/basitalisandhu/agentic-semgrep-rules\nsemgrep --config agentic-semgrep-rules/rules .\n```\n\nThe rules are not in the Semgrep Registry yet ([docs/registry.md](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.\n\n### CI (any provider)\n\n```sh\npip install semgrep==1.181.0\nsemgrep --config https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml \\\n  --metrics=off --error --severity ERROR .\n```\n\n`--error` makes the command exit non-zero when there are findings; drop `--severity ERROR` to also fail on WARNING-level rules.\n\n### GitHub Action\n\n```yaml\nname: agent-security\non: [push, pull_request]\npermissions:\n  contents: read\n  security-events: write\njobs:\n  semgrep:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v7\n      - uses: basitalisandhu/agentic-semgrep-rules@v0   # or pin a release tag such as v0.2.0\n        with:\n          severity: ERROR        # fail the job at this level or above (INFO, WARNING, ERROR)\n          upload-sarif: \"true\"   # publish all findings to the Security tab\n```\n\nInputs: `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.\n\n### pre-commit\n\nThe hook runs the bundled rules on staged Python, TypeScript and JavaScript files. It needs `semgrep` on your PATH (`pip install semgrep`).\n\n```yaml\nrepos:\n  - repo: https://github.com/basitalisandhu/agentic-semgrep-rules\n    rev: v0.2.0\n    hooks:\n      - id: agentic-semgrep-rules\n```\n\nIf you already run Semgrep's own hook, point it at the bundle instead:\n\n```yaml\nrepos:\n  - repo: https://github.com/semgrep/pre-commit\n    rev: v1.181.0\n    hooks:\n      - id: semgrep\n        args: [\"--config\", \"https://raw.githubusercontent.com/basitalisandhu/agentic-semgrep-rules/main/agentic-semgrep-rules.yaml\", \"--error\", \"--metrics=off\"]\n```\n\n## Example\n\n```python\n@mcp.tool()\ndef run_command(command: str) -> str:\n    \"\"\"Run any shell command.\"\"\"\n    return subprocess.run(command, shell=True, capture_output=True, text=True).stdout\n```\n\n```text\nrules.python.permissions.agent-tool-param-to-shell\n  Tool parameter 'command' is filled in by the model and reaches 'subprocess.run' as a shell\n  command or executable. This is an \"execute anything\" tool: a prompt injection anywhere in the\n  agent's context becomes OS command injection on the host running the tool. Replace the free-form\n  command with a fixed executable and typed arguments, validate each argument against an allowlist,\n  never use shell=True, run with least privilege and a timeout, and require human approval for\n  anything with side effects.\n```\n\n## Writing a rule\n\nEvery 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:\n\n```sh\nmake test       # semgrep --test on the fixtures, plus an audit for findings on unannotated lines\nmake validate   # rule syntax and metadata lint, and a check that the single-file bundle is current\nmake bundle     # regenerate agentic-semgrep-rules.yaml after changing a rule\nmake smoke      # scan the fixtures and report per-rule counts (add SMOKE_PATHS=... for your own code)\n```\n\n[docs/rule-writing.md](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](CONTRIBUTING.md) has the PR checklist.\n\n## Precision\n\nEvery 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](https://github.com/basitalisandhu/agent-threat-model), 21 files) with zero findings; see [docs/smoke-test.md](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.\n\n## Frequently asked questions\n\n**Are there Semgrep rules for LLM applications and AI agents?**\nYes. 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 .`\n\n**How do I catch eval of model output or unbounded tool permissions in CI?**\nPin 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.\n\n**Which frameworks and SDKs do the rules understand?**\nSources: 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.\n\n**How noisy are the rules on real code?**\nPrecision 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](https://github.com/basitalisandhu/agent-threat-model), 21 files) with zero findings ([docs/smoke-test.md](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.\n\n**Can I use the rules commercially and contribute my own?**\nYes 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](docs/rule-writing.md) explains the conventions and the Semgrep behaviours to watch for.\n\n## Roadmap\n\n- Semgrep Registry publication (see [docs/registry.md](docs/registry.md)) and an `awesome-semgrep` listing.\n- Go and Java coverage for the `llm-output-to-*` family.\n- Rules for agent memory and RAG stores (unsanitised retrieval text in prompts, pickle-backed vector stores).\n- Secrets in agent configuration files (`.mcp.json`, desktop-client MCP config files, `.env` committed next to agent code) via the generic language.\n- Autofix suggestions (`fix:`) for the flag-based rules.\n- Cross-file (interprocedural) variants when Semgrep's open-source engine supports them.\n\n## Related projects\n\nMore tools by the same author: https://github.com/basitalisandhu\n\n- [ai-agent-incidents](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](https://basitalisandhu.github.io/ai-agent-incidents/).\n- [agent-threat-model](https://github.com/basitalisandhu/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.\n- [agent-security-skills](https://github.com/basitalisandhu/agent-security-skills): Claude Code plugin and agentskills-compatible skill pack for agent security reviews: threat modelling, config audits, policy generation, incident lookup.\n\n## Licence\n\nMIT, see [LICENSE](LICENSE). Copyright 2026 Muhammad Basit Ali.\n"}