ghostrules 0.4.0__tar.gz → 0.4.1__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 (280) hide show
  1. {ghostrules-0.4.0 → ghostrules-0.4.1}/.ghostrules.lock +1 -1
  2. {ghostrules-0.4.0 → ghostrules-0.4.1}/CHANGELOG.md +30 -0
  3. {ghostrules-0.4.0 → ghostrules-0.4.1}/PKG-INFO +19 -7
  4. {ghostrules-0.4.0 → ghostrules-0.4.1}/README.md +18 -6
  5. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/README.md +3 -3
  6. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/RESULTS.md +15 -9
  7. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/attacks/context-and-config.toml +4 -4
  8. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/attacks/prose.toml +13 -13
  9. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/attacks/unicode.toml +5 -5
  10. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/attacks/w5.toml +6 -6
  11. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/heldout-results.json +5 -5
  12. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/lib.py +18 -1
  13. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/run.py +102 -20
  14. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/USAGE.md +3 -3
  15. ghostrules-0.4.1/release-notes.md +27 -0
  16. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/__init__.py +1 -1
  17. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reach.py +41 -0
  18. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/scripts.py +84 -22
  19. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/scanner.py +48 -4
  20. ghostrules-0.4.1/src/ghostrules/visibility.py +85 -0
  21. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_bench.py +29 -5
  22. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_ci_files.py +3 -1
  23. ghostrules-0.4.1/tests/test_script_own_service.py +211 -0
  24. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_visibility.py +62 -6
  25. ghostrules-0.4.0/release-notes.md +0 -108
  26. ghostrules-0.4.0/src/ghostrules/visibility.py +0 -40
  27. {ghostrules-0.4.0 → ghostrules-0.4.1}/.ghostrules.toml +0 -0
  28. {ghostrules-0.4.0 → ghostrules-0.4.1}/.gitattributes +0 -0
  29. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  30. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/ISSUE_TEMPLATE/false-positive.yml +0 -0
  31. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/ISSUE_TEMPLATE/new-rule.yml +0 -0
  32. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/dependabot.yml +0 -0
  33. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/workflows/ci.yml +0 -0
  34. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/workflows/corpus.yml +0 -0
  35. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/workflows/pages.yml +0 -0
  36. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/workflows/release.yml +0 -0
  37. {ghostrules-0.4.0 → ghostrules-0.4.1}/.github/workflows/self-test.yml +0 -0
  38. {ghostrules-0.4.0 → ghostrules-0.4.1}/.gitignore +0 -0
  39. {ghostrules-0.4.0 → ghostrules-0.4.1}/.pre-commit-hooks.yaml +0 -0
  40. {ghostrules-0.4.0 → ghostrules-0.4.1}/CLAUDE.md +0 -0
  41. {ghostrules-0.4.0 → ghostrules-0.4.1}/CONTRIBUTING.md +0 -0
  42. {ghostrules-0.4.0 → ghostrules-0.4.1}/LICENSE +0 -0
  43. {ghostrules-0.4.0 → ghostrules-0.4.1}/SECURITY.md +0 -0
  44. {ghostrules-0.4.0 → ghostrules-0.4.1}/action-requirements.in +0 -0
  45. {ghostrules-0.4.0 → ghostrules-0.4.1}/action-requirements.txt +0 -0
  46. {ghostrules-0.4.0 → ghostrules-0.4.1}/action.yml +0 -0
  47. {ghostrules-0.4.0 → ghostrules-0.4.1}/assets/banner-dark.png +0 -0
  48. {ghostrules-0.4.0 → ghostrules-0.4.1}/assets/banner-light.png +0 -0
  49. {ghostrules-0.4.0 → ghostrules-0.4.1}/assets/logo.svg +0 -0
  50. {ghostrules-0.4.0 → ghostrules-0.4.1}/assets/social-preview.png +0 -0
  51. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/.cache-run.log +0 -0
  52. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/attacks/sources.toml +0 -0
  53. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/heldout-labels.toml +0 -0
  54. {ghostrules-0.4.0 → ghostrules-0.4.1}/bench/heldout.toml +0 -0
  55. {ghostrules-0.4.0 → ghostrules-0.4.1}/demo/README.md +0 -0
  56. {ghostrules-0.4.0 → ghostrules-0.4.1}/demo/demo.gif +0 -0
  57. {ghostrules-0.4.0 → ghostrules-0.4.1}/demo/demo.tape +0 -0
  58. {ghostrules-0.4.0 → ghostrules-0.4.1}/demo/imports.gif +0 -0
  59. {ghostrules-0.4.0 → ghostrules-0.4.1}/demo/imports.tape +0 -0
  60. {ghostrules-0.4.0 → ghostrules-0.4.1}/demo/lock.tape +0 -0
  61. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/AGENT_MODELS.md +0 -0
  62. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/DECISIONS.md +0 -0
  63. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/RULES.md +0 -0
  64. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/PLAN.md +0 -0
  65. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/SPEC.md +0 -0
  66. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.1.1/GR012-FIX.md +0 -0
  67. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.2/PLAN.md +0 -0
  68. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.2/RULES.md +0 -0
  69. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.2/SPEC.md +0 -0
  70. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.3/CORPUS.md +0 -0
  71. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.3/PLAN.md +0 -0
  72. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.3/PLAYGROUND.md +0 -0
  73. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.3/STUDY.md +0 -0
  74. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/design/v0.4/REMEDIATION.md +0 -0
  75. {ghostrules-0.4.0 → ghostrules-0.4.1}/docs/v0.2.1/FIXES.md +0 -0
  76. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/clean-repo/.claude/settings.json +0 -0
  77. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/clean-repo/.mcp.json +0 -0
  78. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/clean-repo/AGENTS.md +0 -0
  79. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/clean-repo/CLAUDE.md +0 -0
  80. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/clean-repo/README.md +0 -0
  81. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/.claude/rules/api.md +0 -0
  82. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/.env +0 -0
  83. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/AGENTS.md +0 -0
  84. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/CLAUDE.md +0 -0
  85. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/README.md +0 -0
  86. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/docs/setup.md +0 -0
  87. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/docs/style.md +0 -0
  88. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/shared/team-rules.md +0 -0
  89. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/src/api/users.ts +0 -0
  90. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/import-chain-repo/src/legacy/CLAUDE.md +0 -0
  91. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/poisoned-repo/.claude/settings.json +0 -0
  92. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/poisoned-repo/.claude/skills/setup/SKILL.md +0 -0
  93. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/poisoned-repo/.cursorrules +0 -0
  94. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/poisoned-repo/.github/copilot-instructions.md +0 -0
  95. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/poisoned-repo/.mcp.json +0 -0
  96. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/poisoned-repo/AGENTS.md +0 -0
  97. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/poisoned-repo/CLAUDE.md +0 -0
  98. {ghostrules-0.4.0 → ghostrules-0.4.1}/examples/poisoned-repo/README.md +0 -0
  99. {ghostrules-0.4.0 → ghostrules-0.4.1}/pyproject.toml +0 -0
  100. {ghostrules-0.4.0 → ghostrules-0.4.1}/scripts/build_site.py +0 -0
  101. {ghostrules-0.4.0 → ghostrules-0.4.1}/scripts/changelog_section.py +0 -0
  102. {ghostrules-0.4.0 → ghostrules-0.4.1}/scripts/gen_rules_table.py +0 -0
  103. {ghostrules-0.4.0 → ghostrules-0.4.1}/scripts/make_demo.py +0 -0
  104. {ghostrules-0.4.0 → ghostrules-0.4.1}/scripts/make_fixtures.py +0 -0
  105. {ghostrules-0.4.0 → ghostrules-0.4.1}/scripts/set_owner.py +0 -0
  106. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/_compat.py +0 -0
  107. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/__init__.py +0 -0
  108. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/base.py +0 -0
  109. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/claude_code.py +0 -0
  110. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/codex.py +0 -0
  111. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/copilot_cli.py +0 -0
  112. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/cursor.py +0 -0
  113. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/gemini_cli.py +0 -0
  114. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/generic.py +0 -0
  115. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/agents/repo.py +0 -0
  116. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/baseline.py +0 -0
  117. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/changeset.py +0 -0
  118. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/clean.py +0 -0
  119. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/cli.py +0 -0
  120. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/config.py +0 -0
  121. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/context.py +0 -0
  122. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/context_command.py +0 -0
  123. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/contextscan.py +0 -0
  124. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/firstparty.py +0 -0
  125. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/gitref.py +0 -0
  126. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/globs.py +0 -0
  127. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/ignores.py +0 -0
  128. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/imports.py +0 -0
  129. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/jsonloc.py +0 -0
  130. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/lock.py +0 -0
  131. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/markdown_utils.py +0 -0
  132. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/mcp_config.py +0 -0
  133. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/models.py +0 -0
  134. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/playground.py +0 -0
  135. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/py.typed +0 -0
  136. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/__init__.py +0 -0
  137. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/common.py +0 -0
  138. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/context.py +0 -0
  139. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/context_data.py +0 -0
  140. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/json_.py +0 -0
  141. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/lock.py +0 -0
  142. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/markdown.py +0 -0
  143. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/sarif.py +0 -0
  144. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reporters/table.py +0 -0
  145. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reveal.py +0 -0
  146. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/reveal_data.py +0 -0
  147. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/__init__.py +0 -0
  148. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/agent_settings.py +0 -0
  149. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/context_rules.py +0 -0
  150. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/directive.py +0 -0
  151. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/helpers.py +0 -0
  152. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/injection.py +0 -0
  153. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/known_installers.py +0 -0
  154. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/mcp.py +0 -0
  155. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/patterns.py +0 -0
  156. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/registry.py +0 -0
  157. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/scanner_config.py +0 -0
  158. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/rules/unicode.py +0 -0
  159. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/scancontext.py +0 -0
  160. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/schemas/context.schema.json +0 -0
  161. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/secrets.py +0 -0
  162. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/sensitive.py +0 -0
  163. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/targets.py +0 -0
  164. {ghostrules-0.4.0 → ghostrules-0.4.1}/src/ghostrules/unicode_utils.py +0 -0
  165. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/analyze.py +0 -0
  166. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/01-adoption-dark.svg +0 -0
  167. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/01-adoption-light.svg +0 -0
  168. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/02-tools-per-repo-dark.svg +0 -0
  169. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/02-tools-per-repo-light.svg +0 -0
  170. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/03-claude-agents-dark.svg +0 -0
  171. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/03-claude-agents-light.svg +0 -0
  172. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/04-what-agents-read-dark.svg +0 -0
  173. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/04-what-agents-read-light.svg +0 -0
  174. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/05-risk-dark.svg +0 -0
  175. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/05-risk-light.svg +0 -0
  176. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/06-precision-dark.svg +0 -0
  177. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts/06-precision-light.svg +0 -0
  178. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/charts.py +0 -0
  179. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/adoption_families.csv +0 -0
  180. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/claude_agents_relation.csv +0 -0
  181. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/conditional_rules.csv +0 -0
  182. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/findings_by_rule.csv +0 -0
  183. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/headline.json +0 -0
  184. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/imports.csv +0 -0
  185. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/instruction_file_lines.csv +0 -0
  186. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/numbers.csv +0 -0
  187. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/precision_by_rule.csv +0 -0
  188. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/precision_runs.csv +0 -0
  189. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/risk_indicators.csv +0 -0
  190. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/sample.csv +0 -0
  191. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/skills_concentration.csv +0 -0
  192. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/data/tools_per_repo.csv +0 -0
  193. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/features.py +0 -0
  194. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/label_classes.py +0 -0
  195. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/labels.toml +0 -0
  196. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/report.md +0 -0
  197. {ghostrules-0.4.0 → ghostrules-0.4.1}/study/report.py +0 -0
  198. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/conftest.py +0 -0
  199. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/corpus_fakes.py +0 -0
  200. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/data/sarif-schema-2.1.0.json +0 -0
  201. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr001_flag_forged/AGENTS.md +0 -0
  202. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr001_flag_ok/AGENTS.md +0 -0
  203. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr001_tags/AGENTS.md +0 -0
  204. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr002_bidi/AGENTS.md +0 -0
  205. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr002_rtl_medium/AGENTS.md +0 -0
  206. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_bom_midfile/AGENTS.md +0 -0
  207. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_bom_ok/AGENTS.md +0 -0
  208. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_emoji_zwj_ok/AGENTS.md +0 -0
  209. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_indic_ok/AGENTS.md +0 -0
  210. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_persian_ok/AGENTS.md +0 -0
  211. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_soft_hyphen/AGENTS.md +0 -0
  212. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_zw_file_high/AGENTS.md +0 -0
  213. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_zw_line_high/AGENTS.md +0 -0
  214. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr003_zw_medium/AGENTS.md +0 -0
  215. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr004_vs_bytes/AGENTS.md +0 -0
  216. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr004_vs_ok/AGENTS.md +0 -0
  217. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr005_benign_ok/CLAUDE.md +0 -0
  218. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr005_comment_imperative/CLAUDE.md +0 -0
  219. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr005_comment_injection/CLAUDE.md +0 -0
  220. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr005_comment_multiline/CLAUDE.md +0 -0
  221. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr005_in_code_block_ok/CLAUDE.md +0 -0
  222. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr005_short_ok/CLAUDE.md +0 -0
  223. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr005_unterminated/CLAUDE.md +0 -0
  224. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr006_ok/AGENTS.md +0 -0
  225. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr006_url/AGENTS.md +0 -0
  226. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/fixtures/unicode/gr006_word/AGENTS.md +0 -0
  227. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/pr_helpers.py +0 -0
  228. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/secret_samples.py +0 -0
  229. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_agents_claude.py +0 -0
  230. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_agents_others.py +0 -0
  231. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_agents_repo.py +0 -0
  232. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_baseline.py +0 -0
  233. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_bench_heldout.py +0 -0
  234. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_cli.py +0 -0
  235. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_codex_gemini.py +0 -0
  236. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_config.py +0 -0
  237. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_context_api.py +0 -0
  238. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_context_command.py +0 -0
  239. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_core_without_cli_deps.py +0 -0
  240. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_corpus_packaging.py +0 -0
  241. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_corpus_tools.py +0 -0
  242. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_crlf.py +0 -0
  243. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_cross_sentence.py +0 -0
  244. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_demo.py +0 -0
  245. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_directive.py +0 -0
  246. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_docs.py +0 -0
  247. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_globs.py +0 -0
  248. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_gr030.py +0 -0
  249. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_hidden_ignores.py +0 -0
  250. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_hook_scripts.py +0 -0
  251. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_imports.py +0 -0
  252. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_lock.py +0 -0
  253. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_lock_base.py +0 -0
  254. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_markdown_utils.py +0 -0
  255. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_mcp_config.py +0 -0
  256. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_models.py +0 -0
  257. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_noise_fixes.py +0 -0
  258. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_patterns.py +0 -0
  259. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_playground.py +0 -0
  260. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_pr_mode.py +0 -0
  261. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_precision_study.py +0 -0
  262. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_precision_v021.py +0 -0
  263. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_precision_v030.py +0 -0
  264. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_referenced_files.py +0 -0
  265. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_reporters.py +0 -0
  266. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_rules_config.py +0 -0
  267. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_rules_injection.py +0 -0
  268. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_rules_unicode.py +0 -0
  269. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_scan_imports.py +0 -0
  270. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_scanner.py +0 -0
  271. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_secret_masking.py +0 -0
  272. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_secrets.py +0 -0
  273. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_study.py +0 -0
  274. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_symlink_alias.py +0 -0
  275. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_targets.py +0 -0
  276. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/test_unicode_utils.py +0 -0
  277. {ghostrules-0.4.0 → ghostrules-0.4.1}/tests/web/test_playground_smoke.py +0 -0
  278. {ghostrules-0.4.0 → ghostrules-0.4.1}/web/app.js +0 -0
  279. {ghostrules-0.4.0 → ghostrules-0.4.1}/web/index.html +0 -0
  280. {ghostrules-0.4.0 → ghostrules-0.4.1}/web/style.css +0 -0
@@ -26,6 +26,6 @@
26
26
  "generic": []
27
27
  },
28
28
  "configs": [],
29
- "generator": "ghostrules 0.4.0",
29
+ "generator": "ghostrules 0.4.1",
30
30
  "lock_version": 1
31
31
  }
@@ -5,6 +5,36 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.4.1] - 2026-10-29
9
+
10
+ Follow-up to 0.4.0: fewer false positives on scripts, fewer misses on visible attacks, and a
11
+ benchmark headline that no longer counts cases the author wrote. No breaking changes.
12
+
13
+ ### Changed
14
+
15
+ - **Visible prose that reads as an attack stays `high`.** Inside one paragraph, exfiltration plus
16
+ concealment (GR013 + GR011), override plus exfiltration (GR010 + GR013) and a sensitive file plus
17
+ a URL outside `allow_domains` (GR014 + URL) keep the rule's severity even though the text is
18
+ visible. Examples, quotations, code blocks and descriptions stay `low`. On the held-out set no
19
+ repository starts to fail the check, but two GR014 findings already labelled false positive now
20
+ report `high`.
21
+ - **GR110 no longer reports a token sent to the service it belongs to**: `GITHUB_*`/`GH_*` to
22
+ `api.github.com`, `github.com` or `uploads.github.com`; `AZURE_*`, `ADO_*` and `SYSTEM_ACCESSTOKEN`
23
+ to `dev.azure.com` or `*.visualstudio.com`; `<X>_API_KEY` to a host that contains `x`. It still
24
+ reports any other host, a collector domain, the whole environment, a credentials file, a variable
25
+ with no known service and a destination it cannot read. The held-out GR110 finding is still
26
+ reported: its shape is not covered.
27
+ - **The benchmark headline counts only the independent cases.** Recall is split by origin (`author`,
28
+ `review`, `public`; `ours` is now `author`) and the README states the independent figure first,
29
+ with its sample size. Every number in `README.md` and `bench/README.md` is generated from the data
30
+ (`python bench/run.py report --write`) and a test fails when one is stale.
31
+
32
+ ### Added
33
+
34
+ - **Inline hook code is read as a script**: the code in `python3 -c`, `node -e`, `bash -c` and `sh -c`
35
+ (and the other interpreters in the hook table) is checked by GR110–GR113. The finding points at the
36
+ command in the settings file.
37
+
8
38
  ## [0.4.0] - 2026-10-29
9
39
 
10
40
  Remediation release, from an independent review of 0.3.0 ([plan](docs/design/v0.4/REMEDIATION.md)).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ghostrules
3
- Version: 0.4.0
3
+ Version: 0.4.1
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
@@ -149,7 +149,7 @@ ghostrules lock --check .ghostrules.lock
149
149
 
150
150
  ```yaml
151
151
  - uses: actions/checkout@v4
152
- - uses: BardLaudian/ghostrules@v0.4.0
152
+ - uses: BardLaudian/ghostrules@v0.4.1
153
153
  with:
154
154
  fail-on: high # critical | high | medium | low | info | none
155
155
  lock-check: "true" # fail if what the agents read changed
@@ -158,7 +158,7 @@ ghostrules lock --check .ghostrules.lock
158
158
  ```yaml
159
159
  repos:
160
160
  - repo: https://github.com/BardLaudian/ghostrules
161
- rev: v0.4.0
161
+ rev: v0.4.1
162
162
  hooks:
163
163
  - id: ghostrules
164
164
  ```
@@ -171,8 +171,16 @@ A scanner that cries wolf gets switched off, and one that misses gets trusted by
171
171
  measured and the misses are listed ([bench/](https://github.com/BardLaudian/ghostrules/blob/main/bench/README.md),
172
172
  [full results](https://github.com/BardLaudian/ghostrules/blob/main/bench/RESULTS.md)). Nothing below was tuned on the data it is measured on.
173
173
 
174
- <!-- bench:start -->
175
- **Recall: 101 attack cases** written from public sources and defanged. 87 are reported (86%); 50 (50%) fail the default `--fail-on high` check, because visible prose is `low` by default (hidden text and configuration are not). The 14 misses include paraphrases, other languages, scripts and files that nothing points the scanner to, tool descriptions served by a running MCP server, two scanner evasions that only the pull-request mode closes, and gaps of ours; each is listed in the results. 4 cases come from the independent review; its other 14 are not in the repository yet.
174
+ <!-- bench:recall:start -->
175
+ **Recall on independent cases: 1 of 4 reported (25%); 1 fail the default `--fail-on high` check.** They are 4 of the 18 cases an independent review wrote; the other 14 are not in the repository yet, so this is a small sample. The 97 cases written by the author or taken from public sources are shown separately because the author knew what the rules match: of all 101 cases 87 are reported (86%). Visible prose is `low` by default, so the check fails less often than the rules report (hidden text and configuration are not lowered). The misses include paraphrases, other languages, scripts and files that nothing points the scanner to, tool descriptions served by a running MCP server, two scanner evasions that only the pull-request mode closes, and gaps of ours; each is listed in the results.
176
+
177
+ | Origin | Cases | Reported | Fails the default check |
178
+ |---|---:|---:|---:|
179
+ | author | 28 | 20 (71%) | 9 (32%) |
180
+ | review (independent) | 4 | 1 (25%) | 1 (25%) |
181
+ | public | 69 | 66 (96%) | 40 (58%) |
182
+
183
+ All origins, by technique:
176
184
 
177
185
  | Technique | Cases | Reported | Fails the default check |
178
186
  |---|---:|---:|---:|
@@ -197,9 +205,13 @@ measured and the misses are listed ([bench/](https://github.com/BardLaudian/ghos
197
205
  | unicode-tags | 7 | 7 | 7 |
198
206
  | variation-selectors | 3 | 3 | 3 |
199
207
  | zero-width | 3 | 3 | 1 |
200
- <!-- bench:end -->
208
+ <!-- bench:recall:end -->
209
+
210
+ <!-- bench:precision:start -->
211
+ **Precision: 100 repositories** frozen before any v0.4 tuning (851 agent files, default settings). They gave 21 findings, and 2 repositories would fail the default check. Precision for a user is true positives over everything reported, because a right pattern in a harmless place is still something to read: **4 of 21 (19 %)**, from labels given by the assistant that ran the benchmark and **not reviewed by a human** (0 findings unlabelled). Per rule: GR014 1 of 5; GR022 1 of 6; GR102 1 of 1; GR107 1 of 1; GR003 0 of 2; GR011 0 of 1; GR012 0 of 4; GR110 0 of 1. 21 findings are too few for a percentage per rule to mean much.
212
+ <!-- bench:precision:end -->
201
213
 
202
- **Precision: 100 repositories** frozen before any v0.4 tuning (874 agent files, default settings). They gave 21 findings, and 2 repositories would fail the default check. Precision for a user is true positives over everything reported, because a right pattern in a harmless place is still something to read: **4 of 21 (19 %)**, from labels given by the assistant that ran the benchmark and **not reviewed by a human**. Per rule: GR102 and GR107 1 of 1; GR014 1 of 5; GR022 1 of 6; GR012 0 of 4; GR003 0 of 2; GR011 and GR110 0 of 1 (the new GR110 finding is a script that sends its own API key to the API it documents). Twenty-one findings are too few for a percentage per rule to mean much.
214
+ The new GR110 finding is a script that sends its own API key to the API it documents.
203
215
 
204
216
  A workflow scheduled weekly scans 300 popular public repositories (`corpus/`, the only part of the project that uses the network) and fails if a critical or high finding has not been reviewed; last run: 2026-10-28. Those rules were tuned on that data, so its numbers are not an estimate of precision on new repositories. On it, 0.2.1 reported 84 critical and 156 high findings and 0.3.0 reports none critical and 53 high. The [study](https://github.com/BardLaudian/ghostrules/blob/main/study/report.md) used the same tooling; `ghostrules` is a set of heuristics, not a proof: see the [limits](https://github.com/BardLaudian/ghostrules/blob/main/docs/USAGE.md#limits).
205
217
 
@@ -89,7 +89,7 @@ ghostrules lock --check .ghostrules.lock
89
89
 
90
90
  ```yaml
91
91
  - uses: actions/checkout@v4
92
- - uses: BardLaudian/ghostrules@v0.4.0
92
+ - uses: BardLaudian/ghostrules@v0.4.1
93
93
  with:
94
94
  fail-on: high # critical | high | medium | low | info | none
95
95
  lock-check: "true" # fail if what the agents read changed
@@ -98,7 +98,7 @@ ghostrules lock --check .ghostrules.lock
98
98
  ```yaml
99
99
  repos:
100
100
  - repo: https://github.com/BardLaudian/ghostrules
101
- rev: v0.4.0
101
+ rev: v0.4.1
102
102
  hooks:
103
103
  - id: ghostrules
104
104
  ```
@@ -111,8 +111,16 @@ A scanner that cries wolf gets switched off, and one that misses gets trusted by
111
111
  measured and the misses are listed ([bench/](https://github.com/BardLaudian/ghostrules/blob/main/bench/README.md),
112
112
  [full results](https://github.com/BardLaudian/ghostrules/blob/main/bench/RESULTS.md)). Nothing below was tuned on the data it is measured on.
113
113
 
114
- <!-- bench:start -->
115
- **Recall: 101 attack cases** written from public sources and defanged. 87 are reported (86%); 50 (50%) fail the default `--fail-on high` check, because visible prose is `low` by default (hidden text and configuration are not). The 14 misses include paraphrases, other languages, scripts and files that nothing points the scanner to, tool descriptions served by a running MCP server, two scanner evasions that only the pull-request mode closes, and gaps of ours; each is listed in the results. 4 cases come from the independent review; its other 14 are not in the repository yet.
114
+ <!-- bench:recall:start -->
115
+ **Recall on independent cases: 1 of 4 reported (25%); 1 fail the default `--fail-on high` check.** They are 4 of the 18 cases an independent review wrote; the other 14 are not in the repository yet, so this is a small sample. The 97 cases written by the author or taken from public sources are shown separately because the author knew what the rules match: of all 101 cases 87 are reported (86%). Visible prose is `low` by default, so the check fails less often than the rules report (hidden text and configuration are not lowered). The misses include paraphrases, other languages, scripts and files that nothing points the scanner to, tool descriptions served by a running MCP server, two scanner evasions that only the pull-request mode closes, and gaps of ours; each is listed in the results.
116
+
117
+ | Origin | Cases | Reported | Fails the default check |
118
+ |---|---:|---:|---:|
119
+ | author | 28 | 20 (71%) | 9 (32%) |
120
+ | review (independent) | 4 | 1 (25%) | 1 (25%) |
121
+ | public | 69 | 66 (96%) | 40 (58%) |
122
+
123
+ All origins, by technique:
116
124
 
117
125
  | Technique | Cases | Reported | Fails the default check |
118
126
  |---|---:|---:|---:|
@@ -137,9 +145,13 @@ measured and the misses are listed ([bench/](https://github.com/BardLaudian/ghos
137
145
  | unicode-tags | 7 | 7 | 7 |
138
146
  | variation-selectors | 3 | 3 | 3 |
139
147
  | zero-width | 3 | 3 | 1 |
140
- <!-- bench:end -->
148
+ <!-- bench:recall:end -->
149
+
150
+ <!-- bench:precision:start -->
151
+ **Precision: 100 repositories** frozen before any v0.4 tuning (851 agent files, default settings). They gave 21 findings, and 2 repositories would fail the default check. Precision for a user is true positives over everything reported, because a right pattern in a harmless place is still something to read: **4 of 21 (19 %)**, from labels given by the assistant that ran the benchmark and **not reviewed by a human** (0 findings unlabelled). Per rule: GR014 1 of 5; GR022 1 of 6; GR102 1 of 1; GR107 1 of 1; GR003 0 of 2; GR011 0 of 1; GR012 0 of 4; GR110 0 of 1. 21 findings are too few for a percentage per rule to mean much.
152
+ <!-- bench:precision:end -->
141
153
 
142
- **Precision: 100 repositories** frozen before any v0.4 tuning (874 agent files, default settings). They gave 21 findings, and 2 repositories would fail the default check. Precision for a user is true positives over everything reported, because a right pattern in a harmless place is still something to read: **4 of 21 (19 %)**, from labels given by the assistant that ran the benchmark and **not reviewed by a human**. Per rule: GR102 and GR107 1 of 1; GR014 1 of 5; GR022 1 of 6; GR012 0 of 4; GR003 0 of 2; GR011 and GR110 0 of 1 (the new GR110 finding is a script that sends its own API key to the API it documents). Twenty-one findings are too few for a percentage per rule to mean much.
154
+ The new GR110 finding is a script that sends its own API key to the API it documents.
143
155
 
144
156
  A workflow scheduled weekly scans 300 popular public repositories (`corpus/`, the only part of the project that uses the network) and fails if a critical or high finding has not been reviewed; last run: 2026-10-28. Those rules were tuned on that data, so its numbers are not an estimate of precision on new repositories. On it, 0.2.1 reported 84 critical and 156 high findings and 0.3.0 reports none critical and 53 high. The [study](https://github.com/BardLaudian/ghostrules/blob/main/study/report.md) used the same tooling; `ghostrules` is a set of heuristics, not a proof: see the [limits](https://github.com/BardLaudian/ghostrules/blob/main/docs/USAGE.md#limits).
145
157
 
@@ -6,7 +6,7 @@ Two measurements, both reported with their misses and without tuning on them (W6
6
6
 
7
7
  ## Recall: the attack set (`attacks/`)
8
8
 
9
- 81 cases, each a technique written from a public description (sources in `attacks/sources.toml`:
9
+ <!-- bench:cases:start -->101 cases (28 by the author, 4 from the independent review, 69 from public sources), each a technique written from a public description<!-- bench:cases:end --> (sources in `attacks/sources.toml`:
10
10
  Rehberger and Butler on Unicode smuggling, Trojan Source, Pillar's Rules File Backdoor, Invariant
11
11
  Labs on MCP tool poisoning, Check Point's Claude Code CVEs, OWASP's malicious-skills entry), with
12
12
  the rule that should catch it and whether it does today (`status = "detected"` or `"known-miss"`).
@@ -21,7 +21,7 @@ the rule that should catch it and whether it does today (`status = "detected"` o
21
21
  - **Misses stay in the base and are listed.** A test fails when a declared status stops matching what
22
22
  the scanner does, so a miss that gets fixed must be updated, and a detection that regresses shows.
23
23
  - **Provenance.** `origin` is `public` (a public technique), `review` (a case from the independent
24
- review) or `ours` (written by the maintainer). Only **4 of the review's 18 cases** are included:
24
+ review) or `author` (written by the maintainer, who knew what the rules match: not independent). <!-- bench:provenance:start -->Only **4 of the review's 18 cases**<!-- bench:provenance:end --> are included:
25
25
  the ones `REMEDIATION.md` describes. The other 14 are not in the repository and are not
26
26
  invented here; add them as `origin = "review"` cases when the list is available. Ideally someone
27
27
  who has not seen the rules writes another 20.
@@ -38,7 +38,7 @@ right pattern in a harmless place is still something someone has to read.
38
38
  - **The labels are provisional.** They were given by the assistant that ran the benchmark, from the
39
39
  finding and the lines around it, and no human has reviewed them. Borderline calls are noted in
40
40
  [RESULTS.md](RESULTS.md).
41
- - **The set is small.** 100 repositories gave 18 findings: a per-rule percentage on 1 to 6 findings
41
+ - **The set is small.** <!-- bench:heldout:start -->100 repositories gave 21 findings<!-- bench:heldout:end -->: a per-rule percentage on 1 to 6 findings
42
42
  is an anecdote, not an estimate.
43
43
  - **Never tune on it.** A rule changed after reading these findings would no longer be measured by
44
44
  this set. Fixing what it shows needs a new held-out set, frozen first.
@@ -5,6 +5,12 @@ measured and how. Nothing here was tuned on the held-out set.
5
5
 
6
6
  ## Recall on the attack set
7
7
 
8
+ | Origin | Cases | Reported | Fails the default check |
9
+ |---|---:|---:|---:|
10
+ | author | 28 | 20 (71%) | 9 (32%) |
11
+ | review (independent) | 4 | 1 (25%) | 1 (25%) |
12
+ | public | 69 | 66 (96%) | 40 (58%) |
13
+
8
14
  | Technique | Cases | Reported | Fails the default check |
9
15
  |---|---:|---:|---:|
10
16
  | agent-settings | 9 | 8 | 7 |
@@ -69,20 +75,20 @@ Known misses:
69
75
  - `mcp-tool-description-poisoning` (mcp-server-content, public): Tool descriptions come from a running server and are never read (docs/USAGE.md, Limits).
70
76
  - `evasion-ignore-end-of-line` (scanner-evasion, review): In the default scan; with --suppressions-from-ref or the Action on a pull request it is reported.
71
77
  - `evasion-exclude-in-config` (scanner-evasion, review): In the default scan; --config-from-ref or the Action on a pull request ignores the added config.
72
- - `override-french` (instruction-override, ours): Patterns exist for English and Spanish only.
73
- - `override-leetspeak` (instruction-override, ours): Patterns match words, not look-alike spellings.
74
- - `override-paraphrase` (instruction-override, ours): Paraphrases cannot be caught by patterns (REMEDIATION W5.6).
75
- - `conceal-paraphrase` (concealment, ours): A paraphrase; patterns cannot catch intent (REMEDIATION W5.6).
76
- - `exfil-spanish` (exfiltration, ours): Found when the bench ran: GR013 has no Spanish patterns, although GR010, GR011, GR014 and GR016 do.
78
+ - `override-french` (instruction-override, author): Patterns exist for English and Spanish only.
79
+ - `override-leetspeak` (instruction-override, author): Patterns match words, not look-alike spellings.
80
+ - `override-paraphrase` (instruction-override, author): Paraphrases cannot be caught by patterns (REMEDIATION W5.6).
81
+ - `conceal-paraphrase` (concealment, author): A paraphrase; patterns cannot catch intent (REMEDIATION W5.6).
82
+ - `exfil-spanish` (exfiltration, author): Found when the bench ran: GR013 has no Spanish patterns, although GR010, GR011, GR014 and GR016 do.
77
83
  - `exfil-upload-config-file` (exfiltration, review): From REMEDIATION.md; a paraphrase, out of reach of patterns.
78
84
  - `sensitive-npmrc` (sensitive-file, public): Found when the bench ran: 'Open ~/.npmrc' is not matched, even at info; the read-verb list lacks 'open'.
79
- - `sensitive-paraphrase` (sensitive-file, ours): No path or file name to match: a description of the file.
80
- - `rce-python-urlopen-exec` (remote-code-execution, ours): GR012 knows shell pipes, PowerShell and eval of curl; not exec(urlopen(...)).
81
- - `skill-script-not-mentioned` (skill-scripts, ours): Only scripts that SKILL.md references are scanned (REMEDIATION W5.2), by design.
85
+ - `sensitive-paraphrase` (sensitive-file, author): No path or file name to match: a description of the file.
86
+ - `rce-python-urlopen-exec` (remote-code-execution, author): GR012 knows shell pipes, PowerShell and eval of curl; not exec(urlopen(...)).
87
+ - `skill-script-not-mentioned` (skill-scripts, author): Only scripts that SKILL.md references are scanned (REMEDIATION W5.2), by design.
82
88
 
83
89
  ## Precision on the held-out set
84
90
 
85
- 100 of 100 repositories scanned (874 agent files), ghostrules 0.3.0 at commit 8ce68eb, 2026-10-29, default settings and an empty configuration. 21 findings shown by default; 2 repositories would fail `--fail-on high`; 0 findings unlabelled.
91
+ 100 of 100 repositories scanned (851 agent files), ghostrules 0.4.1 at commit bef33a6, 2026-10-29, default settings and an empty configuration. 21 findings shown by default; 2 repositories would fail `--fail-on high`; 0 findings unlabelled.
86
92
 
87
93
  | Rule | Findings | True positive | Benign context | False positive | Unlabelled | Precision for a user |
88
94
  |---|---:|---:|---:|---:|---:|---:|
@@ -26,7 +26,7 @@ status = "detected"
26
26
  [[case]]
27
27
  id = "import-chain-payload"
28
28
  technique = "imports"
29
- origin = "ours"
29
+ origin = "author"
30
30
  source = "ours"
31
31
  description = "A clean CLAUDE.md imports a file that imports a file with the instruction."
32
32
  expected = ["GR013"]
@@ -39,7 +39,7 @@ status = "detected"
39
39
  [[case]]
40
40
  id = "import-payload-in-comment-of-imported-file"
41
41
  technique = "imports"
42
- origin = "ours"
42
+ origin = "author"
43
43
  source = "ours"
44
44
  description = "The instruction sits in an HTML comment of an imported file."
45
45
  expected = ["GR005"]
@@ -51,7 +51,7 @@ status = "detected"
51
51
  [[case]]
52
52
  id = "symlink-out-of-repo"
53
53
  technique = "imports"
54
- origin = "ours"
54
+ origin = "author"
55
55
  source = "ours"
56
56
  description = "An instruction file that is a symlink pointing outside the repository."
57
57
  expected = ["GR025"]
@@ -76,7 +76,7 @@ status = "detected"
76
76
  [[case]]
77
77
  id = "referenced-script-by-name"
78
78
  technique = "referenced-files"
79
- origin = "ours"
79
+ origin = "author"
80
80
  source = "ours"
81
81
  description = "'Run scripts/setup.sh first' where the script exfiltrates."
82
82
  expected = ["GR013", "GR110"]
@@ -37,7 +37,7 @@ status = "detected"
37
37
  [[case]]
38
38
  id = "html-comment-short"
39
39
  technique = "html-comment"
40
- origin = "ours"
40
+ origin = "author"
41
41
  source = "ours"
42
42
  description = "A short comment (under 40 characters) that conceals."
43
43
  expected = ["GR011"]
@@ -60,7 +60,7 @@ status = "detected"
60
60
  [[case]]
61
61
  id = "override-spanish"
62
62
  technique = "instruction-override"
63
- origin = "ours"
63
+ origin = "author"
64
64
  source = "ours"
65
65
  description = "The same phrase in Spanish."
66
66
  expected = ["GR010"]
@@ -104,7 +104,7 @@ status = "detected"
104
104
  [[case]]
105
105
  id = "override-across-lines"
106
106
  technique = "instruction-override"
107
- origin = "ours"
107
+ origin = "author"
108
108
  source = "ours"
109
109
  description = "The phrase broken over several lines of a list."
110
110
  expected = ["GR010"]
@@ -115,7 +115,7 @@ status = "detected"
115
115
  [[case]]
116
116
  id = "override-french"
117
117
  technique = "instruction-override"
118
- origin = "ours"
118
+ origin = "author"
119
119
  source = "ours"
120
120
  description = "The override in French: only English and Spanish are covered."
121
121
  expected = ["GR010"]
@@ -127,7 +127,7 @@ note = "Patterns exist for English and Spanish only."
127
127
  [[case]]
128
128
  id = "override-leetspeak"
129
129
  technique = "instruction-override"
130
- origin = "ours"
130
+ origin = "author"
131
131
  source = "ours"
132
132
  description = "Digits for letters."
133
133
  expected = ["GR010"]
@@ -139,7 +139,7 @@ note = "Patterns match words, not look-alike spellings."
139
139
  [[case]]
140
140
  id = "override-paraphrase"
141
141
  technique = "instruction-override"
142
- origin = "ours"
142
+ origin = "author"
143
143
  source = "ours"
144
144
  description = "The same intent without any listed phrase."
145
145
  expected = ["GR010"]
@@ -173,7 +173,7 @@ status = "detected"
173
173
  [[case]]
174
174
  id = "conceal-spanish"
175
175
  technique = "concealment"
176
- origin = "ours"
176
+ origin = "author"
177
177
  source = "ours"
178
178
  description = "Concealment in Spanish."
179
179
  expected = ["GR011"]
@@ -184,7 +184,7 @@ status = "detected"
184
184
  [[case]]
185
185
  id = "conceal-paraphrase"
186
186
  technique = "concealment"
187
- origin = "ours"
187
+ origin = "author"
188
188
  source = "ours"
189
189
  description = "Concealment worded without a listed phrase."
190
190
  expected = ["GR011"]
@@ -229,7 +229,7 @@ status = "detected"
229
229
  [[case]]
230
230
  id = "exfil-spanish"
231
231
  technique = "exfiltration"
232
- origin = "ours"
232
+ origin = "author"
233
233
  source = "ours"
234
234
  description = "The same instruction in Spanish."
235
235
  expected = ["GR013"]
@@ -253,7 +253,7 @@ note = "From REMEDIATION.md; a paraphrase, out of reach of patterns."
253
253
  [[case]]
254
254
  id = "exfil-two-sentences"
255
255
  technique = "exfiltration"
256
- origin = "ours"
256
+ origin = "author"
257
257
  source = "ours"
258
258
  description = "The secret and the destination are in different sentences."
259
259
  expected = ["GR013"]
@@ -309,7 +309,7 @@ note = "Found when the bench ran: 'Open ~/.npmrc' is not matched, even at info;
309
309
  [[case]]
310
310
  id = "sensitive-paraphrase"
311
311
  technique = "sensitive-file"
312
- origin = "ours"
312
+ origin = "author"
313
313
  source = "ours"
314
314
  description = "The same target described without a path."
315
315
  expected = ["GR014"]
@@ -376,7 +376,7 @@ status = "detected"
376
376
  [[case]]
377
377
  id = "rce-python-urlopen-exec"
378
378
  technique = "remote-code-execution"
379
- origin = "ours"
379
+ origin = "author"
380
380
  source = "ours"
381
381
  description = "Download and exec from Python instead of a shell pipe."
382
382
  expected = ["GR012"]
@@ -410,7 +410,7 @@ status = "detected"
410
410
  [[case]]
411
411
  id = "encoded-hex-blob"
412
412
  technique = "encoded-payload"
413
- origin = "ours"
413
+ origin = "author"
414
414
  source = "ours"
415
415
  description = "A long hex string."
416
416
  expected = ["GR015"]
@@ -69,7 +69,7 @@ status = "detected"
69
69
  [[case]]
70
70
  id = "tags-with-flag-prefix"
71
71
  technique = "unicode-tags"
72
- origin = "ours"
72
+ origin = "author"
73
73
  source = "ours"
74
74
  description = "Tags that start like an emoji flag sequence (black flag, tags, cancel tag), which the rule treats as legitimate."
75
75
  expected = ["GR001"]
@@ -115,7 +115,7 @@ status = "detected"
115
115
  [[case]]
116
116
  id = "zwsp-inside-keyword"
117
117
  technique = "zero-width"
118
- origin = "ours"
118
+ origin = "author"
119
119
  source = "ours"
120
120
  description = "Zero-width spaces split a keyword so a naive text match misses it."
121
121
  expected = ["GR003"]
@@ -137,7 +137,7 @@ status = "detected"
137
137
  [[case]]
138
138
  id = "soft-hyphen-split"
139
139
  technique = "zero-width"
140
- origin = "ours"
140
+ origin = "author"
141
141
  source = "ours"
142
142
  description = "Soft hyphens inside a word, invisible when rendered."
143
143
  expected = ["GR003"]
@@ -181,7 +181,7 @@ status = "detected"
181
181
  [[case]]
182
182
  id = "homoglyph-keyword"
183
183
  technique = "homoglyph"
184
- origin = "ours"
184
+ origin = "author"
185
185
  source = "ours"
186
186
  description = "A Cyrillic letter inside a Latin word that is meant to look like an override phrase."
187
187
  expected = ["GR006"]
@@ -203,7 +203,7 @@ status = "detected"
203
203
  [[case]]
204
204
  id = "homoglyph-greek"
205
205
  technique = "homoglyph"
206
- origin = "ours"
206
+ origin = "author"
207
207
  source = "ours"
208
208
  description = "A Greek omicron in a command name."
209
209
  expected = ["GR006"]
@@ -38,7 +38,7 @@ status = "detected"
38
38
  [[case]]
39
39
  id = "codex-override-payload"
40
40
  technique = "exfiltration"
41
- origin = "ours"
41
+ origin = "author"
42
42
  source = "ours"
43
43
  description = "The instruction sits in AGENTS.override.md, which Codex reads before AGENTS.md."
44
44
  expected = ["GR013"]
@@ -180,7 +180,7 @@ status = "detected"
180
180
  [[case]]
181
181
  id = "skill-script-not-mentioned"
182
182
  technique = "skill-scripts"
183
- origin = "ours"
183
+ origin = "author"
184
184
  source = "ours"
185
185
  description = "A script in a skill directory that SKILL.md never mentions."
186
186
  expected = ["GR110"]
@@ -193,7 +193,7 @@ note = "Only scripts that SKILL.md references are scanned (REMEDIATION W5.2), by
193
193
  [[case]]
194
194
  id = "referenced-see-and-do"
195
195
  technique = "referenced-files"
196
- origin = "ours"
196
+ origin = "author"
197
197
  source = "ours"
198
198
  description = "'See docs/setup.txt and do what it says', with the payload in the .txt."
199
199
  expected = ["GR013"]
@@ -205,7 +205,7 @@ status = "detected"
205
205
  [[case]]
206
206
  id = "referenced-follow-instructions-in"
207
207
  technique = "referenced-files"
208
- origin = "ours"
208
+ origin = "author"
209
209
  source = "ours"
210
210
  description = "'Follow the instructions in notes/steps.md', a markdown file that is not imported."
211
211
  expected = ["GR013"]
@@ -217,7 +217,7 @@ status = "detected"
217
217
  [[case]]
218
218
  id = "exfil-read-then-upload"
219
219
  technique = "exfiltration"
220
- origin = "ours"
220
+ origin = "author"
221
221
  source = "ours"
222
222
  description = "A private key is read in one sentence and uploaded in the next."
223
223
  expected = ["GR013"]
@@ -228,7 +228,7 @@ status = "detected"
228
228
  [[case]]
229
229
  id = "exfil-note-then-post"
230
230
  technique = "exfiltration"
231
- origin = "ours"
231
+ origin = "author"
232
232
  source = "ours"
233
233
  description = "The env file is opened in one sentence and its values posted in the next."
234
234
  expected = ["GR013"]
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "date": "2026-10-29",
3
- "ghostrules_version": "0.3.0",
4
- "commit": "8ce68eb",
3
+ "ghostrules_version": "0.4.1",
4
+ "commit": "bef33a6",
5
5
  "repos_total": 100,
6
6
  "repos_scanned": 100,
7
- "files_scanned": 874,
7
+ "files_scanned": 851,
8
8
  "repos_failing_default_check": 2,
9
9
  "findings": [
10
10
  {
@@ -44,7 +44,7 @@
44
44
  },
45
45
  {
46
46
  "rule_id": "GR014",
47
- "severity": "low",
47
+ "severity": "high",
48
48
  "fingerprint": "7f3c86ebf1cbcb23"
49
49
  },
50
50
  {
@@ -54,7 +54,7 @@
54
54
  },
55
55
  {
56
56
  "rule_id": "GR014",
57
- "severity": "low",
57
+ "severity": "high",
58
58
  "fingerprint": "95602096758bd17b"
59
59
  },
60
60
  {
@@ -29,7 +29,10 @@ from ghostrules.scanner import scan_paths
29
29
  HERE = Path(__file__).resolve().parent
30
30
  ATTACKS = HERE / "attacks"
31
31
  STATUSES = ("detected", "known-miss")
32
- ORIGINS = ("public", "review", "ours")
32
+ ORIGINS = ("author", "review", "public")
33
+ # The headline recall counts only the cases the project's author did not write or choose.
34
+ INDEPENDENT = "review"
35
+ REVIEW_TOTAL = 18 # cases the independent review reported; the rest are not in the repository yet
33
36
 
34
37
  _TOKEN = re.compile(r"\{(tags|vs|b64|hex):([^{}]*)\}|\{u\+([0-9A-Fa-f]{4,6})\}")
35
38
 
@@ -161,6 +164,20 @@ def recall_table(outcomes: list[Outcome]) -> list[dict[str, Any]]:
161
164
  return sorted(rows.values(), key=lambda r: r["technique"])
162
165
 
163
166
 
167
+ def origin_table(outcomes: list[Outcome]) -> list[dict[str, Any]]:
168
+ """Per origin (every origin, even with no case): cases, reported, failing the default check."""
169
+ return [
170
+ {
171
+ "origin": origin,
172
+ "cases": len(mine),
173
+ "reported": sum(o.reported for o in mine),
174
+ "fails_ci": sum(o.fails_ci for o in mine),
175
+ }
176
+ for origin in ORIGINS
177
+ for mine in [[o for o in outcomes if o.case.origin == origin]]
178
+ ]
179
+
180
+
164
181
  # --- precision on the held-out set --------------------------------------------------------------
165
182
 
166
183
  LABELS = ("tp", "benign-context", "fp")