ghostrules 0.2.0__tar.gz → 0.3.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 (225) hide show
  1. {ghostrules-0.2.0 → ghostrules-0.3.0}/.ghostrules.lock +7 -7
  2. {ghostrules-0.2.0 → ghostrules-0.3.0}/.ghostrules.toml +2 -0
  3. ghostrules-0.3.0/.github/workflows/corpus.yml +79 -0
  4. ghostrules-0.3.0/.github/workflows/pages.yml +48 -0
  5. {ghostrules-0.2.0 → ghostrules-0.3.0}/.gitignore +6 -0
  6. ghostrules-0.3.0/CHANGELOG.md +203 -0
  7. {ghostrules-0.2.0 → ghostrules-0.3.0}/CLAUDE.md +3 -3
  8. {ghostrules-0.2.0 → ghostrules-0.3.0}/CONTRIBUTING.md +6 -4
  9. {ghostrules-0.2.0 → ghostrules-0.3.0}/PKG-INFO +53 -20
  10. {ghostrules-0.2.0 → ghostrules-0.3.0}/README.md +47 -19
  11. {ghostrules-0.2.0 → ghostrules-0.3.0}/action.yml +2 -4
  12. {ghostrules-0.2.0 → ghostrules-0.3.0}/demo/README.md +1 -0
  13. ghostrules-0.3.0/demo/imports.gif +0 -0
  14. {ghostrules-0.2.0 → ghostrules-0.3.0}/docs/AGENT_MODELS.md +19 -3
  15. {ghostrules-0.2.0 → ghostrules-0.3.0}/docs/DECISIONS.md +266 -2
  16. {ghostrules-0.2.0 → ghostrules-0.3.0}/docs/RULES.md +92 -14
  17. {ghostrules-0.2.0/docs → ghostrules-0.3.0/docs/design}/SPEC.md +1 -1
  18. {ghostrules-0.2.0/docs → ghostrules-0.3.0/docs/design}/v0.2/PLAN.md +1 -1
  19. {ghostrules-0.2.0 → ghostrules-0.3.0}/pyproject.toml +9 -0
  20. ghostrules-0.3.0/release-notes.md +50 -0
  21. ghostrules-0.3.0/scripts/build_site.py +41 -0
  22. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/__init__.py +1 -1
  23. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/agents/base.py +79 -17
  24. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/agents/claude_code.py +6 -11
  25. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/agents/cursor.py +1 -9
  26. ghostrules-0.3.0/src/ghostrules/agents/repo.py +223 -0
  27. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/contextscan.py +6 -2
  28. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/imports.py +9 -3
  29. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/lock.py +13 -3
  30. ghostrules-0.3.0/src/ghostrules/playground.py +151 -0
  31. ghostrules-0.3.0/src/ghostrules/reporters/context.py +161 -0
  32. ghostrules-0.2.0/src/ghostrules/reporters/context.py → ghostrules-0.3.0/src/ghostrules/reporters/context_data.py +10 -148
  33. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/reporters/markdown.py +2 -1
  34. ghostrules-0.3.0/src/ghostrules/reveal.py +37 -0
  35. ghostrules-0.3.0/src/ghostrules/reveal_data.py +107 -0
  36. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/context_rules.py +71 -12
  37. ghostrules-0.3.0/src/ghostrules/rules/directive.py +211 -0
  38. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/injection.py +229 -23
  39. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/known_installers.py +2 -0
  40. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/patterns.py +42 -11
  41. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/unicode.py +23 -2
  42. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/scancontext.py +2 -0
  43. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/scanner.py +11 -4
  44. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/sensitive.py +10 -1
  45. ghostrules-0.3.0/study/analyze.py +278 -0
  46. ghostrules-0.3.0/study/charts/01-adoption-dark.svg +245 -0
  47. ghostrules-0.3.0/study/charts/01-adoption-light.svg +245 -0
  48. ghostrules-0.3.0/study/charts/02-tools-per-repo-dark.svg +143 -0
  49. ghostrules-0.3.0/study/charts/02-tools-per-repo-light.svg +143 -0
  50. ghostrules-0.3.0/study/charts/03-claude-agents-dark.svg +126 -0
  51. ghostrules-0.3.0/study/charts/03-claude-agents-light.svg +126 -0
  52. ghostrules-0.3.0/study/charts/04-what-agents-read-dark.svg +143 -0
  53. ghostrules-0.3.0/study/charts/04-what-agents-read-light.svg +143 -0
  54. ghostrules-0.3.0/study/charts/05-risk-dark.svg +126 -0
  55. ghostrules-0.3.0/study/charts/05-risk-light.svg +126 -0
  56. ghostrules-0.3.0/study/charts/06-precision-dark.svg +432 -0
  57. ghostrules-0.3.0/study/charts/06-precision-light.svg +432 -0
  58. ghostrules-0.3.0/study/charts.py +283 -0
  59. ghostrules-0.3.0/study/data/adoption_families.csv +13 -0
  60. ghostrules-0.3.0/study/data/claude_agents_relation.csv +5 -0
  61. ghostrules-0.3.0/study/data/conditional_rules.csv +5 -0
  62. ghostrules-0.3.0/study/data/findings_by_rule.csv +33 -0
  63. ghostrules-0.3.0/study/data/headline.json +18 -0
  64. ghostrules-0.3.0/study/data/imports.csv +6 -0
  65. ghostrules-0.3.0/study/data/instruction_file_lines.csv +6 -0
  66. ghostrules-0.3.0/study/data/numbers.csv +12 -0
  67. ghostrules-0.3.0/study/data/precision_by_rule.csv +15 -0
  68. ghostrules-0.3.0/study/data/precision_runs.csv +5 -0
  69. ghostrules-0.3.0/study/data/risk_indicators.csv +7 -0
  70. ghostrules-0.3.0/study/data/sample.csv +3 -0
  71. ghostrules-0.3.0/study/data/skills_concentration.csv +7 -0
  72. ghostrules-0.3.0/study/data/tools_per_repo.csv +8 -0
  73. ghostrules-0.3.0/study/features.py +138 -0
  74. ghostrules-0.3.0/study/label_classes.py +133 -0
  75. ghostrules-0.3.0/study/labels.toml +2646 -0
  76. ghostrules-0.3.0/study/report.md +166 -0
  77. ghostrules-0.3.0/study/report.py +211 -0
  78. ghostrules-0.3.0/tests/corpus_fakes.py +103 -0
  79. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_agents_claude.py +31 -11
  80. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_agents_repo.py +18 -5
  81. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_ci_files.py +22 -2
  82. ghostrules-0.3.0/tests/test_core_without_cli_deps.py +74 -0
  83. ghostrules-0.3.0/tests/test_corpus_packaging.py +31 -0
  84. ghostrules-0.3.0/tests/test_corpus_tools.py +634 -0
  85. ghostrules-0.3.0/tests/test_directive.py +345 -0
  86. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_docs.py +21 -5
  87. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_imports.py +1 -1
  88. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_lock.py +5 -4
  89. ghostrules-0.3.0/tests/test_playground.py +62 -0
  90. ghostrules-0.3.0/tests/test_precision_study.py +164 -0
  91. ghostrules-0.3.0/tests/test_precision_v021.py +162 -0
  92. ghostrules-0.3.0/tests/test_precision_v030.py +343 -0
  93. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_scan_imports.py +14 -12
  94. ghostrules-0.3.0/tests/test_study.py +138 -0
  95. ghostrules-0.3.0/tests/test_symlink_alias.py +422 -0
  96. ghostrules-0.3.0/tests/web/test_playground_smoke.py +99 -0
  97. ghostrules-0.3.0/web/app.js +376 -0
  98. ghostrules-0.3.0/web/index.html +77 -0
  99. ghostrules-0.3.0/web/style.css +101 -0
  100. ghostrules-0.2.0/CHANGELOG.md +0 -104
  101. ghostrules-0.2.0/release-notes.md +0 -42
  102. ghostrules-0.2.0/src/ghostrules/agents/repo.py +0 -115
  103. ghostrules-0.2.0/src/ghostrules/reveal.py +0 -90
  104. {ghostrules-0.2.0 → ghostrules-0.3.0}/.gitattributes +0 -0
  105. {ghostrules-0.2.0 → ghostrules-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  106. {ghostrules-0.2.0 → ghostrules-0.3.0}/.github/ISSUE_TEMPLATE/false-positive.yml +0 -0
  107. {ghostrules-0.2.0 → ghostrules-0.3.0}/.github/ISSUE_TEMPLATE/new-rule.yml +0 -0
  108. {ghostrules-0.2.0 → ghostrules-0.3.0}/.github/workflows/ci.yml +0 -0
  109. {ghostrules-0.2.0 → ghostrules-0.3.0}/.github/workflows/release.yml +0 -0
  110. {ghostrules-0.2.0 → ghostrules-0.3.0}/.github/workflows/self-test.yml +0 -0
  111. {ghostrules-0.2.0 → ghostrules-0.3.0}/.pre-commit-hooks.yaml +0 -0
  112. {ghostrules-0.2.0 → ghostrules-0.3.0}/LICENSE +0 -0
  113. {ghostrules-0.2.0 → ghostrules-0.3.0}/SECURITY.md +0 -0
  114. {ghostrules-0.2.0 → ghostrules-0.3.0}/demo/demo.gif +0 -0
  115. {ghostrules-0.2.0 → ghostrules-0.3.0}/demo/demo.tape +0 -0
  116. {ghostrules-0.2.0 → ghostrules-0.3.0}/demo/imports.tape +0 -0
  117. {ghostrules-0.2.0 → ghostrules-0.3.0}/demo/lock.tape +0 -0
  118. {ghostrules-0.2.0/docs → ghostrules-0.3.0/docs/design}/PLAN.md +0 -0
  119. {ghostrules-0.2.0/docs → ghostrules-0.3.0/docs/design}/v0.1.1/GR012-FIX.md +0 -0
  120. {ghostrules-0.2.0/docs → ghostrules-0.3.0/docs/design}/v0.2/RULES.md +0 -0
  121. {ghostrules-0.2.0/docs → ghostrules-0.3.0/docs/design}/v0.2/SPEC.md +0 -0
  122. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/clean-repo/.claude/settings.json +0 -0
  123. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/clean-repo/.mcp.json +0 -0
  124. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/clean-repo/AGENTS.md +0 -0
  125. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/clean-repo/CLAUDE.md +0 -0
  126. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/clean-repo/README.md +0 -0
  127. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/.claude/rules/api.md +0 -0
  128. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/.env +0 -0
  129. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/AGENTS.md +0 -0
  130. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/CLAUDE.md +0 -0
  131. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/README.md +0 -0
  132. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/docs/setup.md +0 -0
  133. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/docs/style.md +0 -0
  134. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/shared/team-rules.md +0 -0
  135. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/src/api/users.ts +0 -0
  136. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/import-chain-repo/src/legacy/CLAUDE.md +0 -0
  137. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/poisoned-repo/.claude/settings.json +0 -0
  138. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/poisoned-repo/.claude/skills/setup/SKILL.md +0 -0
  139. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/poisoned-repo/.cursorrules +0 -0
  140. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/poisoned-repo/.github/copilot-instructions.md +0 -0
  141. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/poisoned-repo/.mcp.json +0 -0
  142. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/poisoned-repo/AGENTS.md +0 -0
  143. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/poisoned-repo/CLAUDE.md +0 -0
  144. {ghostrules-0.2.0 → ghostrules-0.3.0}/examples/poisoned-repo/README.md +0 -0
  145. {ghostrules-0.2.0 → ghostrules-0.3.0}/scripts/changelog_section.py +0 -0
  146. {ghostrules-0.2.0 → ghostrules-0.3.0}/scripts/gen_rules_table.py +0 -0
  147. {ghostrules-0.2.0 → ghostrules-0.3.0}/scripts/make_demo.py +0 -0
  148. {ghostrules-0.2.0 → ghostrules-0.3.0}/scripts/make_fixtures.py +0 -0
  149. {ghostrules-0.2.0 → ghostrules-0.3.0}/scripts/set_owner.py +0 -0
  150. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/_compat.py +0 -0
  151. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/agents/__init__.py +0 -0
  152. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/agents/copilot_cli.py +0 -0
  153. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/agents/generic.py +0 -0
  154. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/clean.py +0 -0
  155. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/cli.py +0 -0
  156. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/config.py +0 -0
  157. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/context_command.py +0 -0
  158. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/globs.py +0 -0
  159. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/jsonloc.py +0 -0
  160. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/markdown_utils.py +0 -0
  161. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/mcp_config.py +0 -0
  162. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/models.py +0 -0
  163. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/py.typed +0 -0
  164. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/reporters/__init__.py +0 -0
  165. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/reporters/common.py +0 -0
  166. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/reporters/json_.py +0 -0
  167. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/reporters/lock.py +0 -0
  168. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/reporters/sarif.py +0 -0
  169. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/reporters/table.py +0 -0
  170. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/__init__.py +0 -0
  171. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/agent_settings.py +0 -0
  172. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/helpers.py +0 -0
  173. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/mcp.py +0 -0
  174. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/rules/registry.py +0 -0
  175. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/secrets.py +0 -0
  176. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/targets.py +0 -0
  177. {ghostrules-0.2.0 → ghostrules-0.3.0}/src/ghostrules/unicode_utils.py +0 -0
  178. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/conftest.py +0 -0
  179. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/data/sarif-schema-2.1.0.json +0 -0
  180. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr001_flag_forged/AGENTS.md +0 -0
  181. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr001_flag_ok/AGENTS.md +0 -0
  182. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr001_tags/AGENTS.md +0 -0
  183. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr002_bidi/AGENTS.md +0 -0
  184. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr002_rtl_medium/AGENTS.md +0 -0
  185. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_bom_midfile/AGENTS.md +0 -0
  186. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_bom_ok/AGENTS.md +0 -0
  187. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_emoji_zwj_ok/AGENTS.md +0 -0
  188. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_indic_ok/AGENTS.md +0 -0
  189. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_persian_ok/AGENTS.md +0 -0
  190. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_soft_hyphen/AGENTS.md +0 -0
  191. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_zw_file_high/AGENTS.md +0 -0
  192. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_zw_line_high/AGENTS.md +0 -0
  193. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr003_zw_medium/AGENTS.md +0 -0
  194. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr004_vs_bytes/AGENTS.md +0 -0
  195. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr004_vs_ok/AGENTS.md +0 -0
  196. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr005_benign_ok/CLAUDE.md +0 -0
  197. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr005_comment_imperative/CLAUDE.md +0 -0
  198. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr005_comment_injection/CLAUDE.md +0 -0
  199. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr005_comment_multiline/CLAUDE.md +0 -0
  200. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr005_in_code_block_ok/CLAUDE.md +0 -0
  201. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr005_short_ok/CLAUDE.md +0 -0
  202. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr005_unterminated/CLAUDE.md +0 -0
  203. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr006_ok/AGENTS.md +0 -0
  204. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr006_url/AGENTS.md +0 -0
  205. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/fixtures/unicode/gr006_word/AGENTS.md +0 -0
  206. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/secret_samples.py +0 -0
  207. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_agents_others.py +0 -0
  208. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_cli.py +0 -0
  209. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_config.py +0 -0
  210. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_context_command.py +0 -0
  211. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_demo.py +0 -0
  212. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_globs.py +0 -0
  213. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_markdown_utils.py +0 -0
  214. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_mcp_config.py +0 -0
  215. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_models.py +0 -0
  216. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_patterns.py +0 -0
  217. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_reporters.py +0 -0
  218. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_rules_config.py +0 -0
  219. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_rules_injection.py +0 -0
  220. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_rules_unicode.py +0 -0
  221. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_scanner.py +0 -0
  222. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_secret_masking.py +0 -0
  223. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_secrets.py +0 -0
  224. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_targets.py +0 -0
  225. {ghostrules-0.2.0 → ghostrules-0.3.0}/tests/test_unicode_utils.py +0 -0
@@ -3,22 +3,22 @@
3
3
  "claude-code": [
4
4
  {
5
5
  "conditions": [],
6
- "lines": "2a3d3f1fb26381e9 e3b0c44298fc1c14 ddabff43c0a4efe5 6aa552cc1598dcb7 be186e6b970ab643 fb78540ec32b52f9 e3b0c44298fc1c14 d393a517d5cbec9f e3b0c44298fc1c14 85b7ae55df076ab0 2f6995b0ecd75c8d e3b0c44298fc1c14 5f2235a1b010f82c 030e09c253572539 ba2cd86d7e76eff5 3b81b7bc3295aca9 e3b0c44298fc1c14 782729a560418c17 36a8e590ff4c085c e3b0c44298fc1c14 9f4e3cc48918efc0 efeeec23855e4659 2f23b1dcff8841e8 c3abd357eb574eb6 76881ea91e0040bd e3b0c44298fc1c14 36cd3f2bb454f575 ad332335cfaa348c a58865feac87f52b 052081156966d443 5654f9fffd4a2a7a bd0710fd0315ecbb e4d8cec4158bb770 f1b901847390b0ed e3b0c44298fc1c14 966a8946115b377a 5b59b2f749ffabac 9bf96d3c4c385073 43d8bbb16372109c 5226bbb3f5f9e13d add6d744d3ac0c02 3dafca094d79548b 21a3cd5236dfd642 79e12b3b50117933 9cb81024d1184666 f1e7b346d65f35c5 e3b0c44298fc1c14 60ab6be4219694a0 3a1c5dcc5281208a e256f3fb8239bf8c 10d8918d097bebb5 ff85c33a894fab39 10f274c7d25257d2 6216cf539fa87bd8 e3b0c44298fc1c14 7136000e494604c0 8e5a481cbb86370a f75f6ee8f0d942eb 1f59b655e6071114 da29957f3e8be475",
6
+ "lines": "2a3d3f1fb26381e9 e3b0c44298fc1c14 ddabff43c0a4efe5 6aa552cc1598dcb7 be186e6b970ab643 fb78540ec32b52f9 e3b0c44298fc1c14 d393a517d5cbec9f e3b0c44298fc1c14 85b7ae55df076ab0 2f6995b0ecd75c8d e3b0c44298fc1c14 5f2235a1b010f82c 56a4406ad70b7f0a ba2cd86d7e76eff5 a7b723b74ee03fc8 e3b0c44298fc1c14 782729a560418c17 36a8e590ff4c085c e3b0c44298fc1c14 9f4e3cc48918efc0 efeeec23855e4659 2f23b1dcff8841e8 c3abd357eb574eb6 76881ea91e0040bd e3b0c44298fc1c14 36cd3f2bb454f575 ad332335cfaa348c a58865feac87f52b 052081156966d443 5654f9fffd4a2a7a bd0710fd0315ecbb e4d8cec4158bb770 f1b901847390b0ed e3b0c44298fc1c14 966a8946115b377a 5b59b2f749ffabac 9bf96d3c4c385073 43d8bbb16372109c 5226bbb3f5f9e13d add6d744d3ac0c02 3dafca094d79548b 21a3cd5236dfd642 79e12b3b50117933 9cb81024d1184666 f1e7b346d65f35c5 e3b0c44298fc1c14 60ab6be4219694a0 3a1c5dcc5281208a e256f3fb8239bf8c 10d8918d097bebb5 ff85c33a894fab39 10f274c7d25257d2 6216cf539fa87bd8 e3b0c44298fc1c14 7136000e494604c0 aa8d4cf82644ac63 f75f6ee8f0d942eb 1f59b655e6071114 da29957f3e8be475",
7
7
  "load": "always",
8
8
  "path": "CLAUDE.md",
9
- "raw_sha256": "dc7776630751bba0d3c39e83e96b3b6dae1de7cd5630b49e3b0f7853bbbf1f3f",
10
- "seen_sha256": "dc7776630751bba0d3c39e83e96b3b6dae1de7cd5630b49e3b0f7853bbbf1f3f",
9
+ "raw_sha256": "c21de91b03da043818a3d8a83418a2e4ce449e58e1e6a5906e7d7c2212ddfca5",
10
+ "seen_sha256": "c21de91b03da043818a3d8a83418a2e4ce449e58e1e6a5906e7d7c2212ddfca5",
11
11
  "via": []
12
12
  }
13
13
  ],
14
14
  "copilot-cli": [
15
15
  {
16
16
  "conditions": [],
17
- "lines": "2a3d3f1fb26381e9 e3b0c44298fc1c14 ddabff43c0a4efe5 6aa552cc1598dcb7 be186e6b970ab643 fb78540ec32b52f9 e3b0c44298fc1c14 d393a517d5cbec9f e3b0c44298fc1c14 85b7ae55df076ab0 2f6995b0ecd75c8d e3b0c44298fc1c14 5f2235a1b010f82c 030e09c253572539 ba2cd86d7e76eff5 3b81b7bc3295aca9 e3b0c44298fc1c14 782729a560418c17 36a8e590ff4c085c e3b0c44298fc1c14 9f4e3cc48918efc0 efeeec23855e4659 2f23b1dcff8841e8 c3abd357eb574eb6 76881ea91e0040bd e3b0c44298fc1c14 36cd3f2bb454f575 ad332335cfaa348c a58865feac87f52b 052081156966d443 5654f9fffd4a2a7a bd0710fd0315ecbb e4d8cec4158bb770 f1b901847390b0ed e3b0c44298fc1c14 966a8946115b377a 5b59b2f749ffabac 9bf96d3c4c385073 43d8bbb16372109c 5226bbb3f5f9e13d add6d744d3ac0c02 3dafca094d79548b 21a3cd5236dfd642 79e12b3b50117933 9cb81024d1184666 f1e7b346d65f35c5 e3b0c44298fc1c14 60ab6be4219694a0 3a1c5dcc5281208a e256f3fb8239bf8c 10d8918d097bebb5 ff85c33a894fab39 10f274c7d25257d2 6216cf539fa87bd8 e3b0c44298fc1c14 7136000e494604c0 8e5a481cbb86370a f75f6ee8f0d942eb 1f59b655e6071114 da29957f3e8be475",
17
+ "lines": "2a3d3f1fb26381e9 e3b0c44298fc1c14 ddabff43c0a4efe5 6aa552cc1598dcb7 be186e6b970ab643 fb78540ec32b52f9 e3b0c44298fc1c14 d393a517d5cbec9f e3b0c44298fc1c14 85b7ae55df076ab0 2f6995b0ecd75c8d e3b0c44298fc1c14 5f2235a1b010f82c 56a4406ad70b7f0a ba2cd86d7e76eff5 a7b723b74ee03fc8 e3b0c44298fc1c14 782729a560418c17 36a8e590ff4c085c e3b0c44298fc1c14 9f4e3cc48918efc0 efeeec23855e4659 2f23b1dcff8841e8 c3abd357eb574eb6 76881ea91e0040bd e3b0c44298fc1c14 36cd3f2bb454f575 ad332335cfaa348c a58865feac87f52b 052081156966d443 5654f9fffd4a2a7a bd0710fd0315ecbb e4d8cec4158bb770 f1b901847390b0ed e3b0c44298fc1c14 966a8946115b377a 5b59b2f749ffabac 9bf96d3c4c385073 43d8bbb16372109c 5226bbb3f5f9e13d add6d744d3ac0c02 3dafca094d79548b 21a3cd5236dfd642 79e12b3b50117933 9cb81024d1184666 f1e7b346d65f35c5 e3b0c44298fc1c14 60ab6be4219694a0 3a1c5dcc5281208a e256f3fb8239bf8c 10d8918d097bebb5 ff85c33a894fab39 10f274c7d25257d2 6216cf539fa87bd8 e3b0c44298fc1c14 7136000e494604c0 aa8d4cf82644ac63 f75f6ee8f0d942eb 1f59b655e6071114 da29957f3e8be475",
18
18
  "load": "always",
19
19
  "path": "CLAUDE.md",
20
- "raw_sha256": "dc7776630751bba0d3c39e83e96b3b6dae1de7cd5630b49e3b0f7853bbbf1f3f",
21
- "seen_sha256": "dc7776630751bba0d3c39e83e96b3b6dae1de7cd5630b49e3b0f7853bbbf1f3f",
20
+ "raw_sha256": "c21de91b03da043818a3d8a83418a2e4ce449e58e1e6a5906e7d7c2212ddfca5",
21
+ "seen_sha256": "c21de91b03da043818a3d8a83418a2e4ce449e58e1e6a5906e7d7c2212ddfca5",
22
22
  "via": []
23
23
  }
24
24
  ],
@@ -26,6 +26,6 @@
26
26
  "generic": []
27
27
  },
28
28
  "configs": [],
29
- "generator": "ghostrules 0.2.0",
29
+ "generator": "ghostrules 0.3.0",
30
30
  "lock_version": 1
31
31
  }
@@ -7,4 +7,6 @@ exclude = [
7
7
  ".mypy_cache/**",
8
8
  ".ruff_cache/**",
9
9
  ".pytest_cache/**",
10
+ "corpus/.cache/**", # API responses and clones are other people's text
11
+ "corpus/results/**",
10
12
  ]
@@ -0,0 +1,79 @@
1
+ name: Corpus
2
+
3
+ # Runs ghostrules against the public repositories listed in corpus/repos.toml (the only networked
4
+ # part of the project). Never on pull requests.
5
+ on:
6
+ schedule:
7
+ - cron: "17 4 * * 1"
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: corpus
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ corpus:
19
+ runs-on: ubuntu-latest
20
+ timeout-minutes: 120
21
+ env:
22
+ GH_TOKEN: ${{ github.token }}
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+ - name: Install ghostrules from this checkout
29
+ run: pip install -e ".[dev]"
30
+ - name: Scan the regression tier
31
+ run: python corpus/run.py --tier regression
32
+ - name: Gate against labels.toml
33
+ run: python corpus/check.py
34
+ - name: Aggregate files (badge and summary, counts only)
35
+ if: always() && hashFiles('corpus/results/latest-summary.json') != ''
36
+ run: python corpus/badge.py --out corpus-public
37
+ - name: Upload results
38
+ if: always()
39
+ uses: actions/upload-artifact@v4
40
+ with:
41
+ name: corpus-results
42
+ path: |
43
+ corpus/results
44
+ corpus-public
45
+ retention-days: 30
46
+ if-no-files-found: ignore
47
+
48
+ publish:
49
+ # Only the default branch publishes; the badge reads corpus/badge.json from gh-pages.
50
+ needs: corpus
51
+ if: always() && needs.corpus.result != 'skipped' && github.ref == 'refs/heads/main'
52
+ runs-on: ubuntu-latest
53
+ permissions:
54
+ contents: write
55
+ steps:
56
+ - uses: actions/download-artifact@v4
57
+ with:
58
+ name: corpus-results
59
+ path: artifact
60
+ - name: Publish to gh-pages
61
+ env:
62
+ GH_TOKEN: ${{ github.token }}
63
+ REPO: ${{ github.repository }}
64
+ run: |
65
+ set -euo pipefail
66
+ test -f artifact/corpus-public/badge.json
67
+ git clone --quiet --depth 1 --branch gh-pages \
68
+ "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" site \
69
+ || { mkdir site && git -C site init --quiet -b gh-pages \
70
+ && git -C site remote add origin \
71
+ "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git"; }
72
+ mkdir -p site/corpus
73
+ cp artifact/corpus-public/badge.json artifact/corpus-public/summary.json site/corpus/
74
+ cd site
75
+ git config user.name "github-actions[bot]"
76
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
77
+ git add corpus
78
+ git diff --cached --quiet || git commit --quiet -m "corpus: weekly summary"
79
+ git push origin gh-pages
@@ -0,0 +1,48 @@
1
+ name: Playground
2
+
3
+ # Builds the browser playground (web/ plus the wheel of this checkout) and deploys it to GitHub Pages.
4
+ on:
5
+ push:
6
+ tags: ["v*"]
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: pages
14
+ cancel-in-progress: false
15
+
16
+ jobs:
17
+ build:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
21
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
22
+ with:
23
+ python-version: "3.12"
24
+ - name: Install
25
+ run: pip install -e ".[dev,web-test]"
26
+ - name: Build the site
27
+ run: python scripts/build_site.py _site
28
+ - name: Playwright smoke test on the built site
29
+ run: |
30
+ playwright install --with-deps chromium
31
+ pytest -q tests/web
32
+ - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
33
+ - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
34
+ with:
35
+ path: _site
36
+
37
+ deploy:
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+ permissions:
41
+ pages: write
42
+ id-token: write
43
+ environment:
44
+ name: github-pages
45
+ url: ${{ steps.deployment.outputs.page_url }}
46
+ steps:
47
+ - id: deployment
48
+ uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
@@ -12,3 +12,9 @@ dist/
12
12
  htmlcov/
13
13
  *.ghostrules.bak
14
14
  ghostrules.sarif
15
+
16
+ # corpus tooling
17
+ corpus/.cache/
18
+ corpus/results/*
19
+ !corpus/results/latest-summary.json
20
+ _site/
@@ -0,0 +1,203 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.3.0] - 2026-10-28
9
+
10
+ Measurement release. ghostrules now runs against real repositories every week, a browser playground
11
+ runs the real scanner without installing anything, and the first full study of popular repositories
12
+ drove a round of precision fixes. On the 300-repository regression tier 0.2.1 reported 323 findings
13
+ (84 critical, 156 high); 0.3.0 reports 212 (0 critical, 53 high), every critical and high finding
14
+ reviewed. On the 1,283-repository study tier the first run reported 1,072 findings (13 critical, 296
15
+ high); after the fixes 945 (12 critical, 252 high), all of medium and above triaged.
16
+
17
+ ### Added
18
+ - **Playground** (`web/`, `.github/workflows/pages.yml`): a static page that runs the real scanner in
19
+ the browser with Pyodide. Paste a file, drop a folder (agent files and what they import, up to
20
+ 5 MB) or name a public GitHub repo (the only tab that uses the network). Shows what you see versus
21
+ what the agent reads, the context tree per agent and downloadable JSON, Markdown and SARIF reports.
22
+ Playwright smoke test; deployed to GitHub Pages on release tags.
23
+ - **Corpus tooling** (`corpus/`, outside the package and the wheel): `discover.py`, `run.py`,
24
+ `check.py`, `triage.py`, `badge.py`, `filestats.py`, a weekly `corpus.yml` workflow and an endpoint
25
+ badge in the README. Findings are labelled by fingerprint in `corpus/labels.toml`; labels never
26
+ name a repository for a reported finding.
27
+ - **Study tooling** (`study/`): per-repository features, aggregate CSVs, SVG charts in light and dark,
28
+ the triage classes and `study/report.md`, generated from the CSVs.
29
+ - `ghostrules.playground`, `ghostrules.reveal_data` and `ghostrules.reporters.context_data`: the core
30
+ (scanner, rules, context engine, JSON, SARIF and Markdown reporters) now imports without `typer`
31
+ and `rich`; the terminal output is unchanged.
32
+ - `action.yml` is named `ghostrules scan` and its description fits the Marketplace limit (125
33
+ characters).
34
+
35
+ ### Fixed
36
+ - GR003: a lone U+200B between two letters of Khmer, Thai, Lao or Myanmar text is that script's word
37
+ separator and is ignored.
38
+ - GR005: a hidden comment is `medium` when it carries an instruction cue, `low` when it only mentions
39
+ agents, `high` unchanged for injection matches.
40
+ - GR010: `override system|safety` needs a following noun, and defensive text that quotes the phrase
41
+ is `low`.
42
+ - GR011: `without asking the user` is no longer concealment; a prohibition earlier in the clause, a
43
+ safety claim ("does not silently install"), a description in the third person ("silently runs") and
44
+ a modal before it are not reported.
45
+ - GR012: naming the `curl | sh` pattern without a target is `low`; a `curl` in prose no longer joins
46
+ the next code block; `| bash script.sh` is not a data pipe into the shell.
47
+ - GR013: needs a verb, a secret and a URL in the same clause; prohibitions, loopback URLs, `POST`
48
+ and `.post(`, "send the user to" and "send X only to the fixed origin" are not exfiltration.
49
+ - GR014: the verb must govern the path across no paragraph break, clause connector (`before`,
50
+ `after`, `when`), passive or noun use; `### Output` is a heading; `.env.default` and copying a
51
+ dotenv file are not reads.
52
+ - GR015: runs inside URLs, Subresource Integrity digests and low-entropy hex (`AAAA…`) are skipped.
53
+ - GR016: a prohibition ("Avoid `--yolo`"), a table row and a list item that documents a flag are not
54
+ an instruction.
55
+ - A bare `@/`, `@~/`, `@./` or `@../` is a path prefix, not an import.
56
+
57
+ ### Known limits
58
+ - Splitting an exfiltration instruction across two sentences avoids the GR013 prose match, and a
59
+ concealment right after an unrelated prohibition in the same clause is not reported.
60
+
61
+ ## [0.2.1] - 2026-10-28
62
+
63
+ Precision release. A scan of 12 popular public repositories with 0.2.0 found no critical issue,
64
+ but every `high` finding was a false positive, and `context` was wrong for the most common real
65
+ setup. On those repositories 0.2.0 reported 9 high, 18 medium and 8 low findings; 0.2.1 reports
66
+ 2 medium and 2 low, with no high, no critical and no GR025.
67
+
68
+ ### Added
69
+ - `ghostrules context --format json` entries carry `alias_of` (and `link_problem`) when a symlink is
70
+ involved; lock entries carry `link` for an alias. Both keys are omitted otherwise.
71
+ - `ghostrules.rules.directive.classify`: the helper that tells directive, descriptive and example
72
+ text apart (used by GR011, GR014 and GR016).
73
+
74
+ ### Fixed
75
+ - A symbolic link whose final target is a regular file inside the repository is now an **alias**:
76
+ `CLAUDE.md -> AGENTS.md` (the setup Claude Code documents) is loaded under the link's name,
77
+ `context` shows `CLAUDE.md → AGENTS.md (symlink)` and the `AGENTS.md` entry as already read, the
78
+ content is scanned once (findings say `via CLAUDE.md (symlink)`), and `lock` records the link and
79
+ its target. Chains of up to 8 links and in-repository directory links work the same way. Nothing
80
+ is opened before the resolved path is known to be inside the repository.
81
+ GR025 now fires only for links that leave the repository (`high`) or are broken, cyclic or too
82
+ long (`low`), instead of one `medium` per link, and adds an `info` note for a link checked out as
83
+ a text file (Windows with `core.symlinks=false`).
84
+ - GR022 no longer reports npm scopes and mentions (`@n8n/design-system`, `@cline/llms`, `@types/node`)
85
+ as imports that do not load. It reports a token only if it has a file extension, starts with `./`,
86
+ `../`, `~/` or `/`, or its first directory exists next to the importing file.
87
+ - GR011, GR014 and GR016 tell instructions from descriptions and examples. A match in prose that
88
+ describes something ("The scripts read `~/.npmrc`", "The harness runs Claude with
89
+ `bypassPermissions`") is at most `medium`; in a fenced block, in quotes or after "e.g.", "such
90
+ as", "a skill that ..." it is at most `low`; imperative text and decoded hidden text keep their
91
+ severity. A lowered finding says why.
92
+ - GR011: "do not tell the user to/which/what/how ..." is no longer concealment.
93
+ - GR014: the verb must govern the path (same clause, six words at most) and template files
94
+ (`.env.example`, `*.example`, `.env.ai-surfaces.example`) never fire.
95
+ - GR012: `curl ... | python -c`, `| python -m`, `| python script.py`, `| sh -c '...'` use stdin as
96
+ data and are no longer reported; `| python`, `| python -` and `| sh` still are. The Claude Code
97
+ installer (`https://claude.ai/install.sh`) is a known installer. GR105 and GR107 share these
98
+ patterns and follow.
99
+
100
+ ### Changed
101
+ - README: the new tagline is the only one (the old one opens the first paragraph), the import-chain
102
+ GIF comes first and the hidden-Unicode GIF sits further down, both by absolute URL.
103
+ - The original `SPEC.md`, `PLAN.md` and the per-version design documents moved to `docs/design/`
104
+ (design history, not kept up to date). `RULES.md`, `AGENT_MODELS.md` and `DECISIONS.md` stay in
105
+ `docs/`.
106
+
107
+ ## [0.2.0] - 2026-10-28
108
+
109
+ Know exactly what your agent reads: `ghostrules` now models how each agent loads its instructions,
110
+ scans exactly that, shows it, and pins it.
111
+
112
+ ### Added
113
+ - GitHub Action inputs `follow-imports` (default `true`) and `lock-check` (default `false`). With
114
+ `lock-check` the action runs `ghostrules lock --check`, puts the differences in the job summary
115
+ under "What your agents read changed in this PR" and fails the job if there are any; new output
116
+ `lock-exit-code`.
117
+ - Config keys `follow_imports`, `agents` and `lockfile`.
118
+ - `python scripts/make_demo.py --lock-scenario DEST` and the tapes `demo/imports.tape` and
119
+ `demo/lock.tape` for the 0.2 GIFs.
120
+ - `ghostrules lock`: writes `.ghostrules.lock`, a deterministic record (sorted keys, no timestamps,
121
+ CRLF-normalised hashes) of every file in each modelled agent's context, how it is reached, and
122
+ the text the agent actually receives, plus a hash of every MCP and agent settings file.
123
+ `lock --check` recomputes it and shows what changed (added, changed, removed, and changes that
124
+ only touch text an agent strips), with invisible characters revealed; exit 0 up to date, 1
125
+ changed, 2 error. `--format text|markdown|json`, `--update`, `--lockfile`; config key `lockfile`.
126
+ - `ghostrules context [TARGET]`: shows what an agent reads when it works on a file (the files it
127
+ loads at session start, the rules its `paths`/`globs` select, the directories that load on
128
+ demand, the imports it follows and the files it skips, with the reason) and scans exactly that.
129
+ Output as a tree, JSON or Markdown; `--agent`, `--cwd`, `--root`, `--show-text`, `--no-scan`,
130
+ `--home`, `--fail-on`.
131
+ - `examples/import-chain-repo`: a CLAUDE.md that looks clean and reaches a hidden payload through two
132
+ imports, plus a `.env` import and one outside the repository.
133
+ - `scan` follows `@path` imports by default: files an instruction file imports are scanned as
134
+ instructions, and their findings carry the import chain (`via`), the agents that load them and the
135
+ tag `reached-via-import`. `--no-follow-imports` (or `follow_imports = false`) turns it off.
136
+ - Rules GR020 (external import), GR021 (sensitive file imported), GR022 (import that silently does
137
+ not load), GR023 (import cycle), GR024 (tag for payloads reached through an import) and GR025
138
+ (symlinked instruction source).
139
+ - The files each modelled agent loads (including `.claude/rules/**`) are scanned even when the
140
+ discovery globs do not list them. New config keys `follow_imports` and `agents`.
141
+ - JSON findings gain optional `via`, `agents` and `tags`; SARIF results gain `relatedLocations` for
142
+ the chain and `properties.agents`/`tags`; the table and Markdown reports show the chain.
143
+
144
+ - `docs/AGENT_MODELS.md`: how each agent loads its instructions, with the documentation it is based on
145
+ and what is still unverified; `CONTRIBUTING.md` explains how to add a model.
146
+ - `ghostrules` locks its own `CLAUDE.md` (`.ghostrules.lock`), checked in CI.
147
+
148
+ ### Changed
149
+ - GR005 is agent-aware: it says which agents see a hidden comment. Claude Code strips block-level
150
+ comments from CLAUDE.md files, so the finding is info when no configured agent sees the comment.
151
+
152
+ ## [0.1.1] - 2026-10-27
153
+
154
+ ### Changed
155
+ - GR012 (remote code execution pattern) now sets its severity from context instead of reporting
156
+ every `curl ... | sh` as critical. A well-known installer in a code block is low, in prose
157
+ medium; an unknown domain is high in a code block and critical in prose. Plain `http://`, IP
158
+ literals, non-default ports, collector domains, opaque payloads (`base64 -d | sh`,
159
+ `powershell -enc`) and text hidden by invisible characters stay critical. This stops the default
160
+ `--fail-on high` from failing on real repositories that document `uv` or `rustup`.
161
+ GR105 and GR107 (MCP server commands and hooks) are unchanged.
162
+
163
+ ### Added
164
+ - Built-in list of well-known installers (uv/ruff, rustup, Bun, Deno, pnpm, Poetry, Homebrew, nvm,
165
+ SDKMAN, Docker, Ollama, Volta), matched on the exact host and optional path prefix, and
166
+ `ghostrules rules --installers` to print it.
167
+ - `trust_known_installers` option (default `true`); `false` restores the 0.1.0 behaviour.
168
+ - Inline code span awareness for GR012 (`ScanTarget.code_span_at`).
169
+
170
+ ## [0.1.0] - 2026-10-27
171
+
172
+ First release.
173
+
174
+ ### Added
175
+ - Scanner for the files AI coding agents read and obey (`AGENTS.md`, `CLAUDE.md`, `.cursorrules`,
176
+ Copilot instructions, skills, MCP configs, agent settings and more), with `--home` for
177
+ user-level configs and `--all-files` for hidden Unicode anywhere in a tree.
178
+ - 21 rules. Hidden content: Unicode tag characters (GR001), bidirectional controls (GR002),
179
+ zero-width characters (GR003), variation-selector steganography (GR004), hidden HTML comments
180
+ (GR005), mixed-script homoglyphs (GR006). Malicious instructions, English and Spanish: instruction
181
+ override (GR010), concealment (GR011), remote code execution (GR012), data exfiltration (GR013),
182
+ sensitive file access (GR014), encoded payloads (GR015), safety bypass (GR016). Configuration:
183
+ hardcoded secrets (GR101), unpinned packages (GR102), insecure transport (GR103), broad
184
+ filesystem scope (GR104), shell wrappers (GR105), dangerous Docker options (GR106),
185
+ over-permissive agent settings (GR107), auto-trusted MCP servers (GR108).
186
+ - Hidden text decoded by GR001, GR004 and GR015 is re-scanned by GR010-GR016 and reported with
187
+ one more level of severity.
188
+ - CLI: `scan` (the default command), `reveal`, `clean`, `rules`, `version`. Reports as a table
189
+ grouped by file with the decoded hidden text highlighted, JSON, SARIF 2.1.0 (validated against the
190
+ official schema) and Markdown. Exit codes: 0 clean, 1 findings at or above `--fail-on`, 2 error.
191
+ - Configuration through `.ghostrules.toml` or `[tool.ghostrules]`, and inline `ghostrules-ignore`
192
+ suppressions (never for GR001, GR002 or text found inside hidden characters).
193
+ - GitHub Action (SARIF upload to code scanning, job summary, `fail-on` gate), pre-commit hook, CI
194
+ with a smoke test and a self-scan, and a tag-triggered release workflow using PyPI Trusted
195
+ Publishing.
196
+ - Demo repositories `examples/poisoned-repo` (every rule fires) and `examples/clean-repo` (no
197
+ findings), and `demo/demo.tape` to record the README GIF.
198
+
199
+ ### Security
200
+ - Read-only: nothing found in a scanned file is executed, imported, downloaded or sent anywhere,
201
+ and the tool makes no network requests. Symbolic links are not followed.
202
+ - Secrets are never printed: every finding, warning and error passes through one masking step
203
+ (`ghp_****…a1b2`), and a test fails if a secret appears in any report format.
@@ -11,9 +11,9 @@ It is a **defensive** security tool. It only reads files and reports findings. I
11
11
  anything it finds, never makes network requests, and never phones home.
12
12
 
13
13
  ## Source-of-truth documents (read before coding)
14
- - `docs/SPEC.md` — features, CLI, file targets, output formats, config. **The contract.**
14
+ - `docs/design/SPEC.md` — features, CLI, file targets, output formats, config. **The contract.**
15
15
  - `docs/RULES.md` — every detection rule: ID, severity, logic, true/false positive examples.
16
- - `docs/PLAN.md` — build order, phase by phase, with acceptance criteria.
16
+ - `docs/design/PLAN.md` — build order, phase by phase, with acceptance criteria.
17
17
 
18
18
  If something in the code disagrees with these docs, the docs win unless the user says otherwise.
19
19
  If a doc is ambiguous, pick the simplest reasonable option and note it in `docs/DECISIONS.md`.
@@ -54,7 +54,7 @@ ghostrules scan examples/clean-repo # must report zero findings
54
54
  - Never add code that executes, downloads or evaluates scanned content.
55
55
 
56
56
  ## Workflow
57
- - Work phase by phase following `docs/PLAN.md`. After each phase: run tests + ruff, then
57
+ - Work phase by phase following `docs/design/PLAN.md`. After each phase: run tests + ruff, then
58
58
  stop and summarize what was done and what is next.
59
59
  - Commit after each phase with a conventional commit message (`feat:`, `test:`, `docs:`…).
60
60
  - Keep the README in English and keep it updated when CLI flags change.
@@ -17,10 +17,12 @@ ghostrules scan examples/poisoned-repo # must find issues, exit code 1
17
17
  ghostrules scan examples/clean-repo # must report nothing, exit code 0
18
18
  ```
19
19
 
20
- The documents in `docs/` are the contract: `SPEC.md` (features), `RULES.md` (every rule),
21
- `AGENT_MODELS.md` (how each agent loads its instructions, with sources) and `DECISIONS.md`
22
- (choices made where they were ambiguous). `docs/v0.2/` holds the 0.2 design documents. If code and docs disagree, fix one of
23
- them in the same pull request.
20
+ The documents in `docs/` are the contract: `RULES.md` (every rule), `AGENT_MODELS.md` (how each
21
+ agent loads its instructions, with sources) and `DECISIONS.md` (choices made where they were
22
+ ambiguous). `docs/design/` is the **design history**: the original `SPEC.md` and `PLAN.md` and the
23
+ per-version design documents (`v0.1.1/`, `v0.2/`). They explain why things are the way they are and
24
+ are not kept up to date. If code and the contract documents disagree, fix one of them in the same
25
+ pull request.
24
26
 
25
27
  ## Add a rule in three steps
26
28
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ghostrules
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Find the instructions your AI agent sees but you don't.
5
5
  Project-URL: Homepage, https://github.com/BardLaudian/ghostrules
6
6
  Project-URL: Documentation, https://github.com/BardLaudian/ghostrules#readme
@@ -45,33 +45,40 @@ Requires-Dist: rich>=13
45
45
  Requires-Dist: tomli>=2.0; python_version < '3.11'
46
46
  Requires-Dist: typer>=0.12
47
47
  Provides-Extra: dev
48
+ Requires-Dist: hatchling; extra == 'dev'
48
49
  Requires-Dist: jsonschema>=4; extra == 'dev'
49
50
  Requires-Dist: mypy>=1.10; extra == 'dev'
50
51
  Requires-Dist: pytest-cov>=5; extra == 'dev'
51
52
  Requires-Dist: pytest>=8; extra == 'dev'
52
53
  Requires-Dist: pyyaml>=6; extra == 'dev'
53
54
  Requires-Dist: ruff>=0.6; extra == 'dev'
55
+ Provides-Extra: study
56
+ Requires-Dist: matplotlib>=3.8; extra == 'study'
57
+ Provides-Extra: web-test
58
+ Requires-Dist: playwright>=1.45; extra == 'web-test'
54
59
  Description-Content-Type: text/markdown
55
60
 
56
61
  # ghostrules
57
62
 
58
63
  *Know exactly what your AI agent reads — and approve every change to it.*
59
64
 
60
- *Find the instructions your AI agent sees but you don't.*
65
+ **[Try it in your browser →](https://bardlaudian.github.io/ghostrules/)** Paste an `AGENTS.md`, drop a folder or name a public repo. It runs the real scanner in the page: your files never leave it.
61
66
 
62
67
  [![CI](https://github.com/BardLaudian/ghostrules/actions/workflows/ci.yml/badge.svg)](https://github.com/BardLaudian/ghostrules/actions/workflows/ci.yml)
63
68
  [![PyPI](https://img.shields.io/pypi/v/ghostrules.svg)](https://pypi.org/project/ghostrules/)
64
69
  [![Python versions](https://img.shields.io/pypi/pyversions/ghostrules.svg)](https://pypi.org/project/ghostrules/)
70
+ [![Corpus](https://img.shields.io/endpoint?url=https%3A%2F%2Fbardlaudian.github.io%2Fghostrules%2Fcorpus%2Fbadge.json)](https://github.com/BardLaudian/ghostrules/blob/main/corpus/README.md)
65
71
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/BardLaudian/ghostrules/blob/main/LICENSE)
66
72
 
67
- ![ghostrules finds an instruction hidden in an innocent-looking AGENTS.md](https://raw.githubusercontent.com/BardLaudian/ghostrules/main/demo/demo.gif)
73
+ ![A CLAUDE.md that looks clean imports a file that imports a file with a hidden instruction; ghostrules finds it and shows the chain](https://raw.githubusercontent.com/BardLaudian/ghostrules/main/demo/imports.gif)
68
74
 
69
- `ghostrules` scans a repository (and, if you ask, your machine) for **hidden instructions and risky
70
- configuration in the files AI coding agents read and obey**: `AGENTS.md`, `CLAUDE.md`,
71
- `.cursorrules`, Copilot instructions, skills, MCP configs and more. It models how each agent
72
- loads those files (imports, rules that apply to some paths, what it strips), so it scans what the
73
- agent really reads, and it can pin that and show every change to it. It runs as a CLI, a GitHub
74
- Action (with SARIF for the Security tab) and a pre-commit hook.
75
+ `ghostrules` finds the instructions your AI agent sees but you don't. It scans a repository (and, if
76
+ you ask, your machine) for **hidden instructions and risky configuration in the files AI coding
77
+ agents read and obey**: `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, Copilot instructions, skills, MCP
78
+ configs and more. It models how each agent loads those files (imports, rules that apply to some
79
+ paths, what it strips), so it scans what the agent really reads, and it can pin that and show every
80
+ change to it. It runs as a CLI, a GitHub Action (with SARIF for the Security tab) and a pre-commit
81
+ hook.
75
82
 
76
83
  It is a defensive tool. It only **reads** files and reports findings: it never executes anything it
77
84
  finds, never makes network requests and never phones home. Secrets it finds are always printed
@@ -114,10 +121,16 @@ influences what the agent does, and a reviewer cannot see everything that is in
114
121
  | **Telling the agent to misbehave** (GR010–GR016) | "Ignore previous instructions", hiding actions from the user, `curl … \| sh`, sending `.env` or keys to a URL, reading `~/.ssh`, base64 payloads, `--dangerously-skip-permissions`; in English and Spanish |
115
122
  | **Risky configuration** (GR101–GR108) | Hardcoded tokens in MCP configs, unpinned `npx`/`uvx` servers, `http://` remotes, a filesystem server on `/`, shell wrappers, `docker --privileged`, `bypassPermissions`, auto-enabled project MCP servers |
116
123
 
124
+ The oldest trick is still the hidden Unicode one: an innocent-looking `AGENTS.md` that carries an
125
+ instruction only the model can read.
126
+
127
+ ![ghostrules finds an instruction hidden in an innocent-looking AGENTS.md](https://raw.githubusercontent.com/BardLaudian/ghostrules/main/demo/demo.gif)
128
+
117
129
  ### How it works
118
130
 
119
- 1. It finds the agent files (see [Supported agents and files](#supported-agents-and-files)). Symbolic
120
- links are never followed.
131
+ 1. It finds the agent files (see [Supported agents and files](#supported-agents-and-files)). A
132
+ symbolic link is an alias only when it ends at a file inside the repository (`CLAUDE.md ->
133
+ AGENTS.md`); it is never followed out of it.
121
134
  2. It runs the rules on each one. Rules are pure functions over the text: no I/O, no execution.
122
135
  3. Hidden text (tag characters, variation selectors, base64) is **decoded and scanned again**, and
123
136
  what it contains is reported one severity level higher.
@@ -167,8 +180,8 @@ Findings ───────────────────────
167
180
 
168
181
  The imported file is scanned like any instruction file, and its findings carry the chain that
169
182
  leads to it. Imports are followed the way the agent follows them: relative to the importing file,
170
- not inside code spans or code blocks, to the documented depth, never through a symbolic link and
171
- never outside the repository (GR020 reports those). An import of a credentials file such as `.env`
183
+ not inside code spans or code blocks, to the documented depth, never outside the repository
184
+ (GR020 reports those) and never through a symbolic link that leaves it (GR025). An import of a credentials file such as `.env`
172
185
  is reported (GR021) and the file is never read.
173
186
 
174
187
  | `context` option | Default | Meaning |
@@ -347,7 +360,7 @@ jobs:
347
360
  runs-on: ubuntu-latest
348
361
  steps:
349
362
  - uses: actions/checkout@v4
350
- - uses: BardLaudian/ghostrules@v0.2.0
363
+ - uses: BardLaudian/ghostrules@v0.3.0
351
364
  with:
352
365
  fail-on: high # critical | high | medium | low | info | none
353
366
  all-files: "false" # also check every text file for hidden Unicode
@@ -380,7 +393,7 @@ output `lock-exit-code` is `0` up to date, `1` changed, `2` the check could not
380
393
  ```yaml
381
394
  repos:
382
395
  - repo: https://github.com/BardLaudian/ghostrules
383
- rev: v0.2.0
396
+ rev: v0.3.0
384
397
  hooks:
385
398
  - id: ghostrules
386
399
  ```
@@ -421,7 +434,7 @@ the URLs in any file.
421
434
  | <a id="gr022"></a>`GR022` | low | instructions | **Import that silently does not load.** An @path the agent ignores: missing file, too deep, quoted path or a path cut at a space. |
422
435
  | <a id="gr023"></a>`GR023` | info | instructions | **Import cycle.** Files that import each other in a loop. Agents stop at the depth limit; it is only noise. |
423
436
  | <a id="gr024"></a>`GR024` | info | instructions | **Payload reached only through an import.** Not a detector: findings in a file that only an @import reaches carry the import chain and the tag reached-via-import. Disabling it stops ghostrules from following imports. |
424
- | <a id="gr025"></a>`GR025` | medium | instructions | **Symlinked instruction source.** An instruction file or rules directory that is a symlink. ghostrules never follows it. |
437
+ | <a id="gr025"></a>`GR025` | high | instructions | **Symlinked instruction source.** An instruction file or rules directory that is a symlink the agent would follow out of the repository, a broken or cyclic one, or a symlink checked out as a text file. A link whose target is a file inside the repository is an alias and is not reported. |
425
438
  | <a id="gr101"></a>`GR101` | critical | config | **Hardcoded secret in agent config.** API keys and tokens written into MCP server env/args/headers/URLs or agent settings env. |
426
439
  | <a id="gr102"></a>`GR102` | medium | config | **Unpinned package execution.** MCP servers launched with npx/uvx/etc. without an exact version run whatever is latest. |
427
440
  | <a id="gr103"></a>`GR103` | high | config | **Insecure remote MCP transport.** Remote MCP servers reached over plain http:// expose traffic and credentials. |
@@ -455,7 +468,11 @@ Matching is case-insensitive on file names. `--home` adds the user-level files m
455
468
  On top of these patterns, `ghostrules` scans every file the modelled agents load: files reached
456
469
  through `@path` imports and `.claude/rules/**/*.md`, for instance.
457
470
 
458
- Symbolic links are never followed during repository scans. Known agent files are always scanned
471
+ A symbolic link whose final target is a file inside the repository (`CLAUDE.md -> AGENTS.md`, or a
472
+ `.claude/rules/shared -> ../shared-rules` directory) is an **alias**: the agent reads the target
473
+ under the link's name, `context` shows `CLAUDE.md → AGENTS.md (symlink)`, the content is scanned
474
+ once, and findings say they were reached through the link. A link that leaves the repository, is
475
+ broken or loops is never read (GR025). Known agent files are always scanned
459
476
  whatever their size, so padding a file cannot hide it; the 2 MB and binary-file limits only
460
477
  apply to the extra files `--all-files` adds.
461
478
 
@@ -525,7 +542,8 @@ request can update it; what it gives you is that any change to what the agents r
525
542
  explicit, reviewable diff. Put `.ghostrules.lock` and the instruction files in `CODEOWNERS`.
526
543
 
527
544
  **Does it follow imports that leave the repository?** No. `@/etc/hosts` and `@~/notes.md` are
528
- reported (GR020) and never read, and `ghostrules` never follows a symbolic link (GR025).
545
+ reported (GR020) and never read, and `ghostrules` never follows a symbolic link out of the
546
+ repository (GR025).
529
547
 
530
548
  **Which files does it look at?** The agent files in the table above. `--all-files` extends the
531
549
  hidden-Unicode checks to every text file; `exclude` and `extra_targets` in the configuration adjust
@@ -535,6 +553,21 @@ the list.
535
553
  Findings below that level are still reported but do not change the exit code, and `--fail-on none`
536
554
  never fails.
537
555
 
556
+ ## How we test precision
557
+
558
+ A scanner that cries wolf gets switched off, so precision is measured, not assumed. Every week a
559
+ workflow scans 300 popular public repositories with agent files (`corpus/`, the only part of the
560
+ project that uses the network) and fails if a critical or high finding has not been reviewed. Each
561
+ false positive found that way became a test and a rule change; findings that are correct matches in
562
+ a harmless context (a `curl | sh` installer in a README) are labelled and kept. On the first run of
563
+ the 300 repositories 0.2.1 reported 84 critical and 156 high findings; 0.3.0 reports none critical
564
+ and 53 high, all reviewed.
565
+
566
+ The same tooling scanned 1,583 repositories for
567
+ [a study of what popular repositories tell their AI agents](https://github.com/BardLaudian/ghostrules/blob/main/study/report.md):
568
+ aggregates only, no repository is named next to a finding, and every number comes from the CSVs in
569
+ `study/data/`.
570
+
538
571
  ## Limits
539
572
 
540
573
  `ghostrules` is a set of heuristics, not a proof: no findings does not mean a file is safe, and some
@@ -546,8 +579,8 @@ running server.
546
579
  The agent models follow each agent's public documentation and say `(unverified)` where it is
547
580
  silent. They do not see what lives outside the repository or the machine: Cursor user and team
548
581
  rules, managed policy files, Claude Code's `claudeMdExcludes` and `--add-dir`, instructions turned
549
- off inside Copilot with `/instructions`. A `CLAUDE.md` that is a symbolic link is reported but not
550
- read. The lock is a visibility control, not a barrier (see above).
582
+ off inside Copilot with `/instructions`. A symbolic link that leaves the repository is reported but
583
+ not read. The lock is a visibility control, not a barrier (see above).
551
584
 
552
585
  ## Roadmap
553
586