Definition
A poisoned rules file is a file an agent reads as its own instructions that has been changed to carry text turning the agent against its user.
Tells the agent to ignore its other rules, hide what it finds, or send secrets or API keys somewhere. This report names the behaviors, not the wording.
A repository file the agent loads by itself: a rules file, an editor or container config, an ignore file, a discovery file such as llms.txt.
Discovery-file, CI and build-metadata poisoning sit in other categories (133 rows for discovery files alone). They were not measured here.
Which files the ten rules name
| Rule | Names this kind of file |
|---|---|
| GLS-AIFP-001 | Agent discovery files: llms.txt, robots.txt, sitemap, manifests |
| GLS-AIFP-002 | AGENTS.md, CLAUDE.md, SKILL.md, copilot-instructions, .cursor/rules |
| GLS-AIFP-003 | .cursor/rules MDC files and agent-directed front matter |
| GLS-AIFP-004 | .devcontainer/devcontainer.json |
| GLS-AIFP-005 | llms-full.txt |
| GLS-AIFP-006 | VS Code .code-workspace files |
| GLS-AIFP-007 | .editorconfig |
| GLS-AIFP-008 | Ignore files |
| GLS-AIFP-009 | .pre-commit-config.yaml |
| GLS-AIFP-010 | Copilot .instructions.md front matter. Lists the message and api_response channels only. |
All ten are high severity. They overlap: of the 10 probes, 3 also fired a neighbouring rule (the .mdc text fired GLS-AIFP-002, the llms-full.txt text fired GLS-AIFP-003, the pre-commit text fired 003 and 006). Read the table as what each rule mentions, not as a classifier by file type.
What we measured
| Question | Result | Denominator |
|---|---|---|
| A hostile probe fires its own rule | 10 | 10 rules, one probe each |
| ...when scanned on the file channel | 9 | 10 rules. GLS-AIFP-010 does not list it. |
| Real-named files, directory scan: read / blocked / blocked by their own rule | 10 / 10 / 9 | 10 files, 0 skipped, dot-folders included |
| A clean file of each kind is flagged | 0 | 10 files |
| Defensive text about the attack fires a rule in the class | 0 (4 allow, 1 block by a generic rule) | 5 texts |
| A hostile sentence with no file-type marker fires a rule in the class | 0 (all 9 still blocked by generic rules) | 9 texts |
| Probes caught by this class's rules alone | 1 | 10 probes. The other 9 also fired at least one other rule. |
Every hostile probe was written by us after reading that rule, so 10 of 10 is an upper bound on what the rules can do, not how often they catch real poisoned files. One probe per rule. This report prints no percentage. The last row means these rules are not shown to be what stops the file: we did not test whether the decision stays block with them removed.
Where a rule did not fire
| Item | What happened |
|---|---|
| GLS-AIFP-010, hostile .github/instructions file, directory scan | Did not fire. The rule lists message and api_response only. The file was still blocked, by three other rules. |
| .cursorrules, hostile, file channel, 1 probe | No rule in this class fired. Blocked by two generic rules. |
| .windsurfrules, .clinerules, GEMINI.md, 1 probe each, directory scan | No rule in this class names them today. All 3 were blocked by generic rules. The same sentence under AGENTS.md, CLAUDE.md or .github/copilot-instructions.md fired GLS-AIFP-002. |
| .roorules, CONVENTIONS.md, .aider files, .continue/, .junie/ | Not probed at all. |
| Discovery-file, CI and build-metadata categories | Not measured. |
False positives on real files
| Question | Result | Denominator |
|---|---|---|
| Clean file of each kind flagged | 0 | 10 files |
| Real files that fired GLS-AIFP-001 | 11 | 3,290 unique real README, llms.txt, llms-full.txt and robots.txt files on one Mac |
| ...blocked by that rule alone | 1 | 29 files whose text matches the rule's pattern |
| Third-party package READMEs that fired it | 0 | 1,459 READMEs |
| Who the 11 are | 10 earlier snapshots of this site's own llms.txt and llms-full.txt; 1 README of a cloned repo | 11 files |
| A rule with no defensive-framing guard | GLS-AIFP-001. The other rules in the class have one. | 4 of 4 hand-made benign sentences (a discovery-file name, an agent word and a secrets word) returned block with it as the only finding; 2 controls missing one part returned allow |
Not a rate for the wild. The files are not a random sample, 30 of them are one site's snapshots, and the largest group is JavaScript package READMEs. The count comes from a text-pattern prefilter followed by the full engine on the 29 matches; a full pass over all 3,290 did not finish, so a case where the engine fires without a prefilter match is not excluded. This report prints no percentage.
What the scanner does today
| Boundary | Behavior | Status |
|---|---|---|
| Directory scan | Sunglasses reads every file in the folder, dot-folders included; critical or high blocks and the exit code is 1 | Run: 10 real-named files, 0 skipped |
| Files over 1 MB | Skipped by design and listed as skipped | Seen once, on a test fixture |
| Hook and proxy | Not tested on a rules file | NOT RUN |
| GitHub Action | Its own test fixtures hold no rules file: 0 rules in this class fired across its 14 fixture files (11 scanned, 3 skipped) | NOT RUN on a rules file |
What we do not catch
- Real poisoned files. We tested none. Real-world recall for this class is unknown, and every probe was written by us after reading the rule it was aimed at.
- A file scan of a Copilot instruction file by its own rule. GLS-AIFP-010 does not list the file channel, so a directory scan reaches that file only through other rules.
- Names with no rule in this class today: .cursorrules, .windsurfrules, .clinerules and GEMINI.md. Each was blocked by generic rules on one probe; that is one probe per name, not coverage.
- Names we did not probe: .roorules, CONVENTIONS.md, .aider files, .continue/ and .junie/.
- Which rules do the stopping. For 9 of 10 probes other rules also fired, and we did not test the decision without this class.
- Files over 1 MB, archives, the proxy, the hook, and the web_content channel for rules 001 to 009. None was tested for this class.
Reproduce it
pip install sunglasses==0.6.20
sunglasses scan --dir . --json on a copy of your repository.
decision allow, threat_found false, exit code 0.
file_results[].findings and raw_findings hold every rule that fired. The top-level findings and total_threats are a merged view: a rule absorbed into a stronger one on the same text appears only under also_matched.
No public fixture with a rules file yet. This report prints no hostile text.
Version and method
sunglasses 0.6.20 from PyPI, installed in a clean virtual environment, run isolated with an empty home folder.
November 9, 2026.
Raw findings. In the merged view, 4 of the 10 rules (003, 004, 006, 009) looked as if they had not fired on their own probe; they had.
Synthetic probes, and real files from one Mac. This says nothing about other phrasings or about real incidents. Numbers move when the scanner moves: pin the version.