Attack Class · AI Agent Security

Poisoned Rules Files: What a Directory Scan Catches and Misses

Published · Sunglasses team · Attack class report

A poisoned rules file is a file an AI agent loads as its own instructions that carries text written to turn the agent against its user. This report counts what ten scanner rules do with our own probes, which files they name, and what they do not reach. Every number carries its denominator, and we wrote the probes after reading the rules.

Quick answer
sunglasses://reports/poisoned rules files#quick-answer
What it is

A file an agent loads as its own rules (AGENTS.md, .cursor/rules and similar) that carries text written to turn the agent against its user.

What we tried

Ten rules in this class on sunglasses 0.6.20, one hostile probe each. All 10 fired on their own probe. We wrote every probe 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.

On a file scan

9 of the 10 fired when the probe was scanned as a file. GLS-AIFP-010 lists the message and api_response channels only.

Real file names

10 files under their real names in a test folder: a directory scan read all 10, blocked all 10, and 9 were blocked by the rule for their own kind. Clean files: 0 of 10 flagged.

What we miss

No real poisoned file was tested. .cursorrules, .windsurfrules, .clinerules and GEMINI.md have no rule in this class today; one probe each was still blocked by generic rules.

Method

sunglasses 0.6.20 from PyPI in a clean environment, measured November 9, 2026. Every probe is synthetic.

§01 · Definition

Definition

sunglasses://reports/poisoned rules files#definition
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.

What the text does

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.

Carried by

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.

Not in these numbers

Discovery-file, CI and build-metadata poisoning sit in other categories (133 rows for discovery files alone). They were not measured here.

§02 · Which files the ten rules name

Which files the ten rules name

RuleNames this kind of file
GLS-AIFP-001Agent discovery files: llms.txt, robots.txt, sitemap, manifests
GLS-AIFP-002AGENTS.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-005llms-full.txt
GLS-AIFP-006VS Code .code-workspace files
GLS-AIFP-007.editorconfig
GLS-AIFP-008Ignore files
GLS-AIFP-009.pre-commit-config.yaml
GLS-AIFP-010Copilot .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.

§03 · What we measured

What we measured

sunglasses://reports/poisoned rules files#what-we-measured
10rules in the class
10 of 10fired on their own probe (upper bound)
9 of 10fired when scanned as a file
0 of 10clean files flagged
QuestionResultDenominator
A hostile probe fires its own rule1010 rules, one probe each
...when scanned on the file channel910 rules. GLS-AIFP-010 does not list it.
Real-named files, directory scan: read / blocked / blocked by their own rule10 / 10 / 910 files, 0 skipped, dot-folders included
A clean file of each kind is flagged010 files
Defensive text about the attack fires a rule in the class0 (4 allow, 1 block by a generic rule)5 texts
A hostile sentence with no file-type marker fires a rule in the class0 (all 9 still blocked by generic rules)9 texts
Probes caught by this class's rules alone110 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.

§04 · Where a rule did not fire

Where a rule did not fire

ItemWhat happened
GLS-AIFP-010, hostile .github/instructions file, directory scanDid not fire. The rule lists message and api_response only. The file was still blocked, by three other rules.
.cursorrules, hostile, file channel, 1 probeNo rule in this class fired. Blocked by two generic rules.
.windsurfrules, .clinerules, GEMINI.md, 1 probe each, directory scanNo 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 categoriesNot measured.
§05 · False positives on real files

False positives on real files

QuestionResultDenominator
Clean file of each kind flagged010 files
Real files that fired GLS-AIFP-001113,290 unique real README, llms.txt, llms-full.txt and robots.txt files on one Mac
...blocked by that rule alone129 files whose text matches the rule's pattern
Third-party package READMEs that fired it01,459 READMEs
Who the 11 are10 earlier snapshots of this site's own llms.txt and llms-full.txt; 1 README of a cloned repo11 files
A rule with no defensive-framing guardGLS-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.

§06 · What the scanner does today

What the scanner does today

BoundaryBehaviorStatus
Directory scanSunglasses reads every file in the folder, dot-folders included; critical or high blocks and the exit code is 1Run: 10 real-named files, 0 skipped
Files over 1 MBSkipped by design and listed as skippedSeen once, on a test fixture
Hook and proxyNot tested on a rules fileNOT RUN
GitHub ActionIts 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
§07 · What we do not catch

What we do not catch

  1. 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.
  2. 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.
  3. 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.
  4. Names we did not probe: .roorules, CONVENTIONS.md, .aider files, .continue/ and .junie/.
  5. Which rules do the stopping. For 9 of 10 probes other rules also fired, and we did not test the decision without this class.
  6. 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.
§08 · Reproduce it

Reproduce it

sunglasses://reports/poisoned rules files#reproduce-it
Install

pip install sunglasses==0.6.20

Run

sunglasses scan --dir . --json on a copy of your repository.

Expect on a folder with no findings

decision allow, threat_found false, exit code 0.

Read the rules that fired

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.

Fixture

No public fixture with a rules file yet. This report prints no hostile text.

§09 · Version and method

Version and method

sunglasses://reports/poisoned rules files#version-and-method
Package

sunglasses 0.6.20 from PyPI, installed in a clean virtual environment, run isolated with an empty home folder.

Measured

November 9, 2026.

Counting

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.

Scope

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.

Frequently Asked Questions

sunglasses://reports/poisoned rules files#faq
Q.01

What is a poisoned rules file?

A file an AI agent loads as its own instructions, such as AGENTS.md or a .cursor/rules file, that has been changed to carry text turning the agent against its user.

Q.02

Does the scanner stop them?

Not shown. On our own probes, written after reading the rules, each of the 10 rules in this class fired on its probe and 9 of 10 did so on a file scan. No real poisoned file was tested, so real-world recall is unknown.

Q.03

How do I check my own repository?

Run the one command in the reproduce section on a copy of your repository, on the same package version, and read the rules that fired in file_results.

Related reading

Scan what the agent sees, before it acts

Sunglasses is an open source input filter for AI agent security. It blocks prompt injection before your agent reads it. pip install sunglasses