ghostrules 0.1.0__tar.gz → 0.2.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.
- ghostrules-0.2.0/.ghostrules.lock +31 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.github/workflows/ci.yml +1 -0
- ghostrules-0.2.0/.github/workflows/self-test.yml +153 -0
- ghostrules-0.2.0/CHANGELOG.md +104 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/CONTRIBUTING.md +33 -3
- {ghostrules-0.1.0 → ghostrules-0.2.0}/PKG-INFO +248 -11
- ghostrules-0.2.0/README.md +515 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/SECURITY.md +6 -2
- {ghostrules-0.1.0 → ghostrules-0.2.0}/action.yml +54 -3
- ghostrules-0.2.0/demo/README.md +24 -0
- ghostrules-0.2.0/demo/demo.gif +0 -0
- ghostrules-0.2.0/demo/imports.tape +46 -0
- ghostrules-0.2.0/demo/lock.tape +38 -0
- ghostrules-0.2.0/docs/AGENT_MODELS.md +227 -0
- ghostrules-0.2.0/docs/DECISIONS.md +627 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/docs/RULES.md +81 -1
- ghostrules-0.2.0/docs/v0.1.1/GR012-FIX.md +90 -0
- ghostrules-0.2.0/docs/v0.2/PLAN.md +87 -0
- ghostrules-0.2.0/docs/v0.2/RULES.md +74 -0
- ghostrules-0.2.0/docs/v0.2/SPEC.md +152 -0
- ghostrules-0.2.0/examples/import-chain-repo/.claude/rules/api.md +7 -0
- ghostrules-0.2.0/examples/import-chain-repo/.env +1 -0
- ghostrules-0.2.0/examples/import-chain-repo/AGENTS.md +3 -0
- ghostrules-0.2.0/examples/import-chain-repo/CLAUDE.md +7 -0
- ghostrules-0.2.0/examples/import-chain-repo/README.md +11 -0
- ghostrules-0.2.0/examples/import-chain-repo/docs/setup.md +4 -0
- ghostrules-0.2.0/examples/import-chain-repo/docs/style.md +4 -0
- ghostrules-0.2.0/examples/import-chain-repo/shared/team-rules.md +4 -0
- ghostrules-0.2.0/examples/import-chain-repo/src/api/users.ts +1 -0
- ghostrules-0.2.0/examples/import-chain-repo/src/legacy/CLAUDE.md +3 -0
- ghostrules-0.2.0/release-notes.md +42 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/scripts/make_demo.py +107 -2
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/__init__.py +1 -1
- ghostrules-0.2.0/src/ghostrules/agents/__init__.py +49 -0
- ghostrules-0.2.0/src/ghostrules/agents/base.py +638 -0
- ghostrules-0.2.0/src/ghostrules/agents/claude_code.py +268 -0
- ghostrules-0.2.0/src/ghostrules/agents/copilot_cli.py +169 -0
- ghostrules-0.2.0/src/ghostrules/agents/cursor.py +136 -0
- ghostrules-0.2.0/src/ghostrules/agents/generic.py +80 -0
- ghostrules-0.2.0/src/ghostrules/agents/repo.py +115 -0
- ghostrules-0.2.0/src/ghostrules/cli.py +618 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/config.py +34 -1
- ghostrules-0.2.0/src/ghostrules/context_command.py +127 -0
- ghostrules-0.2.0/src/ghostrules/contextscan.py +118 -0
- ghostrules-0.2.0/src/ghostrules/globs.py +190 -0
- ghostrules-0.2.0/src/ghostrules/imports.py +120 -0
- ghostrules-0.2.0/src/ghostrules/lock.py +446 -0
- ghostrules-0.2.0/src/ghostrules/markdown_utils.py +359 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/models.py +43 -4
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/reporters/common.py +5 -0
- ghostrules-0.2.0/src/ghostrules/reporters/context.py +410 -0
- ghostrules-0.2.0/src/ghostrules/reporters/lock.py +330 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/reporters/markdown.py +4 -1
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/reporters/sarif.py +30 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/reporters/table.py +10 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/rules/__init__.py +2 -1
- ghostrules-0.2.0/src/ghostrules/rules/context_rules.py +318 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/rules/injection.py +64 -8
- ghostrules-0.2.0/src/ghostrules/rules/known_installers.py +86 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/rules/registry.py +46 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/rules/unicode.py +27 -0
- ghostrules-0.2.0/src/ghostrules/scancontext.py +29 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/scanner.py +134 -7
- ghostrules-0.2.0/src/ghostrules/sensitive.py +51 -0
- ghostrules-0.2.0/tests/test_agents_claude.py +552 -0
- ghostrules-0.2.0/tests/test_agents_others.py +322 -0
- ghostrules-0.2.0/tests/test_agents_repo.py +59 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_ci_files.py +122 -2
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_cli.py +8 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_config.py +6 -0
- ghostrules-0.2.0/tests/test_context_command.py +332 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_demo.py +75 -3
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_docs.py +94 -1
- ghostrules-0.2.0/tests/test_globs.py +101 -0
- ghostrules-0.2.0/tests/test_imports.py +187 -0
- ghostrules-0.2.0/tests/test_lock.py +450 -0
- ghostrules-0.2.0/tests/test_markdown_utils.py +265 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_rules_injection.py +127 -0
- ghostrules-0.2.0/tests/test_scan_imports.py +491 -0
- ghostrules-0.1.0/.github/workflows/self-test.yml +0 -73
- ghostrules-0.1.0/CHANGELOG.md +0 -41
- ghostrules-0.1.0/README.md +0 -278
- ghostrules-0.1.0/docs/DECISIONS.md +0 -320
- ghostrules-0.1.0/release-notes.md +0 -32
- ghostrules-0.1.0/src/ghostrules/cli.py +0 -319
- ghostrules-0.1.0/src/ghostrules/markdown_utils.py +0 -59
- ghostrules-0.1.0/tests/test_markdown_utils.py +0 -67
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.ghostrules.toml +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.gitattributes +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.github/ISSUE_TEMPLATE/false-positive.yml +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.github/ISSUE_TEMPLATE/new-rule.yml +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.github/workflows/release.yml +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.gitignore +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/.pre-commit-hooks.yaml +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/LICENSE +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/demo/demo.tape +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/docs/PLAN.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/docs/SPEC.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/clean-repo/.claude/settings.json +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/clean-repo/.mcp.json +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/clean-repo/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/clean-repo/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/clean-repo/README.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/poisoned-repo/.claude/settings.json +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/poisoned-repo/.claude/skills/setup/SKILL.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/poisoned-repo/.cursorrules +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/poisoned-repo/.github/copilot-instructions.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/poisoned-repo/.mcp.json +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/poisoned-repo/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/poisoned-repo/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/examples/poisoned-repo/README.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/pyproject.toml +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/scripts/changelog_section.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/scripts/gen_rules_table.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/scripts/make_fixtures.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/scripts/set_owner.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/_compat.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/clean.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/jsonloc.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/mcp_config.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/py.typed +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/reporters/__init__.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/reporters/json_.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/reveal.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/rules/agent_settings.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/rules/helpers.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/rules/mcp.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/rules/patterns.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/secrets.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/targets.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/src/ghostrules/unicode_utils.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/conftest.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/data/sarif-schema-2.1.0.json +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr001_flag_forged/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr001_flag_ok/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr001_tags/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr002_bidi/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr002_rtl_medium/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_bom_midfile/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_bom_ok/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_emoji_zwj_ok/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_indic_ok/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_persian_ok/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_soft_hyphen/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_zw_file_high/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_zw_line_high/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr003_zw_medium/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr004_vs_bytes/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr004_vs_ok/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr005_benign_ok/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr005_comment_imperative/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr005_comment_injection/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr005_comment_multiline/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr005_in_code_block_ok/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr005_short_ok/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr005_unterminated/CLAUDE.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr006_ok/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr006_url/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/fixtures/unicode/gr006_word/AGENTS.md +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/secret_samples.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_mcp_config.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_models.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_patterns.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_reporters.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_rules_config.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_rules_unicode.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_scanner.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_secret_masking.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_secrets.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_targets.py +0 -0
- {ghostrules-0.1.0 → ghostrules-0.2.0}/tests/test_unicode_utils.py +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"agents": {
|
|
3
|
+
"claude-code": [
|
|
4
|
+
{
|
|
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",
|
|
7
|
+
"load": "always",
|
|
8
|
+
"path": "CLAUDE.md",
|
|
9
|
+
"raw_sha256": "dc7776630751bba0d3c39e83e96b3b6dae1de7cd5630b49e3b0f7853bbbf1f3f",
|
|
10
|
+
"seen_sha256": "dc7776630751bba0d3c39e83e96b3b6dae1de7cd5630b49e3b0f7853bbbf1f3f",
|
|
11
|
+
"via": []
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"copilot-cli": [
|
|
15
|
+
{
|
|
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",
|
|
18
|
+
"load": "always",
|
|
19
|
+
"path": "CLAUDE.md",
|
|
20
|
+
"raw_sha256": "dc7776630751bba0d3c39e83e96b3b6dae1de7cd5630b49e3b0f7853bbbf1f3f",
|
|
21
|
+
"seen_sha256": "dc7776630751bba0d3c39e83e96b3b6dae1de7cd5630b49e3b0f7853bbbf1f3f",
|
|
22
|
+
"via": []
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"cursor": [],
|
|
26
|
+
"generic": []
|
|
27
|
+
},
|
|
28
|
+
"configs": [],
|
|
29
|
+
"generator": "ghostrules 0.2.0",
|
|
30
|
+
"lock_version": 1
|
|
31
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
name: Self-test
|
|
2
|
+
|
|
3
|
+
# Runs the action from this very checkout against the demo repositories.
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
action:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Poisoned demo with fail-on none (must pass, must report findings)
|
|
19
|
+
id: poisoned
|
|
20
|
+
uses: ./
|
|
21
|
+
with:
|
|
22
|
+
path: examples/poisoned-repo
|
|
23
|
+
fail-on: none
|
|
24
|
+
upload-sarif: "false"
|
|
25
|
+
|
|
26
|
+
- name: SARIF must contain results, led by the hidden instruction
|
|
27
|
+
run: |
|
|
28
|
+
python - <<'PY'
|
|
29
|
+
import json
|
|
30
|
+
run = json.load(open("ghostrules.sarif"))["runs"][0]
|
|
31
|
+
results = run["results"]
|
|
32
|
+
assert len(results) > 10, f"expected many results, got {len(results)}"
|
|
33
|
+
ids = {r["ruleId"] for r in results}
|
|
34
|
+
assert {"GR001", "GR012", "GR101"} <= ids, ids
|
|
35
|
+
assert results[0]["ruleId"] == "GR001", results[0]["ruleId"]
|
|
36
|
+
assert run["tool"]["driver"]["rules"], "rules missing"
|
|
37
|
+
print(f"ok: {len(results)} results")
|
|
38
|
+
PY
|
|
39
|
+
|
|
40
|
+
- name: The exit-code output is 0
|
|
41
|
+
env:
|
|
42
|
+
EXIT_CODE: ${{ steps.poisoned.outputs.exit-code }}
|
|
43
|
+
run: test "$EXIT_CODE" = "0"
|
|
44
|
+
|
|
45
|
+
- name: Poisoned demo with the default fail-on (must fail the step)
|
|
46
|
+
id: gate
|
|
47
|
+
uses: ./
|
|
48
|
+
continue-on-error: true
|
|
49
|
+
with:
|
|
50
|
+
path: examples/poisoned-repo
|
|
51
|
+
upload-sarif: "false"
|
|
52
|
+
|
|
53
|
+
- name: The gate must have failed
|
|
54
|
+
env:
|
|
55
|
+
OUTCOME: ${{ steps.gate.outcome }}
|
|
56
|
+
EXIT_CODE: ${{ steps.gate.outputs.exit-code }}
|
|
57
|
+
run: |
|
|
58
|
+
test "$OUTCOME" = "failure"
|
|
59
|
+
test "$EXIT_CODE" = "1"
|
|
60
|
+
|
|
61
|
+
- name: Clean demo (must pass with no results)
|
|
62
|
+
uses: ./
|
|
63
|
+
with:
|
|
64
|
+
path: examples/clean-repo
|
|
65
|
+
upload-sarif: "false"
|
|
66
|
+
|
|
67
|
+
- name: SARIF must be empty
|
|
68
|
+
run: |
|
|
69
|
+
python - <<'PY'
|
|
70
|
+
import json
|
|
71
|
+
results = json.load(open("ghostrules.sarif"))["runs"][0]["results"]
|
|
72
|
+
assert results == [], results
|
|
73
|
+
PY
|
|
74
|
+
|
|
75
|
+
import-chain:
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v4
|
|
79
|
+
|
|
80
|
+
- name: Import-chain demo, following imports (default)
|
|
81
|
+
uses: ./
|
|
82
|
+
with:
|
|
83
|
+
path: examples/import-chain-repo
|
|
84
|
+
fail-on: none
|
|
85
|
+
upload-sarif: "false"
|
|
86
|
+
|
|
87
|
+
- name: SARIF must carry the hidden payload with the import chain that reaches it
|
|
88
|
+
run: |
|
|
89
|
+
python - <<'PY'
|
|
90
|
+
import json
|
|
91
|
+
results = json.load(open("ghostrules.sarif"))["runs"][0]["results"]
|
|
92
|
+
ids = {r["ruleId"] for r in results}
|
|
93
|
+
assert {"GR001", "GR020", "GR021"} <= ids, ids
|
|
94
|
+
hidden = next(r for r in results if r["ruleId"] == "GR001")
|
|
95
|
+
chain = [l["physicalLocation"]["artifactLocation"]["uri"] for l in hidden["relatedLocations"]]
|
|
96
|
+
assert chain == ["CLAUDE.md", "docs/style.md"], chain
|
|
97
|
+
assert hidden["locations"][0]["physicalLocation"]["artifactLocation"]["uri"] == "shared/team-rules.md"
|
|
98
|
+
assert "reached-via-import" in hidden["properties"]["tags"]
|
|
99
|
+
print(f"ok: {len(results)} results")
|
|
100
|
+
PY
|
|
101
|
+
|
|
102
|
+
- name: Import-chain demo with follow-imports off (a 0.1-style scan finds nothing)
|
|
103
|
+
uses: ./
|
|
104
|
+
with:
|
|
105
|
+
path: examples/import-chain-repo
|
|
106
|
+
follow-imports: "false"
|
|
107
|
+
upload-sarif: "false"
|
|
108
|
+
|
|
109
|
+
- name: SARIF must be empty
|
|
110
|
+
run: |
|
|
111
|
+
python - <<'PY'
|
|
112
|
+
import json
|
|
113
|
+
results = json.load(open("ghostrules.sarif"))["runs"][0]["results"]
|
|
114
|
+
assert results == [], results
|
|
115
|
+
PY
|
|
116
|
+
|
|
117
|
+
- name: Lock a copy of the demo, then make a pull-request-like change to another copy
|
|
118
|
+
run: |
|
|
119
|
+
python scripts/make_demo.py --lock-scenario "$RUNNER_TEMP/acme"
|
|
120
|
+
ghostrules lock --root "$RUNNER_TEMP/acme/base"
|
|
121
|
+
cp "$RUNNER_TEMP/acme/base/.ghostrules.lock" "$RUNNER_TEMP/acme/pr/"
|
|
122
|
+
|
|
123
|
+
- name: Lock check on the unchanged repository (must pass)
|
|
124
|
+
id: unchanged
|
|
125
|
+
uses: ./
|
|
126
|
+
with:
|
|
127
|
+
path: ${{ runner.temp }}/acme/base
|
|
128
|
+
fail-on: none
|
|
129
|
+
lock-check: "true"
|
|
130
|
+
upload-sarif: "false"
|
|
131
|
+
|
|
132
|
+
- name: The unchanged lock exit code is 0
|
|
133
|
+
env:
|
|
134
|
+
LOCK_EXIT_CODE: ${{ steps.unchanged.outputs.lock-exit-code }}
|
|
135
|
+
run: test "$LOCK_EXIT_CODE" = "0"
|
|
136
|
+
|
|
137
|
+
- name: Lock check after the pull request (must fail the step)
|
|
138
|
+
id: changed
|
|
139
|
+
uses: ./
|
|
140
|
+
continue-on-error: true
|
|
141
|
+
with:
|
|
142
|
+
path: ${{ runner.temp }}/acme/pr
|
|
143
|
+
fail-on: none
|
|
144
|
+
lock-check: "true"
|
|
145
|
+
upload-sarif: "false"
|
|
146
|
+
|
|
147
|
+
- name: The changed lock must have failed with exit code 1
|
|
148
|
+
env:
|
|
149
|
+
OUTCOME: ${{ steps.changed.outcome }}
|
|
150
|
+
LOCK_EXIT_CODE: ${{ steps.changed.outputs.lock-exit-code }}
|
|
151
|
+
run: |
|
|
152
|
+
test "$OUTCOME" = "failure"
|
|
153
|
+
test "$LOCK_EXIT_CODE" = "1"
|
|
@@ -0,0 +1,104 @@
|
|
|
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.2.0] - 2026-10-28
|
|
9
|
+
|
|
10
|
+
Know exactly what your agent reads: `ghostrules` now models how each agent loads its instructions,
|
|
11
|
+
scans exactly that, shows it, and pins it.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- GitHub Action inputs `follow-imports` (default `true`) and `lock-check` (default `false`). With
|
|
15
|
+
`lock-check` the action runs `ghostrules lock --check`, puts the differences in the job summary
|
|
16
|
+
under "What your agents read changed in this PR" and fails the job if there are any; new output
|
|
17
|
+
`lock-exit-code`.
|
|
18
|
+
- Config keys `follow_imports`, `agents` and `lockfile`.
|
|
19
|
+
- `python scripts/make_demo.py --lock-scenario DEST` and the tapes `demo/imports.tape` and
|
|
20
|
+
`demo/lock.tape` for the 0.2 GIFs.
|
|
21
|
+
- `ghostrules lock`: writes `.ghostrules.lock`, a deterministic record (sorted keys, no timestamps,
|
|
22
|
+
CRLF-normalised hashes) of every file in each modelled agent's context, how it is reached, and
|
|
23
|
+
the text the agent actually receives, plus a hash of every MCP and agent settings file.
|
|
24
|
+
`lock --check` recomputes it and shows what changed (added, changed, removed, and changes that
|
|
25
|
+
only touch text an agent strips), with invisible characters revealed; exit 0 up to date, 1
|
|
26
|
+
changed, 2 error. `--format text|markdown|json`, `--update`, `--lockfile`; config key `lockfile`.
|
|
27
|
+
- `ghostrules context [TARGET]`: shows what an agent reads when it works on a file (the files it
|
|
28
|
+
loads at session start, the rules its `paths`/`globs` select, the directories that load on
|
|
29
|
+
demand, the imports it follows and the files it skips, with the reason) and scans exactly that.
|
|
30
|
+
Output as a tree, JSON or Markdown; `--agent`, `--cwd`, `--root`, `--show-text`, `--no-scan`,
|
|
31
|
+
`--home`, `--fail-on`.
|
|
32
|
+
- `examples/import-chain-repo`: a CLAUDE.md that looks clean and reaches a hidden payload through two
|
|
33
|
+
imports, plus a `.env` import and one outside the repository.
|
|
34
|
+
- `scan` follows `@path` imports by default: files an instruction file imports are scanned as
|
|
35
|
+
instructions, and their findings carry the import chain (`via`), the agents that load them and the
|
|
36
|
+
tag `reached-via-import`. `--no-follow-imports` (or `follow_imports = false`) turns it off.
|
|
37
|
+
- Rules GR020 (external import), GR021 (sensitive file imported), GR022 (import that silently does
|
|
38
|
+
not load), GR023 (import cycle), GR024 (tag for payloads reached through an import) and GR025
|
|
39
|
+
(symlinked instruction source).
|
|
40
|
+
- The files each modelled agent loads (including `.claude/rules/**`) are scanned even when the
|
|
41
|
+
discovery globs do not list them. New config keys `follow_imports` and `agents`.
|
|
42
|
+
- JSON findings gain optional `via`, `agents` and `tags`; SARIF results gain `relatedLocations` for
|
|
43
|
+
the chain and `properties.agents`/`tags`; the table and Markdown reports show the chain.
|
|
44
|
+
|
|
45
|
+
- `docs/AGENT_MODELS.md`: how each agent loads its instructions, with the documentation it is based on
|
|
46
|
+
and what is still unverified; `CONTRIBUTING.md` explains how to add a model.
|
|
47
|
+
- `ghostrules` locks its own `CLAUDE.md` (`.ghostrules.lock`), checked in CI.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- GR005 is agent-aware: it says which agents see a hidden comment. Claude Code strips block-level
|
|
51
|
+
comments from CLAUDE.md files, so the finding is info when no configured agent sees the comment.
|
|
52
|
+
|
|
53
|
+
## [0.1.1] - 2026-10-27
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
- GR012 (remote code execution pattern) now sets its severity from context instead of reporting
|
|
57
|
+
every `curl ... | sh` as critical. A well-known installer in a code block is low, in prose
|
|
58
|
+
medium; an unknown domain is high in a code block and critical in prose. Plain `http://`, IP
|
|
59
|
+
literals, non-default ports, collector domains, opaque payloads (`base64 -d | sh`,
|
|
60
|
+
`powershell -enc`) and text hidden by invisible characters stay critical. This stops the default
|
|
61
|
+
`--fail-on high` from failing on real repositories that document `uv` or `rustup`.
|
|
62
|
+
GR105 and GR107 (MCP server commands and hooks) are unchanged.
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
- Built-in list of well-known installers (uv/ruff, rustup, Bun, Deno, pnpm, Poetry, Homebrew, nvm,
|
|
66
|
+
SDKMAN, Docker, Ollama, Volta), matched on the exact host and optional path prefix, and
|
|
67
|
+
`ghostrules rules --installers` to print it.
|
|
68
|
+
- `trust_known_installers` option (default `true`); `false` restores the 0.1.0 behaviour.
|
|
69
|
+
- Inline code span awareness for GR012 (`ScanTarget.code_span_at`).
|
|
70
|
+
|
|
71
|
+
## [0.1.0] - 2026-10-27
|
|
72
|
+
|
|
73
|
+
First release.
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
- Scanner for the files AI coding agents read and obey (`AGENTS.md`, `CLAUDE.md`, `.cursorrules`,
|
|
77
|
+
Copilot instructions, skills, MCP configs, agent settings and more), with `--home` for
|
|
78
|
+
user-level configs and `--all-files` for hidden Unicode anywhere in a tree.
|
|
79
|
+
- 21 rules. Hidden content: Unicode tag characters (GR001), bidirectional controls (GR002),
|
|
80
|
+
zero-width characters (GR003), variation-selector steganography (GR004), hidden HTML comments
|
|
81
|
+
(GR005), mixed-script homoglyphs (GR006). Malicious instructions, English and Spanish: instruction
|
|
82
|
+
override (GR010), concealment (GR011), remote code execution (GR012), data exfiltration (GR013),
|
|
83
|
+
sensitive file access (GR014), encoded payloads (GR015), safety bypass (GR016). Configuration:
|
|
84
|
+
hardcoded secrets (GR101), unpinned packages (GR102), insecure transport (GR103), broad
|
|
85
|
+
filesystem scope (GR104), shell wrappers (GR105), dangerous Docker options (GR106),
|
|
86
|
+
over-permissive agent settings (GR107), auto-trusted MCP servers (GR108).
|
|
87
|
+
- Hidden text decoded by GR001, GR004 and GR015 is re-scanned by GR010-GR016 and reported with
|
|
88
|
+
one more level of severity.
|
|
89
|
+
- CLI: `scan` (the default command), `reveal`, `clean`, `rules`, `version`. Reports as a table
|
|
90
|
+
grouped by file with the decoded hidden text highlighted, JSON, SARIF 2.1.0 (validated against the
|
|
91
|
+
official schema) and Markdown. Exit codes: 0 clean, 1 findings at or above `--fail-on`, 2 error.
|
|
92
|
+
- Configuration through `.ghostrules.toml` or `[tool.ghostrules]`, and inline `ghostrules-ignore`
|
|
93
|
+
suppressions (never for GR001, GR002 or text found inside hidden characters).
|
|
94
|
+
- GitHub Action (SARIF upload to code scanning, job summary, `fail-on` gate), pre-commit hook, CI
|
|
95
|
+
with a smoke test and a self-scan, and a tag-triggered release workflow using PyPI Trusted
|
|
96
|
+
Publishing.
|
|
97
|
+
- Demo repositories `examples/poisoned-repo` (every rule fires) and `examples/clean-repo` (no
|
|
98
|
+
findings), and `demo/demo.tape` to record the README GIF.
|
|
99
|
+
|
|
100
|
+
### Security
|
|
101
|
+
- Read-only: nothing found in a scanned file is executed, imported, downloaded or sent anywhere,
|
|
102
|
+
and the tool makes no network requests. Symbolic links are not followed.
|
|
103
|
+
- Secrets are never printed: every finding, warning and error passes through one masking step
|
|
104
|
+
(`ghp_****…a1b2`), and a test fails if a secret appears in any report format.
|
|
@@ -17,8 +17,9 @@ 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
|
-
`
|
|
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
|
|
22
23
|
them in the same pull request.
|
|
23
24
|
|
|
24
25
|
## Add a rule in three steps
|
|
@@ -46,7 +47,8 @@ them in the same pull request.
|
|
|
46
47
|
|
|
47
48
|
2. **Test it.** Add at least one positive and one negative test (see `tests/test_rules_*.py`). Add
|
|
48
49
|
a case to `scripts/make_demo.py` so `examples/poisoned-repo` triggers the rule (a test fails
|
|
49
|
-
if
|
|
50
|
+
if a `GR00x`, `GR01x` or `GR1xx` rule does not fire there; the import rules `GR02x` are tested
|
|
51
|
+
in `tests/test_scan_imports.py`) and make sure `examples/clean-repo` still reports nothing.
|
|
50
52
|
Fixtures with invisible characters are generated by `scripts/make_fixtures.py`.
|
|
51
53
|
|
|
52
54
|
3. **Document it.** Describe the rule in `docs/RULES.md` (ID, severity, logic, true and false
|
|
@@ -58,6 +60,32 @@ them in the same pull request.
|
|
|
58
60
|
python scripts/gen_rules_table.py --write
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
## Add an agent model
|
|
64
|
+
|
|
65
|
+
`ghostrules context` and `ghostrules lock` need to know how an agent loads its instructions. A
|
|
66
|
+
model is one module, one section of `docs/AGENT_MODELS.md` and its tests:
|
|
67
|
+
|
|
68
|
+
1. **Write down what the agent's documentation says** in a new section of `docs/AGENT_MODELS.md`:
|
|
69
|
+
which files it reads and in what order, which are conditional or loaded on demand, whether it
|
|
70
|
+
follows `@` imports (relative to what, how deep), what it strips before the model sees the text,
|
|
71
|
+
and the page and date you checked. Anything the documentation does not state is marked
|
|
72
|
+
**UNVERIFIED** and modelled conservatively (a file the agent might read is included). Model only
|
|
73
|
+
what is written there.
|
|
74
|
+
2. **Implement it** in `src/ghostrules/agents/<name>.py` with a `build(repo, *, cwd, target, home)`
|
|
75
|
+
method that returns a `ContextGraph`. Use the `Builder` in `agents/base.py`: it reads files
|
|
76
|
+
safely (never through a symlink), applies your text transforms (`render_*`), follows imports
|
|
77
|
+
with the `ImportPolicy` you pass, hashes the result and records the edges. Register the model in
|
|
78
|
+
`agents/__init__.py` (`AGENT_NAMES`, `get_model`) and in `config.AGENT_NAMES`. Use
|
|
79
|
+
`globs.matches_any` for `paths`/`globs`/`applyTo` and `markdown_utils.parse_frontmatter` for
|
|
80
|
+
front matter.
|
|
81
|
+
3. **Test it with a fake repository** built in `tmp_path` (see `tests/test_agents_claude.py`):
|
|
82
|
+
given the tree, the working directory and the target, the ordered entries, their `load`
|
|
83
|
+
states, `matches` and `unverified` flags must be what the documentation implies. Add the
|
|
84
|
+
precedence and depth cases that apply to the agent.
|
|
85
|
+
4. **Say what you could not model** (user-level or organisation settings, environment variables)
|
|
86
|
+
in `ContextGraph.notes`, and add a line to `CHANGELOG.md` and a decision to
|
|
87
|
+
`docs/DECISIONS.md` for every judgement call.
|
|
88
|
+
|
|
61
89
|
## Rules of the repository
|
|
62
90
|
|
|
63
91
|
- **Defanged only.** Fixtures, demos and tests use `attacker.example`, `exfil.example`,
|
|
@@ -77,4 +105,6 @@ them in the same pull request.
|
|
|
77
105
|
- [ ] positive and negative tests for every new or changed detection
|
|
78
106
|
- [ ] `examples/poisoned-repo` still triggers every rule; `examples/clean-repo` is still clean
|
|
79
107
|
- [ ] `docs/RULES.md`, `docs/DECISIONS.md`, `CHANGELOG.md` and the README table are updated
|
|
108
|
+
- [ ] for an agent model: `docs/AGENT_MODELS.md` cites the documentation and the date, and the
|
|
109
|
+
unverified parts are marked
|
|
80
110
|
- [ ] nothing real in the fixtures: no secrets, domains or payloads that work
|