Janitor skill-bundle audit
Overview
Agent-context files (SKILL.md, CLAUDE.md, AGENTS.md, .cursorrules,
.aider.conf.yml, the .claude/ tree, .mcp.json, etc.) are the
"instructions a human reader sees" surface AND the "instructions the
agent silently obeys" surface. An attacker who can write to one of
those files can issue covert directives — multilingual
"ignore-previous-instructions" payloads, HTML-comment authority
impersonations, base-URL overrides that redirect every subsequent LLM
call to an attacker-controlled gateway, cross-skill shadowing that
makes every other skill invoke a malicious one first.
This skill scans every agent-context file in the project with the
shared rule catalogue from scripts/lib/agent_config_patterns.py
(Wave 2 of the github-monitoring distillation) and surfaces every
match grouped by file + severity. Read-only — never edits a file.
The companion heartbeat detector ai-context-poisoning.py
audits installed packages for postinstall code that writes to an
agent-context file. This skill audits the current content of those
files for attacker-planted directives.
Prerequisites
uv on PATH (the underlying scanner runs as a PEP-723 script).
- The project root resolves either from
$CLAUDE_PROJECT_DIR or the
current working directory.
- The project is NOT the janitor's own repo. The skill refuses to
scan itself unless
CLAUDE_PLUGIN_ALLOW_SELF_SCAN=1.
Arguments
Parse $ARGUMENTS for any of (all optional):
--path <dir> — audit a subtree instead of the whole project root.
--severity <CRITICAL|HIGH|MEDIUM|LOW> — minimum severity to report
(default: LOW = everything).
--json — emit one JSON-line per finding instead of the grouped
human-readable table.
--strict — exit non-zero if any CRITICAL/HIGH finding is reported
(default: exit 0 — the skill SURFACES findings, doesn't gate CI).
Instructions
Pre-flight checks:
# Resolve project root (worktree-safe).
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
PROJECT_ROOT="$(git worktree list | head -n1 | awk '{print $1}')"
else
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
fi
# Self-scan guard.
if [ -f "$PROJECT_ROOT/.claude-plugin/plugin.json" ] && \
[ "$(jq -r .name "$PROJECT_ROOT/.claude-plugin/plugin.json" 2>/dev/null)" = "ai-maestro-janitor" ] && \
[ -z "$CLAUDE_PLUGIN_ALLOW_SELF_SCAN" ]; then
echo "[SKIP] refuses to audit the janitor's own repo (set CLAUDE_PLUGIN_ALLOW_SELF_SCAN=1 to override)"
exit 0
fi
Enumerate agent-context files under $PROJECT_ROOT (or
--path subtree). The canonical inventory lives in
agent_context_files() from scripts/lib/security_helpers.py:
CLAUDE.md, CLAUDE.local.md, AGENTS.md, AGENT.md,
.cursorrules, .cursor/rules/*, .aiderrules, .aider.conf.yml,
.codexrules, .continuerules, .windsurfrules, .cody/instructions,
.claude/settings.json, .claude/agents/*, .claude/skills/*,
.claude/hooks/*, .claude/commands/*, .claude/CLAUDE.md,
.mcp.json, .claude-plugin/plugin.json
Plus every SKILL.md under skills/ and every *.md under
agents/ and commands/ in the project tree. Skip node_modules/,
.venv/, dist/, build/ — those are scanned by the
ai-context-poisoning detector, not by this skill.
Scan each file through agent_config_patterns.scan_text():
uv run --with /dev/null - <<'PY'
import sys, json
from pathlib import Path
sys.path.insert(0, str(Path("$PROJECT_ROOT") / "scripts" / "lib"))
import agent_config_patterns as acp
for fp in target_files:
text = Path(fp).read_text(encoding="utf-8", errors="replace")
for f in acp.scan_text(text, file_kind="prose"):
print(json.dumps({
"file": fp, "line": f.line, "col": f.column,
"rule_id": f.rule_id, "severity": f.severity,
"owasp": f.owasp_asi, "match": f.matched_text,
"description": f.description,
}))
PY
Group + render to the user, ordered by severity then by file:
┌─────────────────────────────────────────────────────────────┐
│ CRITICAL (3) │
├─────────────────────────────────────────────────────────────┤
│ CLAUDE.md:42 │
│ prompt-injection-multilingual (ASI-01) │
│ <ignore-prev-instructions payload> │
│ skills/foo/SKILL.md:7 │
│ html-comment-impersonation (ASI-01) │
│ <html-comment-impersonation hit> │
│ .cursorrules:1 │
│ base-url-override (ASI-04) │
│ <base-url-override to attacker-controlled gateway> │
└─────────────────────────────────────────────────────────────┘
Recommend remediation under each grouping, one line per
rule class:
prompt-injection-multilingual → "Strip the override directive
and audit who introduced it (git blame on the offending line)."
html-comment-impersonation → "Remove the HTML comment; treat
every HTML comment that forges a system / admin / instruction
directive — rule html-comment-impersonation, whose signature
lives in agent_config_patterns.py — as an attack signal."
base-url-override → "Restore the original endpoint; if this is
intentional, document it in a separate non-agent-context file."
cross-skill-shadowing → "Skill descriptions must not mandate
other skills; convert to suggestion or remove."
exfil-webhook-sink → "Block-list the host; rotate any credential
mentioned nearby in the same file."
Report one summary line to stdout in addition to the table:
skill-bundle-audit: 7 finding(s) across 4 file(s) [3 CRITICAL, 2 HIGH, 2 MEDIUM]. Worst: CLAUDE.md:42 prompt-injection-multilingual.
Output
A grouped table of findings to the user PLUS one summary line. With
--json, ONE finding per line to stdout (no table). With --strict,
exit 1 if any CRITICAL/HIGH finding fired (so CI can wrap this skill
as a gate).
Error Handling
- No agent-context files found →
skill-bundle-audit: 0 file(s) matched the agent-context inventory under <path>; exit 0.
- A file is unreadable (permission, binary, broken symlink) → skip
silently; the scan keeps running. Count the skips in the summary
line.
- The patterns module fails to import → report
FAILED: cannot import agent_config_patterns from <path> and exit 2.
Examples
User: /janitor-skill-bundle-audit
User: scan my skills for prompt injection
User: audit CLAUDE.md and .cursorrules for attacks
User: check .claude/agents/ for skill hijack patterns
User: /janitor-skill-bundle-audit --severity HIGH --strict
User: /janitor-skill-bundle-audit --path skills/ --json
Scope
This skill is the content audit: it looks for attacker directives
INSIDE agent-context files. The complementary surfaces:
ai-context-poisoning heartbeat detector — audits installed
packages (node_modules / site-packages) for postinstall code that
writes to an agent-context file. Different threat, same surface.
janitor-github-workflow-doctor — audits .github/workflows/ for
workflow-level attacks (template injection, missing pinning, etc.).
janitor-supply-chain-watcher — audits lockfiles for known-vuln
advisories.
The four together — workflow + lockfile + installed-package +
agent-context-content — cover the supply-chain → agent-runtime
attack surface end-to-end.
Resources
Checklist
Copy this checklist and track your progress:
Source: Emasoft/ai-maestro-janitor — distributed by TomeVault.
1---2name: janitor-skill-bundle-audit3description: On-demand security audit of every agent-context bundle in the current project — SKILL.md / CLAUDE.md / AGENTS.md / .cursorrules / .aider.conf.yml / .claude/* / .mcp.json. Detects multilingual prompt-injection, HTML-comment authority impersonation, base-URL override, cross-skill shadowing, dynamic exec in skill bodies, git-hook install directives, exfil webhook sinks, and known-secret-path references. Trigger with /janitor-skill-bundle-audit, "scan my skills for prompt injection", "audit CLAUDE.md for attacks", or "check .cursorrules for hijack patterns". Use when this capability is needed.4---56# Janitor skill-bundle audit78## Overview910Agent-context files (SKILL.md, CLAUDE.md, AGENTS.md, .cursorrules,11.aider.conf.yml, the `.claude/` tree, .mcp.json, etc.) are the12"instructions a human reader sees" surface AND the "instructions the13agent silently obeys" surface. An attacker who can write to one of14those files can issue covert directives — multilingual15"ignore-previous-instructions" payloads, HTML-comment authority16impersonations, base-URL overrides that redirect every subsequent LLM17call to an attacker-controlled gateway, cross-skill shadowing that18makes every other skill invoke a malicious one first.1920This skill scans every agent-context file in the project with the21shared rule catalogue from `scripts/lib/agent_config_patterns.py`22(Wave 2 of the github-monitoring distillation) and surfaces every23match grouped by file + severity. Read-only — never edits a file.2425The companion **heartbeat** detector `ai-context-poisoning.py`26audits installed packages for postinstall code that *writes to* an27agent-context file. This skill audits the *current content* of those28files for attacker-planted directives.2930## Prerequisites3132* `uv` on PATH (the underlying scanner runs as a PEP-723 script).33* The project root resolves either from `$CLAUDE_PROJECT_DIR` or the34 current working directory.35* The project is NOT the janitor's own repo. The skill refuses to36 scan itself unless `CLAUDE_PLUGIN_ALLOW_SELF_SCAN=1`.3738## Arguments3940Parse `$ARGUMENTS` for any of (all optional):4142* `--path <dir>` — audit a subtree instead of the whole project root.43* `--severity <CRITICAL|HIGH|MEDIUM|LOW>` — minimum severity to report44 (default: `LOW` = everything).45* `--json` — emit one JSON-line per finding instead of the grouped46 human-readable table.47* `--strict` — exit non-zero if any CRITICAL/HIGH finding is reported48 (default: exit 0 — the skill SURFACES findings, doesn't gate CI).4950## Instructions51521. **Pre-flight checks**:5354 ```bash55 # Resolve project root (worktree-safe).56 if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then57 PROJECT_ROOT="$(git worktree list | head -n1 | awk '{print $1}')"58 else59 PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"60 fi6162 # Self-scan guard.63 if [ -f "$PROJECT_ROOT/.claude-plugin/plugin.json" ] && \64 [ "$(jq -r .name "$PROJECT_ROOT/.claude-plugin/plugin.json" 2>/dev/null)" = "ai-maestro-janitor" ] && \65 [ -z "$CLAUDE_PLUGIN_ALLOW_SELF_SCAN" ]; then66 echo "[SKIP] refuses to audit the janitor's own repo (set CLAUDE_PLUGIN_ALLOW_SELF_SCAN=1 to override)"67 exit 068 fi69 ```70712. **Enumerate agent-context files** under `$PROJECT_ROOT` (or72 `--path` subtree). The canonical inventory lives in73 `agent_context_files()` from `scripts/lib/security_helpers.py`:7475 ```text76 CLAUDE.md, CLAUDE.local.md, AGENTS.md, AGENT.md,77 .cursorrules, .cursor/rules/*, .aiderrules, .aider.conf.yml,78 .codexrules, .continuerules, .windsurfrules, .cody/instructions,79 .claude/settings.json, .claude/agents/*, .claude/skills/*,80 .claude/hooks/*, .claude/commands/*, .claude/CLAUDE.md,81 .mcp.json, .claude-plugin/plugin.json82 ```8384 Plus every `SKILL.md` under `skills/` and every `*.md` under85 `agents/` and `commands/` in the project tree. Skip `node_modules/`,86 `.venv/`, `dist/`, `build/` — those are scanned by the87 `ai-context-poisoning` detector, not by this skill.88893. **Scan each file** through `agent_config_patterns.scan_text()`:9091 ```bash92 uv run --with /dev/null - <<'PY'93 import sys, json94 from pathlib import Path95 sys.path.insert(0, str(Path("$PROJECT_ROOT") / "scripts" / "lib"))96 import agent_config_patterns as acp9798 for fp in target_files:99 text = Path(fp).read_text(encoding="utf-8", errors="replace")100 for f in acp.scan_text(text, file_kind="prose"):101 print(json.dumps({102 "file": fp, "line": f.line, "col": f.column,103 "rule_id": f.rule_id, "severity": f.severity,104 "owasp": f.owasp_asi, "match": f.matched_text,105 "description": f.description,106 }))107 PY108 ```1091104. **Group + render** to the user, ordered by severity then by file:111112 ```text113 ┌─────────────────────────────────────────────────────────────┐114 │ CRITICAL (3) │115 ├─────────────────────────────────────────────────────────────┤116 │ CLAUDE.md:42 │117 │ prompt-injection-multilingual (ASI-01) │118 │ <ignore-prev-instructions payload> │119 │ skills/foo/SKILL.md:7 │120 │ html-comment-impersonation (ASI-01) │121 │ <html-comment-impersonation hit> │122 │ .cursorrules:1 │123 │ base-url-override (ASI-04) │124 │ <base-url-override to attacker-controlled gateway> │125 └─────────────────────────────────────────────────────────────┘126 ```1271285. **Recommend remediation** under each grouping, one line per129 rule class:130 * `prompt-injection-multilingual` → "Strip the override directive131 and audit who introduced it (git blame on the offending line)."132 * `html-comment-impersonation` → "Remove the HTML comment; treat133 every HTML comment that forges a system / admin / instruction134 directive — rule `html-comment-impersonation`, whose signature135 lives in `agent_config_patterns.py` — as an attack signal."136 * `base-url-override` → "Restore the original endpoint; if this is137 intentional, document it in a separate non-agent-context file."138 * `cross-skill-shadowing` → "Skill descriptions must not mandate139 other skills; convert to suggestion or remove."140 * `exfil-webhook-sink` → "Block-list the host; rotate any credential141 mentioned nearby in the same file."1421436. **Report one summary line** to stdout in addition to the table:144145 ```text146 skill-bundle-audit: 7 finding(s) across 4 file(s) [3 CRITICAL, 2 HIGH, 2 MEDIUM]. Worst: CLAUDE.md:42 prompt-injection-multilingual.147 ```148149## Output150151A grouped table of findings to the user PLUS one summary line. With152`--json`, ONE finding per line to stdout (no table). With `--strict`,153exit 1 if any CRITICAL/HIGH finding fired (so CI can wrap this skill154as a gate).155156## Error Handling157158* No agent-context files found → `skill-bundle-audit: 0 file(s)159 matched the agent-context inventory under <path>`; exit 0.160* A file is unreadable (permission, binary, broken symlink) → skip161 silently; the scan keeps running. Count the skips in the summary162 line.163* The patterns module fails to import → report `FAILED: cannot import164 agent_config_patterns from <path>` and exit 2.165166## Examples167168```text169User: /janitor-skill-bundle-audit170User: scan my skills for prompt injection171User: audit CLAUDE.md and .cursorrules for attacks172User: check .claude/agents/ for skill hijack patterns173User: /janitor-skill-bundle-audit --severity HIGH --strict174User: /janitor-skill-bundle-audit --path skills/ --json175```176177## Scope178179This skill is the **content** audit: it looks for attacker directives180INSIDE agent-context files. The complementary surfaces:181182* `ai-context-poisoning` heartbeat detector — audits *installed183 packages* (node_modules / site-packages) for postinstall code that184 *writes to* an agent-context file. Different threat, same surface.185* `janitor-github-workflow-doctor` — audits `.github/workflows/` for186 workflow-level attacks (template injection, missing pinning, etc.).187* `janitor-supply-chain-watcher` — audits lockfiles for known-vuln188 advisories.189190The four together — workflow + lockfile + installed-package +191agent-context-content — cover the supply-chain → agent-runtime192attack surface end-to-end.193194## Resources195196* [`scripts/lib/agent_config_patterns.py`](../../scripts/lib/agent_config_patterns.py)197 — the rule catalogue + `scan_text()` entry point.198* [`scripts/lib/security_helpers.py`](../../scripts/lib/security_helpers.py)199 — `agent_context_files()` inventory + OWASP Agentic Top-10 taxonomy.200* [`scripts/detectors/ai-context-poisoning.py`](../../scripts/detectors/ai-context-poisoning.py)201 — heartbeat companion detector.202203## Checklist204205Copy this checklist and track your progress:206207* [ ] Pre-flight: project root resolved (worktree-safe)208* [ ] Pre-flight: self-scan guard fires on janitor's own repo209* [ ] Enumerated agent-context files via `agent_context_files()` inventory210* [ ] Scanned each file via `agent_config_patterns.scan_text()`211* [ ] Grouped findings by severity + file212* [ ] Rendered remediation hint per rule class213* [ ] Emitted one summary line214* [ ] Honoured `--json` / `--severity` / `--strict` arguments215216---217> Source: [Emasoft/ai-maestro-janitor](https://github.com/Emasoft/ai-maestro-janitor) — distributed by [TomeVault](https://tomevault.io).218<!-- tomevault:4.0:skill_md:2026-06-15 -->