ctxsentry
Scan a repository for prompt-injection payloads before you point an AI coding agent at it.
Formerly published as ctxguard (≤ 0.3.0); renamed because that name was taken on PyPI.
The gap this fills
Every current MCP / AI-agent security scanner points outward — it probes a remote MCP server, or audits the agent framework you are building. Almost nothing points inward at the untrusted repository you are about to open in Cursor, Claude Code, Copilot, or Windsurf.
That repo is attacker-influenced text. The moment your agent reads its
README.md, CLAUDE.md, .cursorrules, .mcp.json, issue templates, or even a
filename, any instructions hidden in that content enter the model's context.
Cloud Security Alliance research in 2026 documented "README instruction
injection" against coding agents with attack success rates of 41–84%, and MCP
tool-description poisoning (CVE-2025-54136) works the same way.
ctxsentry is a fast, dependency-free static scanner that flags that content so a
human reviews it first.
→ See docs/walkthrough.md for a worked example: five
planted attacks in a sample repo, and the scan that catches every one.
What it detects
| Family | Examples |
|---|---|
| Instruction override | ignore all previous instructions, you are now…, chat-template tokens (<system>, [INST], <|im_start|>), do not tell the user, <IMPORTANT> priority markers, jailbreak / guardrail-removal phrasing (developer mode, do anything now, ignore your guidelines) |
| Agent-directed imperatives | sentences addressed to "the AI / assistant / agent" that also name an action (run, curl, exfiltrate, install, push) |
| Data exfiltration & RCE | instructions to send .env / tokens / file contents somewhere, callback URLs (webhook.site, ngrok, oast, requestbin…), markdown images with query strings, curl … | sh, reverse-shell one-liners (bash -i >& /dev/tcp/…) |
| Data exfiltration & RCE (cont.) | DNS exfiltration (dig $(cat …).attacker), git remote add + git push to a non-GitHub URL, postinstall hooks that shell out |
| Context / prompt disclosure | repeat the text above verbatim, what is your system prompt, fake --- END OF DOCUMENT --- / system override: boundaries injected into retrieved content |
| Hidden Unicode | Unicode Tag characters U+E00xx (decoded and shown), bidirectional overrides (Trojan Source), zero-width runs, Private-Use-Area smuggling, Latin/Cyrillic/Greek homoglyph words |
| Deobfuscated rescan | strips zero-width / tag characters and folds homoglyphs, then re-runs every rule — catches Ignore all previous instructions and іgnоrе … evasions |
| Encoded payloads | base64 / hex blobs are decoded and the plaintext rescanned — a hidden ignore all previous instructions… inside a base64 string is surfaced with the decoded text |
| Layout smuggling | instruction text pushed off-screen by whitespace, display:none / color:#fff / font-size:0 spans, instruction-bearing HTML comments |
| MCP config poisoning | .mcp.json description / instructions fields carrying hidden directives or secret references; server launch commands that pipe a download into a shell |
| Filename injection | control / invisible / bidi characters in filenames, filenames that spell out an imperative |
Severity is boosted by context: the same string is medium in a source
comment but critical in .mcp.json or CLAUDE.md, because agents load those
files as authoritative instructions.
Install
pipx install ctxsentry # recommended
# or
pip install ctxsentry
From source:
git clone https://github.com/EivinDDu/ctxsentry
cd ctxsentry
pip install -e ".[dev]"
Usage
# scan the current repo
ctxsentry scan .
# scan a repo you just cloned, before opening it in your editor
ctxsentry scan ../suspicious-repo
# scan only what changed — fast pre-commit / PR gating
ctxsentry scan . --changed # vs HEAD (+ staged/unstaged/untracked)
ctxsentry scan . --changed origin/main # vs a base branch
# machine-readable output
ctxsentry scan . --format json -o ctxsentry.json
ctxsentry scan . --format sarif -o ctxsentry.sarif # upload to GitHub code scanning
# gate a pipeline
ctxsentry scan . --fail-on medium --git-history
# tune the noise
ctxsentry scan . --min-severity medium --min-confidence medium
# list every rule
ctxsentry rules
# score the detectors against the labelled corpus
ctxsentry bench
By default ctxsentry only reads files an agent treats as context (docs, rule
files, MCP config, *.md, *.txt, config formats). Add --all-text to sweep
source files too.
Exit codes
| Code | Meaning |
|---|---|
0 |
completed; nothing at or above --fail-on |
1 |
findings at or above --fail-on (default: high) |
2 |
usage / runtime error |
Pre-commit hook
# .pre-commit-config.yaml
- repo: https://github.com/EivinDDu/ctxsentry
rev: v0.3.0
hooks:
- id: ctxsentry # add: args: ["--changed"] for staged-only scans
GitHub Action
# .github/workflows/ctxsentry.yml
name: ctxsentry
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EivinDDu/ctxsentry@v0.3.0
with:
fail-on: high # optional (default: high)
# path: .
# args: --changed origin/main # PR-diff-only scan
Suppressing findings
Known-good matches (your own security docs, test fixtures) can be silenced two ways:
-
.ctxsentryignoreat the scan root — one glob per line, optionally scoped to specific rule ids:docs/threat-model.md # ignore every rule for this file examples/** # ignore a whole tree SECURITY.md:CG101,CG401 # ignore only these rules here -
Inline comment on the flagged line or the line above it:
<!-- ctxsentry: ignore CG401 -- example payload documented on purpose -->
How it works
path ─▶ file walk (skips vendored dirs, binaries, >1 MB)
─▶ decode (utf-8 / utf-16 / latin-1), keep invisible chars intact
─▶ classify context (mcp-config │ agent-instructions │ agent-skill │ docs │ generic)
─▶ run detectors:
• regex rule table (ctxsentry/rules.py)
• invisible-Unicode scanner (decodes U+E00xx tag runs)
• deobfuscated rescan (strip zero-width, fold homoglyphs, re-run rules)
• encoded-payload scanner (decodes base64 / hex, rescans plaintext)
• layout / smuggling scanner
• MCP JSON structure walk
• filename scanner
─▶ context-adjust severity ─▶ sort ─▶ render (text │ json │ sarif │ markdown)
No network calls. No LLM. Deterministic.
Benchmark
ctxsentry bench runs the detectors over a labelled corpus in benchmark/
(20 malicious fixtures across every family, 18 realistic benign ones) and reports
precision / recall / F1 / false-positive rate. CI fails the build on any
regression:
cases: 38 TP 20 FN 0 FP 0 TN 18
precision 1.000 recall 1.000 F1 1.000 FP-rate 0.000 rule-accuracy 1.000
The benign fixtures are the point — normal READMEs, a SECURITY.md, setup
docs that mention API keys, ### System Requirements headings — content that
looks adjacent to an attack but must not trip the scanner.
Limitations
- Static pattern matching: a novel paraphrase with no known markers can slip
through, and benign security documentation about prompt injection will
produce findings (tune with
--min-confidence). - Not a replacement for
mcp-scan(runtime MCP), secret scanners, or SAST — it covers the one thing they don't.
Development
pip install -e ".[dev]"
pytest
ctxsentry bench # detection score against benchmark/
Adding a detector? Add a fixture to benchmark/malicious/ (and a benign
counterpart if it could misfire), list it in benchmark/cases.jsonl, and keep
ctxsentry bench at 100% recall / 0 false positives.
Releasing is documented in docs/RELEASING.md — tag a
version and publish a GitHub Release; CI builds and uploads to PyPI via Trusted
Publishing.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ctxsentry-0.4.0.tar.gz.
File metadata
- Download URL: ctxsentry-0.4.0.tar.gz
- Upload date:
- Size: 44.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c34fed07ec0652b1d8d2d03c2b4181be6bed54f8d3c3ac8a9923f16448c38500
|
|
| MD5 |
1e8a64c7797f7a8824c8381b67831069
|
|
| BLAKE2b-256 |
46c5b5bd1218d19fb73b1d16329c7c891044adbdff1005ea7c3bc904afcae7c0
|
Provenance
The following attestation bundles were made for ctxsentry-0.4.0.tar.gz:
Publisher:
publish.yml on EivinDDu/ctxsentry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ctxsentry-0.4.0.tar.gz -
Subject digest:
c34fed07ec0652b1d8d2d03c2b4181be6bed54f8d3c3ac8a9923f16448c38500 - Sigstore transparency entry: 2755409939
- Sigstore integration time:
-
Permalink:
EivinDDu/ctxsentry@213b47675de66010b6f906e1fefe5e4c235f219e -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/EivinDDu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@213b47675de66010b6f906e1fefe5e4c235f219e -
Trigger Event:
release
-
Statement type:
File details
Details for the file ctxsentry-0.4.0-py3-none-any.whl.
File metadata
- Download URL: ctxsentry-0.4.0-py3-none-any.whl
- Upload date:
- Size: 32.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
28c7e30db14d5c6548dad07a06e9b5d3f1f707ff98abce0024f1b8bdac97057a
|
|
| MD5 |
5a1ebb49e00a39f68cfe197fed3b9059
|
|
| BLAKE2b-256 |
2985d5f40379bfc15e61a8d13701b036749d736ded420e704a239dc7138e088d
|
Provenance
The following attestation bundles were made for ctxsentry-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on EivinDDu/ctxsentry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ctxsentry-0.4.0-py3-none-any.whl -
Subject digest:
28c7e30db14d5c6548dad07a06e9b5d3f1f707ff98abce0024f1b8bdac97057a - Sigstore transparency entry: 2755409950
- Sigstore integration time:
-
Permalink:
EivinDDu/ctxsentry@213b47675de66010b6f906e1fefe5e4c235f219e -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/EivinDDu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@213b47675de66010b6f906e1fefe5e4c235f219e -
Trigger Event:
release
-
Statement type: