ctxsentry 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. ctxsentry-0.4.0/.gitignore +27 -0
  2. ctxsentry-0.4.0/CHANGELOG.md +100 -0
  3. ctxsentry-0.4.0/LICENSE +21 -0
  4. ctxsentry-0.4.0/PKG-INFO +256 -0
  5. ctxsentry-0.4.0/README.md +221 -0
  6. ctxsentry-0.4.0/benchmark/benign/api_docs.md +11 -0
  7. ctxsentry-0.4.0/benchmark/benign/architecture.md +3 -0
  8. ctxsentry-0.4.0/benchmark/benign/changelog.md +7 -0
  9. ctxsentry-0.4.0/benchmark/benign/clean_mcp/.mcp.json +22 -0
  10. ctxsentry-0.4.0/benchmark/benign/code_of_conduct.md +3 -0
  11. ctxsentry-0.4.0/benchmark/benign/contributing.md +8 -0
  12. ctxsentry-0.4.0/benchmark/benign/curl_download.md +3 -0
  13. ctxsentry-0.4.0/benchmark/benign/env_setup.md +3 -0
  14. ctxsentry-0.4.0/benchmark/benign/faq.md +7 -0
  15. ctxsentry-0.4.0/benchmark/benign/install_guide.md +11 -0
  16. ctxsentry-0.4.0/benchmark/benign/makefile_notes.md +3 -0
  17. ctxsentry-0.4.0/benchmark/benign/normal_readme.md +17 -0
  18. ctxsentry-0.4.0/benchmark/benign/prompt_injection_explainer.md +3 -0
  19. ctxsentry-0.4.0/benchmark/benign/prompt_notes.md +3 -0
  20. ctxsentry-0.4.0/benchmark/benign/release_process.md +6 -0
  21. ctxsentry-0.4.0/benchmark/benign/security_policy.md +7 -0
  22. ctxsentry-0.4.0/benchmark/benign/system_requirements.md +9 -0
  23. ctxsentry-0.4.0/benchmark/benign/troubleshooting.md +5 -0
  24. ctxsentry-0.4.0/benchmark/cases.jsonl +38 -0
  25. ctxsentry-0.4.0/benchmark/malicious/base64_payload.md +3 -0
  26. ctxsentry-0.4.0/benchmark/malicious/bidi_trojan_source.md +3 -0
  27. ctxsentry-0.4.0/benchmark/malicious/claude_md_standing_exfil.md +3 -0
  28. ctxsentry-0.4.0/benchmark/malicious/dns_exfil.md +3 -0
  29. ctxsentry-0.4.0/benchmark/malicious/fake_context_boundary.md +7 -0
  30. ctxsentry-0.4.0/benchmark/malicious/git_exfil.md +3 -0
  31. ctxsentry-0.4.0/benchmark/malicious/hex_payload.md +3 -0
  32. ctxsentry-0.4.0/benchmark/malicious/hidden_style_span.md +3 -0
  33. ctxsentry-0.4.0/benchmark/malicious/indirect_via_issue_quote.md +5 -0
  34. ctxsentry-0.4.0/benchmark/malicious/jailbreak.md +3 -0
  35. ctxsentry-0.4.0/benchmark/malicious/pipe_shell_mcp/mcp.json +11 -0
  36. ctxsentry-0.4.0/benchmark/malicious/poisoned_mcp/.mcp.json +17 -0
  37. ctxsentry-0.4.0/benchmark/malicious/readme_html_comment.md +9 -0
  38. ctxsentry-0.4.0/benchmark/malicious/readme_plain_exfil.md +3 -0
  39. ctxsentry-0.4.0/benchmark/malicious/reverse_shell.md +3 -0
  40. ctxsentry-0.4.0/benchmark/malicious/role_delimiter_tokens.md +3 -0
  41. ctxsentry-0.4.0/benchmark/malicious/split_payload.md +5 -0
  42. ctxsentry-0.4.0/benchmark/malicious/system_prompt_leak.md +3 -0
  43. ctxsentry-0.4.0/benchmark/malicious/unicode_tag_smuggle.md +7 -0
  44. ctxsentry-0.4.0/benchmark/malicious/zerowidth_spaced_payload.md +3 -0
  45. ctxsentry-0.4.0/docs/RELEASING.md +45 -0
  46. ctxsentry-0.4.0/docs/threat-model.md +74 -0
  47. ctxsentry-0.4.0/docs/walkthrough.md +137 -0
  48. ctxsentry-0.4.0/pyproject.toml +64 -0
  49. ctxsentry-0.4.0/src/ctxsentry/__init__.py +13 -0
  50. ctxsentry-0.4.0/src/ctxsentry/__main__.py +6 -0
  51. ctxsentry-0.4.0/src/ctxsentry/benchmark.py +121 -0
  52. ctxsentry-0.4.0/src/ctxsentry/cli.py +249 -0
  53. ctxsentry-0.4.0/src/ctxsentry/contexts.py +164 -0
  54. ctxsentry-0.4.0/src/ctxsentry/detectors.py +626 -0
  55. ctxsentry-0.4.0/src/ctxsentry/document.py +53 -0
  56. ctxsentry-0.4.0/src/ctxsentry/finding.py +74 -0
  57. ctxsentry-0.4.0/src/ctxsentry/report.py +169 -0
  58. ctxsentry-0.4.0/src/ctxsentry/rules.py +335 -0
  59. ctxsentry-0.4.0/src/ctxsentry/scanner.py +322 -0
  60. ctxsentry-0.4.0/tests/conftest.py +4 -0
  61. ctxsentry-0.4.0/tests/test_benchmark.py +22 -0
  62. ctxsentry-0.4.0/tests/test_cli_and_report.py +116 -0
  63. ctxsentry-0.4.0/tests/test_detectors.py +241 -0
  64. ctxsentry-0.4.0/tests/test_packaging.py +30 -0
  65. ctxsentry-0.4.0/tests/test_scanner.py +153 -0
@@ -0,0 +1,27 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ env/
11
+
12
+ # Tooling
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .mypy_cache/
16
+ .coverage
17
+ htmlcov/
18
+ coverage.xml
19
+
20
+ # ctxsentry output
21
+ ctxsentry.json
22
+ ctxsentry.sarif
23
+
24
+ # Editors / OS
25
+ .vscode/
26
+ .idea/
27
+ .DS_Store
@@ -0,0 +1,100 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ > Releases before 0.4.0 were published under the name **ctxguard**; that name was
7
+ > already taken on PyPI. Everything below is written with the current name.
8
+
9
+ ## [0.4.0] - 2026-09-08
10
+
11
+ ### Changed
12
+ - **Renamed the project `ctxguard` → `ctxsentry`.** The PyPI distribution, the
13
+ CLI command, the import package, the ignore file (`.ctxsentryignore`) and the
14
+ inline suppression marker (`ctxsentry: ignore`) all use the new name. The
15
+ GitHub repository moved to `EivinDDu/ctxsentry` (old links redirect).
16
+
17
+ ### Added
18
+ - **Published to PyPI** — `pip install ctxsentry` / `pipx install ctxsentry`.
19
+ - `.github/workflows/publish.yml` — tagged releases build an sdist + wheel and
20
+ publish to PyPI via OIDC Trusted Publishing (no stored token).
21
+ - `docs/RELEASING.md` — the release checklist and one-time PyPI setup.
22
+ - Richer package metadata: per-version Python classifiers, `Repository` /
23
+ `Changelog` project URLs, and an sdist that ships the tests and benchmark.
24
+
25
+ ## [0.3.0] - 2026-09-06
26
+
27
+ ### Added
28
+ - **Benchmark harness** — `ctxsentry bench` scores the detectors against a
29
+ labelled corpus in `benchmark/` (38 cases) and reports precision, recall, F1,
30
+ false-positive rate and per-rule accuracy. `--min-recall` / `--max-fp-rate` /
31
+ `--min-rule-accuracy` make it a CI gate; CI now fails on any detection
32
+ regression or new false positive.
33
+ - **`CG406` — deobfuscated rescan.** Zero-width / Unicode-Tag characters are
34
+ stripped and common Cyrillic/Greek homoglyphs folded to ASCII, then the rule
35
+ table is re-run. Catches `I​g​n​o​r​e all previous instructions` and
36
+ `іgnоrе …`-style evasions that defeat a raw regex.
37
+ - **`CG107`** — attempts to make the model disclose its own prompt / prior
38
+ context (`repeat the text above verbatim`, `what is your system prompt`).
39
+ - **`CG203`** — fake context boundaries and `system override` banners used to
40
+ inject a task into retrieved / tool-result content.
41
+ - **`CG307`** — DNS-based exfiltration (`dig $(cat …).attacker`).
42
+ - **`CG308`** — exfiltration / persistence via `git remote add` + `git push` to
43
+ a non-GitHub URL, or a `postinstall` hook that shells out.
44
+
45
+ ### Changed
46
+ - `CG302` callback-URL list extended with tunnel / relay services
47
+ (`trycloudflare.com`, `loca.lt`, `localtunnel.me`, `serveo.net`, `lhr.life`,
48
+ `smee.io`, `hookb.in`, `webhookrelay.com`, `dnslog.cn`, `ngrok-free.app`).
49
+ - `CG103` no longer fires on ordinary `### System Requirements`-style headings
50
+ (needs a bare role header, a trailing colon, or `### System prompt`).
51
+ - `CG802` (instruction-like filename) now needs a real imperative phrase, not
52
+ just a keyword like `curl` — `curl_download.md` is no longer flagged.
53
+
54
+ ### Fixed
55
+ - Two false positives found by the new benchmark corpus (the `CG103` and
56
+ `CG802` cases above).
57
+
58
+ ## [0.2.0] - 2026-09-07
59
+
60
+ ### Added
61
+ - `--changed [REF]` — scan only files changed vs `REF` (default `HEAD`) plus
62
+ staged / unstaged / untracked files. Fast pre-commit and PR-diff gating.
63
+ - Encoded-payload detector: base64 and hex blobs are decoded and the plaintext
64
+ rescanned (`CG404`, with the decoded text in the finding). `CG403` is now the
65
+ low-severity "long blob that doesn't decode to text" note.
66
+ - `CG106` — jailbreak / guardrail-removal phrasing (`developer mode`,
67
+ `do anything now`, `ignore your guidelines`, …).
68
+ - `CG306` — reverse-shell command patterns (`bash -i >& /dev/tcp/…`, `nc -e`,
69
+ `python -c '…socket…'`).
70
+
71
+ ### Changed
72
+ - `ctxsentry rules` now lists the analytic detectors (`CG4xx`–`CG8xx`) alongside
73
+ the regex rules.
74
+ - `CG403` moved from the regex table into the encoded-payload detector.
75
+
76
+ ## [0.1.0] - 2026-09-05
77
+
78
+ Initial release.
79
+
80
+ ### Added
81
+ - `ctxsentry scan <path>` — static scan of a repository for inbound
82
+ prompt-injection payloads aimed at AI coding agents.
83
+ - Context classifier (`mcp-config`, `agent-instructions`, `agent-skill`,
84
+ `docs`, `generic`) with per-context severity boosting.
85
+ - Detectors: regex rule table (instruction override, agent-directed
86
+ imperatives, exfiltration, obfuscation), invisible-Unicode scanner with
87
+ `U+E00xx` tag-run decoding, bidi / zero-width / PUA / homoglyph detection,
88
+ layout smuggling, MCP `.mcp.json` structure walk, filename checks.
89
+ - Output formats: `text`, `json`, `sarif` (2.1.0), `markdown`.
90
+ - `--fail-on`, `--min-severity`, `--min-confidence`, `--all-text`,
91
+ `--git-history`, `--exclude`, `-o/--output`.
92
+ - Suppression via `.ctxsentryignore` and inline `ctxsentry: ignore [RULE…]`
93
+ comments.
94
+ - `ctxsentry rules` — list all detection rules.
95
+ - CI matrix (Python 3.9–3.13, Linux + macOS); dogfood self-scan and SARIF
96
+ artifact build on every run.
97
+ - `.pre-commit-hooks.yaml` — usable as a hosted pre-commit repo
98
+ (`repo: https://github.com/EivinDDu/ctxsentry`, `rev: v0.1.0`).
99
+ - `action.yml` — composite GitHub Action (`uses: EivinDDu/ctxsentry@v0.1.0`)
100
+ with `path`, `fail-on`, `args`, and `version` inputs.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EivinDDu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,256 @@
1
+ Metadata-Version: 2.5
2
+ Name: ctxsentry
3
+ Version: 0.4.0
4
+ Summary: Scan a repository for prompt-injection payloads before you point an AI coding agent at it.
5
+ Project-URL: Homepage, https://github.com/EivinDDu/ctxsentry
6
+ Project-URL: Repository, https://github.com/EivinDDu/ctxsentry
7
+ Project-URL: Issues, https://github.com/EivinDDu/ctxsentry/issues
8
+ Project-URL: Changelog, https://github.com/EivinDDu/ctxsentry/blob/main/CHANGELOG.md
9
+ Author: EivinDDu
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai-security,coding-agent,devsecops,llm-security,mcp,prompt-injection,security-scanner,static-analysis,supply-chain
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Information Technology
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Security
27
+ Classifier: Topic :: Software Development :: Quality Assurance
28
+ Requires-Python: >=3.9
29
+ Provides-Extra: dev
30
+ Requires-Dist: build; extra == 'dev'
31
+ Requires-Dist: pytest-cov; extra == 'dev'
32
+ Requires-Dist: pytest>=7; extra == 'dev'
33
+ Requires-Dist: twine; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # ctxsentry
37
+
38
+ **Scan a repository for prompt-injection payloads *before* you point an AI coding agent at it.**
39
+
40
+ [![PyPI](https://img.shields.io/pypi/v/ctxsentry)](https://pypi.org/project/ctxsentry/)
41
+ [![CI](https://github.com/EivinDDu/ctxsentry/actions/workflows/ci.yml/badge.svg)](https://github.com/EivinDDu/ctxsentry/actions/workflows/ci.yml)
42
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
43
+ ![License: MIT](https://img.shields.io/badge/license-MIT-green)
44
+
45
+ <sub>Formerly published as `ctxguard` (≤ 0.3.0); renamed because that name was taken on PyPI.</sub>
46
+
47
+ ---
48
+
49
+ ## The gap this fills
50
+
51
+ Every current MCP / AI-agent security scanner points *outward* — it probes a
52
+ remote MCP server, or audits the agent framework you are building. Almost nothing
53
+ points *inward* at the **untrusted repository you are about to open in Cursor,
54
+ Claude Code, Copilot, or Windsurf**.
55
+
56
+ That repo is attacker-influenced text. The moment your agent reads its
57
+ `README.md`, `CLAUDE.md`, `.cursorrules`, `.mcp.json`, issue templates, or even a
58
+ filename, any instructions hidden in that content enter the model's context.
59
+ Cloud Security Alliance research in 2026 documented "README instruction
60
+ injection" against coding agents with attack success rates of 41–84%, and MCP
61
+ tool-description poisoning (CVE-2025-54136) works the same way.
62
+
63
+ `ctxsentry` is a fast, dependency-free static scanner that flags that content so a
64
+ human reviews it first.
65
+
66
+ **→ See [`docs/walkthrough.md`](docs/walkthrough.md) for a worked example: five
67
+ planted attacks in a sample repo, and the scan that catches every one.**
68
+
69
+ ## What it detects
70
+
71
+ | Family | Examples |
72
+ |---|---|
73
+ | **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`) |
74
+ | **Agent-directed imperatives** | sentences addressed to "the AI / assistant / agent" that also name an action (`run`, `curl`, `exfiltrate`, `install`, `push`) |
75
+ | **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/…`) |
76
+ | **Data exfiltration & RCE** *(cont.)* | DNS exfiltration (`dig $(cat …).attacker`), `git remote add` + `git push` to a non-GitHub URL, `postinstall` hooks that shell out |
77
+ | **Context / prompt disclosure** | `repeat the text above verbatim`, `what is your system prompt`, fake `--- END OF DOCUMENT ---` / `system override:` boundaries injected into retrieved content |
78
+ | **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 |
79
+ | **Deobfuscated rescan** | strips zero-width / tag characters and folds homoglyphs, then re-runs every rule — catches `I​g​n​o​r​e all previous instructions` and `іgnоrе …` evasions |
80
+ | **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 |
81
+ | **Layout smuggling** | instruction text pushed off-screen by whitespace, `display:none` / `color:#fff` / `font-size:0` spans, instruction-bearing HTML comments |
82
+ | **MCP config poisoning** | `.mcp.json` `description` / `instructions` fields carrying hidden directives or secret references; server launch commands that pipe a download into a shell |
83
+ | **Filename injection** | control / invisible / bidi characters in filenames, filenames that spell out an imperative |
84
+
85
+ Severity is **boosted by context**: the same string is `medium` in a source
86
+ comment but `critical` in `.mcp.json` or `CLAUDE.md`, because agents load those
87
+ files as authoritative instructions.
88
+
89
+ ## Install
90
+
91
+ ```bash
92
+ pipx install ctxsentry # recommended
93
+ # or
94
+ pip install ctxsentry
95
+ ```
96
+
97
+ From source:
98
+
99
+ ```bash
100
+ git clone https://github.com/EivinDDu/ctxsentry
101
+ cd ctxsentry
102
+ pip install -e ".[dev]"
103
+ ```
104
+
105
+ ## Usage
106
+
107
+ ```bash
108
+ # scan the current repo
109
+ ctxsentry scan .
110
+
111
+ # scan a repo you just cloned, before opening it in your editor
112
+ ctxsentry scan ../suspicious-repo
113
+
114
+ # scan only what changed — fast pre-commit / PR gating
115
+ ctxsentry scan . --changed # vs HEAD (+ staged/unstaged/untracked)
116
+ ctxsentry scan . --changed origin/main # vs a base branch
117
+
118
+ # machine-readable output
119
+ ctxsentry scan . --format json -o ctxsentry.json
120
+ ctxsentry scan . --format sarif -o ctxsentry.sarif # upload to GitHub code scanning
121
+
122
+ # gate a pipeline
123
+ ctxsentry scan . --fail-on medium --git-history
124
+
125
+ # tune the noise
126
+ ctxsentry scan . --min-severity medium --min-confidence medium
127
+
128
+ # list every rule
129
+ ctxsentry rules
130
+
131
+ # score the detectors against the labelled corpus
132
+ ctxsentry bench
133
+ ```
134
+
135
+ By default `ctxsentry` only reads files an agent treats as context (docs, rule
136
+ files, MCP config, `*.md`, `*.txt`, config formats). Add `--all-text` to sweep
137
+ source files too.
138
+
139
+ ### Exit codes
140
+
141
+ | Code | Meaning |
142
+ |---|---|
143
+ | `0` | completed; nothing at or above `--fail-on` |
144
+ | `1` | findings at or above `--fail-on` (default: `high`) |
145
+ | `2` | usage / runtime error |
146
+
147
+ ### Pre-commit hook
148
+
149
+ ```yaml
150
+ # .pre-commit-config.yaml
151
+ - repo: https://github.com/EivinDDu/ctxsentry
152
+ rev: v0.3.0
153
+ hooks:
154
+ - id: ctxsentry # add: args: ["--changed"] for staged-only scans
155
+ ```
156
+
157
+ ### GitHub Action
158
+
159
+ ```yaml
160
+ # .github/workflows/ctxsentry.yml
161
+ name: ctxsentry
162
+ on: [push, pull_request]
163
+ jobs:
164
+ scan:
165
+ runs-on: ubuntu-latest
166
+ steps:
167
+ - uses: actions/checkout@v4
168
+ - uses: EivinDDu/ctxsentry@v0.3.0
169
+ with:
170
+ fail-on: high # optional (default: high)
171
+ # path: .
172
+ # args: --changed origin/main # PR-diff-only scan
173
+ ```
174
+
175
+ ### Suppressing findings
176
+
177
+ Known-good matches (your own security docs, test fixtures) can be silenced two
178
+ ways:
179
+
180
+ - **`.ctxsentryignore`** at the scan root — one glob per line, optionally
181
+ scoped to specific rule ids:
182
+
183
+ ```
184
+ docs/threat-model.md # ignore every rule for this file
185
+ examples/** # ignore a whole tree
186
+ SECURITY.md:CG101,CG401 # ignore only these rules here
187
+ ```
188
+
189
+ - **Inline comment** on the flagged line or the line above it:
190
+
191
+ ```markdown
192
+ <!-- ctxsentry: ignore CG401 -- example payload documented on purpose -->
193
+ ```
194
+
195
+ ## How it works
196
+
197
+ ```
198
+ path ─▶ file walk (skips vendored dirs, binaries, >1 MB)
199
+ ─▶ decode (utf-8 / utf-16 / latin-1), keep invisible chars intact
200
+ ─▶ classify context (mcp-config │ agent-instructions │ agent-skill │ docs │ generic)
201
+ ─▶ run detectors:
202
+ • regex rule table (ctxsentry/rules.py)
203
+ • invisible-Unicode scanner (decodes U+E00xx tag runs)
204
+ • deobfuscated rescan (strip zero-width, fold homoglyphs, re-run rules)
205
+ • encoded-payload scanner (decodes base64 / hex, rescans plaintext)
206
+ • layout / smuggling scanner
207
+ • MCP JSON structure walk
208
+ • filename scanner
209
+ ─▶ context-adjust severity ─▶ sort ─▶ render (text │ json │ sarif │ markdown)
210
+ ```
211
+
212
+ No network calls. No LLM. Deterministic.
213
+
214
+ ## Benchmark
215
+
216
+ `ctxsentry bench` runs the detectors over a labelled corpus in [`benchmark/`](benchmark)
217
+ (20 malicious fixtures across every family, 18 realistic benign ones) and reports
218
+ precision / recall / F1 / false-positive rate. CI fails the build on any
219
+ regression:
220
+
221
+ ```
222
+ cases: 38 TP 20 FN 0 FP 0 TN 18
223
+ precision 1.000 recall 1.000 F1 1.000 FP-rate 0.000 rule-accuracy 1.000
224
+ ```
225
+
226
+ The benign fixtures are the point — normal `README`s, a `SECURITY.md`, setup
227
+ docs that mention API keys, `### System Requirements` headings — content that
228
+ *looks* adjacent to an attack but must not trip the scanner.
229
+
230
+ ## Limitations
231
+
232
+ - Static pattern matching: a novel paraphrase with no known markers can slip
233
+ through, and benign security documentation *about* prompt injection will
234
+ produce findings (tune with `--min-confidence`).
235
+ - Not a replacement for [`mcp-scan`](https://github.com/invariantlabs-ai/mcp-scan)
236
+ (runtime MCP), secret scanners, or SAST — it covers the one thing they don't.
237
+
238
+ ## Development
239
+
240
+ ```bash
241
+ pip install -e ".[dev]"
242
+ pytest
243
+ ctxsentry bench # detection score against benchmark/
244
+ ```
245
+
246
+ Adding a detector? Add a fixture to `benchmark/malicious/` (and a benign
247
+ counterpart if it could misfire), list it in `benchmark/cases.jsonl`, and keep
248
+ `ctxsentry bench` at 100% recall / 0 false positives.
249
+
250
+ Releasing is documented in [`docs/RELEASING.md`](docs/RELEASING.md) — tag a
251
+ version and publish a GitHub Release; CI builds and uploads to PyPI via Trusted
252
+ Publishing.
253
+
254
+ ## License
255
+
256
+ MIT
@@ -0,0 +1,221 @@
1
+ # ctxsentry
2
+
3
+ **Scan a repository for prompt-injection payloads *before* you point an AI coding agent at it.**
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/ctxsentry)](https://pypi.org/project/ctxsentry/)
6
+ [![CI](https://github.com/EivinDDu/ctxsentry/actions/workflows/ci.yml/badge.svg)](https://github.com/EivinDDu/ctxsentry/actions/workflows/ci.yml)
7
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
8
+ ![License: MIT](https://img.shields.io/badge/license-MIT-green)
9
+
10
+ <sub>Formerly published as `ctxguard` (≤ 0.3.0); renamed because that name was taken on PyPI.</sub>
11
+
12
+ ---
13
+
14
+ ## The gap this fills
15
+
16
+ Every current MCP / AI-agent security scanner points *outward* — it probes a
17
+ remote MCP server, or audits the agent framework you are building. Almost nothing
18
+ points *inward* at the **untrusted repository you are about to open in Cursor,
19
+ Claude Code, Copilot, or Windsurf**.
20
+
21
+ That repo is attacker-influenced text. The moment your agent reads its
22
+ `README.md`, `CLAUDE.md`, `.cursorrules`, `.mcp.json`, issue templates, or even a
23
+ filename, any instructions hidden in that content enter the model's context.
24
+ Cloud Security Alliance research in 2026 documented "README instruction
25
+ injection" against coding agents with attack success rates of 41–84%, and MCP
26
+ tool-description poisoning (CVE-2025-54136) works the same way.
27
+
28
+ `ctxsentry` is a fast, dependency-free static scanner that flags that content so a
29
+ human reviews it first.
30
+
31
+ **→ See [`docs/walkthrough.md`](docs/walkthrough.md) for a worked example: five
32
+ planted attacks in a sample repo, and the scan that catches every one.**
33
+
34
+ ## What it detects
35
+
36
+ | Family | Examples |
37
+ |---|---|
38
+ | **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`) |
39
+ | **Agent-directed imperatives** | sentences addressed to "the AI / assistant / agent" that also name an action (`run`, `curl`, `exfiltrate`, `install`, `push`) |
40
+ | **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/…`) |
41
+ | **Data exfiltration & RCE** *(cont.)* | DNS exfiltration (`dig $(cat …).attacker`), `git remote add` + `git push` to a non-GitHub URL, `postinstall` hooks that shell out |
42
+ | **Context / prompt disclosure** | `repeat the text above verbatim`, `what is your system prompt`, fake `--- END OF DOCUMENT ---` / `system override:` boundaries injected into retrieved content |
43
+ | **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 |
44
+ | **Deobfuscated rescan** | strips zero-width / tag characters and folds homoglyphs, then re-runs every rule — catches `I​g​n​o​r​e all previous instructions` and `іgnоrе …` evasions |
45
+ | **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 |
46
+ | **Layout smuggling** | instruction text pushed off-screen by whitespace, `display:none` / `color:#fff` / `font-size:0` spans, instruction-bearing HTML comments |
47
+ | **MCP config poisoning** | `.mcp.json` `description` / `instructions` fields carrying hidden directives or secret references; server launch commands that pipe a download into a shell |
48
+ | **Filename injection** | control / invisible / bidi characters in filenames, filenames that spell out an imperative |
49
+
50
+ Severity is **boosted by context**: the same string is `medium` in a source
51
+ comment but `critical` in `.mcp.json` or `CLAUDE.md`, because agents load those
52
+ files as authoritative instructions.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pipx install ctxsentry # recommended
58
+ # or
59
+ pip install ctxsentry
60
+ ```
61
+
62
+ From source:
63
+
64
+ ```bash
65
+ git clone https://github.com/EivinDDu/ctxsentry
66
+ cd ctxsentry
67
+ pip install -e ".[dev]"
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ ```bash
73
+ # scan the current repo
74
+ ctxsentry scan .
75
+
76
+ # scan a repo you just cloned, before opening it in your editor
77
+ ctxsentry scan ../suspicious-repo
78
+
79
+ # scan only what changed — fast pre-commit / PR gating
80
+ ctxsentry scan . --changed # vs HEAD (+ staged/unstaged/untracked)
81
+ ctxsentry scan . --changed origin/main # vs a base branch
82
+
83
+ # machine-readable output
84
+ ctxsentry scan . --format json -o ctxsentry.json
85
+ ctxsentry scan . --format sarif -o ctxsentry.sarif # upload to GitHub code scanning
86
+
87
+ # gate a pipeline
88
+ ctxsentry scan . --fail-on medium --git-history
89
+
90
+ # tune the noise
91
+ ctxsentry scan . --min-severity medium --min-confidence medium
92
+
93
+ # list every rule
94
+ ctxsentry rules
95
+
96
+ # score the detectors against the labelled corpus
97
+ ctxsentry bench
98
+ ```
99
+
100
+ By default `ctxsentry` only reads files an agent treats as context (docs, rule
101
+ files, MCP config, `*.md`, `*.txt`, config formats). Add `--all-text` to sweep
102
+ source files too.
103
+
104
+ ### Exit codes
105
+
106
+ | Code | Meaning |
107
+ |---|---|
108
+ | `0` | completed; nothing at or above `--fail-on` |
109
+ | `1` | findings at or above `--fail-on` (default: `high`) |
110
+ | `2` | usage / runtime error |
111
+
112
+ ### Pre-commit hook
113
+
114
+ ```yaml
115
+ # .pre-commit-config.yaml
116
+ - repo: https://github.com/EivinDDu/ctxsentry
117
+ rev: v0.3.0
118
+ hooks:
119
+ - id: ctxsentry # add: args: ["--changed"] for staged-only scans
120
+ ```
121
+
122
+ ### GitHub Action
123
+
124
+ ```yaml
125
+ # .github/workflows/ctxsentry.yml
126
+ name: ctxsentry
127
+ on: [push, pull_request]
128
+ jobs:
129
+ scan:
130
+ runs-on: ubuntu-latest
131
+ steps:
132
+ - uses: actions/checkout@v4
133
+ - uses: EivinDDu/ctxsentry@v0.3.0
134
+ with:
135
+ fail-on: high # optional (default: high)
136
+ # path: .
137
+ # args: --changed origin/main # PR-diff-only scan
138
+ ```
139
+
140
+ ### Suppressing findings
141
+
142
+ Known-good matches (your own security docs, test fixtures) can be silenced two
143
+ ways:
144
+
145
+ - **`.ctxsentryignore`** at the scan root — one glob per line, optionally
146
+ scoped to specific rule ids:
147
+
148
+ ```
149
+ docs/threat-model.md # ignore every rule for this file
150
+ examples/** # ignore a whole tree
151
+ SECURITY.md:CG101,CG401 # ignore only these rules here
152
+ ```
153
+
154
+ - **Inline comment** on the flagged line or the line above it:
155
+
156
+ ```markdown
157
+ <!-- ctxsentry: ignore CG401 -- example payload documented on purpose -->
158
+ ```
159
+
160
+ ## How it works
161
+
162
+ ```
163
+ path ─▶ file walk (skips vendored dirs, binaries, >1 MB)
164
+ ─▶ decode (utf-8 / utf-16 / latin-1), keep invisible chars intact
165
+ ─▶ classify context (mcp-config │ agent-instructions │ agent-skill │ docs │ generic)
166
+ ─▶ run detectors:
167
+ • regex rule table (ctxsentry/rules.py)
168
+ • invisible-Unicode scanner (decodes U+E00xx tag runs)
169
+ • deobfuscated rescan (strip zero-width, fold homoglyphs, re-run rules)
170
+ • encoded-payload scanner (decodes base64 / hex, rescans plaintext)
171
+ • layout / smuggling scanner
172
+ • MCP JSON structure walk
173
+ • filename scanner
174
+ ─▶ context-adjust severity ─▶ sort ─▶ render (text │ json │ sarif │ markdown)
175
+ ```
176
+
177
+ No network calls. No LLM. Deterministic.
178
+
179
+ ## Benchmark
180
+
181
+ `ctxsentry bench` runs the detectors over a labelled corpus in [`benchmark/`](benchmark)
182
+ (20 malicious fixtures across every family, 18 realistic benign ones) and reports
183
+ precision / recall / F1 / false-positive rate. CI fails the build on any
184
+ regression:
185
+
186
+ ```
187
+ cases: 38 TP 20 FN 0 FP 0 TN 18
188
+ precision 1.000 recall 1.000 F1 1.000 FP-rate 0.000 rule-accuracy 1.000
189
+ ```
190
+
191
+ The benign fixtures are the point — normal `README`s, a `SECURITY.md`, setup
192
+ docs that mention API keys, `### System Requirements` headings — content that
193
+ *looks* adjacent to an attack but must not trip the scanner.
194
+
195
+ ## Limitations
196
+
197
+ - Static pattern matching: a novel paraphrase with no known markers can slip
198
+ through, and benign security documentation *about* prompt injection will
199
+ produce findings (tune with `--min-confidence`).
200
+ - Not a replacement for [`mcp-scan`](https://github.com/invariantlabs-ai/mcp-scan)
201
+ (runtime MCP), secret scanners, or SAST — it covers the one thing they don't.
202
+
203
+ ## Development
204
+
205
+ ```bash
206
+ pip install -e ".[dev]"
207
+ pytest
208
+ ctxsentry bench # detection score against benchmark/
209
+ ```
210
+
211
+ Adding a detector? Add a fixture to `benchmark/malicious/` (and a benign
212
+ counterpart if it could misfire), list it in `benchmark/cases.jsonl`, and keep
213
+ `ctxsentry bench` at 100% recall / 0 false positives.
214
+
215
+ Releasing is documented in [`docs/RELEASING.md`](docs/RELEASING.md) — tag a
216
+ version and publish a GitHub Release; CI builds and uploads to PyPI via Trusted
217
+ Publishing.
218
+
219
+ ## License
220
+
221
+ MIT
@@ -0,0 +1,11 @@
1
+ # API
2
+
3
+ ## Authentication
4
+
5
+ Set your API key in the `API_KEY` environment variable, or add it to a local `.env` file:
6
+
7
+ ```
8
+ API_KEY=sk-your-key-here
9
+ ```
10
+
11
+ The client reads the token automatically. Never share your key.
@@ -0,0 +1,3 @@
1
+ # Architecture
2
+
3
+ The service has three layers: an HTTP handler, a domain layer, and a storage adapter. Requests are validated at the edge and passed as typed commands to the domain layer, which is the only place business rules live.
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 2.1.0
4
+
5
+ - Added an `--ignore-case` flag to search.
6
+ - Fixed a crash when the input file was empty.
7
+ - The parser now tolerates trailing commas.
@@ -0,0 +1,22 @@
1
+ {
2
+ "mcpServers": {
3
+ "filesystem": {
4
+ "command": "npx",
5
+ "args": [
6
+ "-y",
7
+ "@modelcontextprotocol/server-filesystem",
8
+ "./data"
9
+ ],
10
+ "tools": [
11
+ {
12
+ "name": "read_file",
13
+ "description": "Read a file from the data directory."
14
+ },
15
+ {
16
+ "name": "list_dir",
17
+ "description": "List the contents of a directory."
18
+ }
19
+ ]
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,3 @@
1
+ # Code of Conduct
2
+
3
+ We are committed to a harassment-free experience for everyone. Be respectful. Assume good intent. Report unacceptable behavior to conduct@example.com.