ward-scanner 0.2.2__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 (165) hide show
  1. ward_scanner-0.2.2/.gitattributes +20 -0
  2. ward_scanner-0.2.2/.github/dependabot.yml +44 -0
  3. ward_scanner-0.2.2/.github/workflows/bench.yml +70 -0
  4. ward_scanner-0.2.2/.github/workflows/ci.yml +77 -0
  5. ward_scanner-0.2.2/.github/workflows/release.yml +68 -0
  6. ward_scanner-0.2.2/.gitignore +35 -0
  7. ward_scanner-0.2.2/.pre-commit-hooks.yaml +39 -0
  8. ward_scanner-0.2.2/.wardignore +25 -0
  9. ward_scanner-0.2.2/CLAUDE.md +32 -0
  10. ward_scanner-0.2.2/LICENSE +21 -0
  11. ward_scanner-0.2.2/PKG-INFO +596 -0
  12. ward_scanner-0.2.2/README.md +535 -0
  13. ward_scanner-0.2.2/RELEASING.md +82 -0
  14. ward_scanner-0.2.2/SECURITY.md +133 -0
  15. ward_scanner-0.2.2/action/entrypoint.sh +74 -0
  16. ward_scanner-0.2.2/action.yml +85 -0
  17. ward_scanner-0.2.2/benchmark/README.md +43 -0
  18. ward_scanner-0.2.2/benchmark/v0.1.1-inaugural.json +85 -0
  19. ward_scanner-0.2.2/benchmark/v0.1.1-inaugural.md +58 -0
  20. ward_scanner-0.2.2/benchmark/v0.1.2.json +85 -0
  21. ward_scanner-0.2.2/benchmark/v0.1.2.md +58 -0
  22. ward_scanner-0.2.2/benchmark/v0.1.3-full.json +89 -0
  23. ward_scanner-0.2.2/benchmark/v0.1.3-full.md +62 -0
  24. ward_scanner-0.2.2/benchmark/v0.1.3-smoke.json +85 -0
  25. ward_scanner-0.2.2/benchmark/v0.1.3-smoke.md +58 -0
  26. ward_scanner-0.2.2/benchmark/v0.1.4-full.json +89 -0
  27. ward_scanner-0.2.2/benchmark/v0.1.4-full.md +62 -0
  28. ward_scanner-0.2.2/benchmark/v0.1.4-smoke.json +85 -0
  29. ward_scanner-0.2.2/benchmark/v0.1.4-smoke.md +58 -0
  30. ward_scanner-0.2.2/benchmark/v0.2.0-full.json +116 -0
  31. ward_scanner-0.2.2/benchmark/v0.2.0-full.md +62 -0
  32. ward_scanner-0.2.2/benchmark/v0.2.0-smoke.json +112 -0
  33. ward_scanner-0.2.2/benchmark/v0.2.0-smoke.md +58 -0
  34. ward_scanner-0.2.2/benchmark/v0.2.1-full.json +116 -0
  35. ward_scanner-0.2.2/benchmark/v0.2.1-full.md +62 -0
  36. ward_scanner-0.2.2/benchmark/v0.2.1-smoke.json +112 -0
  37. ward_scanner-0.2.2/benchmark/v0.2.1-smoke.md +58 -0
  38. ward_scanner-0.2.2/benchmark/v0.2.2-full.json +120 -0
  39. ward_scanner-0.2.2/benchmark/v0.2.2-full.md +62 -0
  40. ward_scanner-0.2.2/benchmark/v0.2.2-smoke.json +116 -0
  41. ward_scanner-0.2.2/benchmark/v0.2.2-smoke.md +58 -0
  42. ward_scanner-0.2.2/docs/launch-writeup.md +123 -0
  43. ward_scanner-0.2.2/pyproject.toml +131 -0
  44. ward_scanner-0.2.2/src/ward/__init__.py +35 -0
  45. ward_scanner-0.2.2/src/ward/bench/__init__.py +22 -0
  46. ward_scanner-0.2.2/src/ward/bench/compare.py +95 -0
  47. ward_scanner-0.2.2/src/ward/bench/corpora.py +126 -0
  48. ward_scanner-0.2.2/src/ward/bench/download.py +176 -0
  49. ward_scanner-0.2.2/src/ward/bench/report.py +182 -0
  50. ward_scanner-0.2.2/src/ward/bench/runner.py +220 -0
  51. ward_scanner-0.2.2/src/ward/bench/samples/README.md +25 -0
  52. ward_scanner-0.2.2/src/ward/bench/samples/advbench_harmful_behaviors.jsonl +50 -0
  53. ward_scanner-0.2.2/src/ward/bench/samples/deepset_prompt_injections.jsonl +50 -0
  54. ward_scanner-0.2.2/src/ward/bench/samples/lakera_ignore_instructions.jsonl +50 -0
  55. ward_scanner-0.2.2/src/ward/bench/samples/spikee_jailbreaks.jsonl +50 -0
  56. ward_scanner-0.2.2/src/ward/cli.py +1034 -0
  57. ward_scanner-0.2.2/src/ward/core/__init__.py +1 -0
  58. ward_scanner-0.2.2/src/ward/core/engine.py +116 -0
  59. ward_scanner-0.2.2/src/ward/core/git_metadata.py +153 -0
  60. ward_scanner-0.2.2/src/ward/core/github_api.py +110 -0
  61. ward_scanner-0.2.2/src/ward/core/models.py +133 -0
  62. ward_scanner-0.2.2/src/ward/core/normalise.py +470 -0
  63. ward_scanner-0.2.2/src/ward/core/rules.py +122 -0
  64. ward_scanner-0.2.2/src/ward/core/verdict.py +36 -0
  65. ward_scanner-0.2.2/src/ward/core/wardignore.py +64 -0
  66. ward_scanner-0.2.2/src/ward/demo.py +184 -0
  67. ward_scanner-0.2.2/src/ward/detectors/__init__.py +30 -0
  68. ward_scanner-0.2.2/src/ward/detectors/ai_tool_specific.py +10 -0
  69. ward_scanner-0.2.2/src/ward/detectors/base.py +86 -0
  70. ward_scanner-0.2.2/src/ward/detectors/exfiltration.py +10 -0
  71. ward_scanner-0.2.2/src/ward/detectors/instruction_override.py +10 -0
  72. ward_scanner-0.2.2/src/ward/detectors/obfuscation.py +279 -0
  73. ward_scanner-0.2.2/src/ward/detectors/role_manipulation.py +10 -0
  74. ward_scanner-0.2.2/src/ward/detectors/tool_call_injection.py +10 -0
  75. ward_scanner-0.2.2/src/ward/judge/__init__.py +40 -0
  76. ward_scanner-0.2.2/src/ward/judge/anthropic_judge.py +101 -0
  77. ward_scanner-0.2.2/src/ward/judge/base.py +54 -0
  78. ward_scanner-0.2.2/src/ward/judge/mock.py +56 -0
  79. ward_scanner-0.2.2/src/ward/judge/prompt.py +132 -0
  80. ward_scanner-0.2.2/src/ward/lab.py +253 -0
  81. ward_scanner-0.2.2/src/ward/lab_reviewer.py +407 -0
  82. ward_scanner-0.2.2/src/ward/reporters/__init__.py +7 -0
  83. ward_scanner-0.2.2/src/ward/reporters/json.py +60 -0
  84. ward_scanner-0.2.2/src/ward/reporters/pretty.py +65 -0
  85. ward_scanner-0.2.2/src/ward/reporters/sarif.py +111 -0
  86. ward_scanner-0.2.2/src/ward/rules/__init__.py +1 -0
  87. ward_scanner-0.2.2/src/ward/rules/ai_tool_quirks.yaml +82 -0
  88. ward_scanner-0.2.2/src/ward/rules/exfiltration.yaml +82 -0
  89. ward_scanner-0.2.2/src/ward/rules/instruction_overrides.yaml +152 -0
  90. ward_scanner-0.2.2/src/ward/rules/multilingual.yaml +99 -0
  91. ward_scanner-0.2.2/src/ward/rules/obfuscation.yaml +23 -0
  92. ward_scanner-0.2.2/src/ward/rules/role_manipulation.yaml +54 -0
  93. ward_scanner-0.2.2/src/ward/rules/tool_calls.yaml +76 -0
  94. ward_scanner-0.2.2/src/ward/selftest.py +134 -0
  95. ward_scanner-0.2.2/tests/__init__.py +0 -0
  96. ward_scanner-0.2.2/tests/conftest.py +12 -0
  97. ward_scanner-0.2.2/tests/fixtures/01_branch_ignore_previous.yaml +6 -0
  98. ward_scanner-0.2.2/tests/fixtures/02_pr_title_role_takeover.yaml +6 -0
  99. ward_scanner-0.2.2/tests/fixtures/03_pr_body_tokenizer_tag.yaml +10 -0
  100. ward_scanner-0.2.2/tests/fixtures/04_commit_message_disregard.yaml +10 -0
  101. ward_scanner-0.2.2/tests/fixtures/05_filename_rtl_override.yaml +6 -0
  102. ward_scanner-0.2.2/tests/fixtures/06_pr_body_zero_width.yaml +6 -0
  103. ward_scanner-0.2.2/tests/fixtures/07_pr_body_fake_tool_call.yaml +8 -0
  104. ward_scanner-0.2.2/tests/fixtures/08_pr_body_mcp_uri.yaml +6 -0
  105. ward_scanner-0.2.2/tests/fixtures/09_branch_shell_injection.yaml +6 -0
  106. ward_scanner-0.2.2/tests/fixtures/10_pr_body_webhook_exfil.yaml +6 -0
  107. ward_scanner-0.2.2/tests/fixtures/11_pr_body_include_secrets.yaml +6 -0
  108. ward_scanner-0.2.2/tests/fixtures/12_pr_body_dns_exfil.yaml +6 -0
  109. ward_scanner-0.2.2/tests/fixtures/13_pr_body_anthropic_tags.yaml +9 -0
  110. ward_scanner-0.2.2/tests/fixtures/14_pr_body_cursor_command.yaml +6 -0
  111. ward_scanner-0.2.2/tests/fixtures/15_filename_ignore_instructions.yaml +6 -0
  112. ward_scanner-0.2.2/tests/fixtures/16_readme_priority_escalation.yaml +10 -0
  113. ward_scanner-0.2.2/tests/fixtures/17_pr_body_markdown_comment.yaml +6 -0
  114. ward_scanner-0.2.2/tests/fixtures/18_pr_body_fake_system_block.yaml +8 -0
  115. ward_scanner-0.2.2/tests/fixtures/19_commit_message_jailbreak.yaml +9 -0
  116. ward_scanner-0.2.2/tests/fixtures/20_clean_pr.yaml +15 -0
  117. ward_scanner-0.2.2/tests/fixtures/21_data_uri_in_branch.yaml +6 -0
  118. ward_scanner-0.2.2/tests/fixtures/22_pr_body_fake_json_tool_call.yaml +8 -0
  119. ward_scanner-0.2.2/tests/fixtures/23_base64_in_commit.yaml +8 -0
  120. ward_scanner-0.2.2/tests/fixtures/24_homoglyph_cyrillic_i.yaml +6 -0
  121. ward_scanner-0.2.2/tests/fixtures/25_russian_multilingual_evasion.yaml +9 -0
  122. ward_scanner-0.2.2/tests/fixtures/26_markdown_image_exfil.yaml +8 -0
  123. ward_scanner-0.2.2/tests/fixtures/27_pretend_chat_turn.yaml +9 -0
  124. ward_scanner-0.2.2/tests/fixtures/28_note_to_ai.yaml +6 -0
  125. ward_scanner-0.2.2/tests/fixtures/29_telegram_exfil.yaml +6 -0
  126. ward_scanner-0.2.2/tests/fixtures/30_aider_add_dotenv.yaml +9 -0
  127. ward_scanner-0.2.2/tests/fixtures/31_fenced_system_block.yaml +11 -0
  128. ward_scanner-0.2.2/tests/fixtures/32_chinese_ignore_previous.yaml +6 -0
  129. ward_scanner-0.2.2/tests/fixtures/33_leetspeak_evasion.yaml +6 -0
  130. ward_scanner-0.2.2/tests/fixtures/34_intra_word_dots.yaml +6 -0
  131. ward_scanner-0.2.2/tests/fixtures/35_suppression_bypass_in_source.yaml +10 -0
  132. ward_scanner-0.2.2/tests/fixtures/36_url_encoded_payload.yaml +7 -0
  133. ward_scanner-0.2.2/tests/fixtures/37_html_entity_payload.yaml +7 -0
  134. ward_scanner-0.2.2/tests/fixtures/38_short_base64_payload.yaml +9 -0
  135. ward_scanner-0.2.2/tests/fixtures/39_all_confusable_payload.yaml +6 -0
  136. ward_scanner-0.2.2/tests/fixtures/40_unicode_tag_smuggle.yaml +6 -0
  137. ward_scanner-0.2.2/tests/fixtures/README.md +20 -0
  138. ward_scanner-0.2.2/tests/fixtures/clean/README.md +10 -0
  139. ward_scanner-0.2.2/tests/fixtures/clean/c01_typo_fix.yaml +13 -0
  140. ward_scanner-0.2.2/tests/fixtures/clean/c02_feature_with_long_description.yaml +22 -0
  141. ward_scanner-0.2.2/tests/fixtures/clean/c03_docs_system_requirements.yaml +14 -0
  142. ward_scanner-0.2.2/tests/fixtures/clean/c04_security_research_with_directive.yaml +14 -0
  143. ward_scanner-0.2.2/tests/fixtures/clean/c05_chinese_localisation.yaml +10 -0
  144. ward_scanner-0.2.2/tests/fixtures/clean/c06_config_role_admin.yaml +21 -0
  145. ward_scanner-0.2.2/tests/fixtures/clean/c07_changelog_with_attack_history.yaml +14 -0
  146. ward_scanner-0.2.2/tests/fixtures/clean/c08_signed_commit_base64.yaml +7 -0
  147. ward_scanner-0.2.2/tests/fixtures/clean/c09_clean_screenshot_markdown.yaml +9 -0
  148. ward_scanner-0.2.2/tests/fixtures/clean/c10_developer_mode_browser_extension.yaml +17 -0
  149. ward_scanner-0.2.2/tests/test_bench.py +298 -0
  150. ward_scanner-0.2.2/tests/test_clean_fixtures.py +36 -0
  151. ward_scanner-0.2.2/tests/test_cli.py +71 -0
  152. ward_scanner-0.2.2/tests/test_demo.py +34 -0
  153. ward_scanner-0.2.2/tests/test_detectors.py +180 -0
  154. ward_scanner-0.2.2/tests/test_fixtures.py +43 -0
  155. ward_scanner-0.2.2/tests/test_github_api.py +28 -0
  156. ward_scanner-0.2.2/tests/test_judge.py +212 -0
  157. ward_scanner-0.2.2/tests/test_lab.py +66 -0
  158. ward_scanner-0.2.2/tests/test_lab_reviewer.py +195 -0
  159. ward_scanner-0.2.2/tests/test_normalise.py +218 -0
  160. ward_scanner-0.2.2/tests/test_reporters.py +67 -0
  161. ward_scanner-0.2.2/tests/test_scan_local.py +216 -0
  162. ward_scanner-0.2.2/tests/test_selftest.py +29 -0
  163. ward_scanner-0.2.2/tests/test_suppression.py +82 -0
  164. ward_scanner-0.2.2/tests/test_verdict.py +49 -0
  165. ward_scanner-0.2.2/tests/test_wardignore.py +56 -0
@@ -0,0 +1,20 @@
1
+ # Force LF endings cross-platform. Without this, files authored on Windows
2
+ # arrive at Linux CI runners with CRLF, which breaks the `#!/usr/bin/env bash`
3
+ # shebang in action/entrypoint.sh and trips a few Python tools.
4
+ * text=auto eol=lf
5
+
6
+ # Binary types we never want munged.
7
+ *.png binary
8
+ *.jpg binary
9
+ *.jpeg binary
10
+ *.gif binary
11
+ *.ico binary
12
+
13
+ # Shell scripts must be LF, full stop.
14
+ *.sh text eol=lf
15
+ *.bash text eol=lf
16
+
17
+ # Windows-specific scripts stay CRLF.
18
+ *.bat text eol=crlf
19
+ *.cmd text eol=crlf
20
+ *.ps1 text eol=crlf
@@ -0,0 +1,44 @@
1
+ # Dependabot configuration.
2
+ #
3
+ # Bumps pip + GitHub Actions dependencies weekly, opens a PR per ecosystem,
4
+ # rebases automatically. Security advisories trigger updates regardless of
5
+ # the schedule. Keeps the supply chain fresh without flooding the PR queue.
6
+
7
+ version: 2
8
+ updates:
9
+ - package-ecosystem: "pip"
10
+ directory: "/"
11
+ schedule:
12
+ interval: "weekly"
13
+ day: "monday"
14
+ open-pull-requests-limit: 5
15
+ commit-message:
16
+ prefix: "deps"
17
+ include: "scope"
18
+ groups:
19
+ python-dev-deps:
20
+ dependency-type: "development"
21
+ update-types: ["minor", "patch"]
22
+ python-runtime-deps:
23
+ dependency-type: "production"
24
+ update-types: ["patch"]
25
+
26
+ - package-ecosystem: "github-actions"
27
+ directory: "/"
28
+ schedule:
29
+ interval: "weekly"
30
+ day: "monday"
31
+ open-pull-requests-limit: 3
32
+ commit-message:
33
+ prefix: "ci"
34
+ include: "scope"
35
+
36
+ - package-ecosystem: "github-actions"
37
+ directory: "/action"
38
+ schedule:
39
+ interval: "weekly"
40
+ day: "monday"
41
+ open-pull-requests-limit: 3
42
+ commit-message:
43
+ prefix: "action"
44
+ include: "scope"
@@ -0,0 +1,70 @@
1
+ name: Bench
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ # Comment on the PR with the detection-rate diff vs the base branch.
7
+ # Makes silent regressions on Ward's benchmark numbers impossible to merge
8
+ # without a human noticing.
9
+ permissions:
10
+ contents: read
11
+ pull-requests: write
12
+
13
+ jobs:
14
+ bench-diff:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Check out PR HEAD
18
+ uses: actions/checkout@v4
19
+ with:
20
+ path: pr
21
+
22
+ - name: Check out base ref
23
+ uses: actions/checkout@v4
24
+ with:
25
+ ref: ${{ github.event.pull_request.base.ref }}
26
+ path: base
27
+
28
+ - name: Set up Python
29
+ uses: actions/setup-python@v5
30
+ with:
31
+ python-version: "3.12"
32
+ cache: pip
33
+
34
+ - name: Bench the base branch
35
+ run: |
36
+ python -m pip install --upgrade pip
37
+ python -m pip install ./base
38
+ ward bench --no-cache --format json --output "${{ runner.temp }}/base.json"
39
+ python -m pip uninstall -y ward-scanner
40
+
41
+ - name: Bench the PR branch
42
+ run: |
43
+ python -m pip install ./pr
44
+ ward bench --no-cache --format json --output "${{ runner.temp }}/pr.json"
45
+
46
+ - name: Compute diff
47
+ run: |
48
+ ward bench-diff \
49
+ "${{ runner.temp }}/base.json" \
50
+ "${{ runner.temp }}/pr.json" \
51
+ --output "${{ runner.temp }}/diff.md"
52
+ cat "${{ runner.temp }}/diff.md"
53
+
54
+ - name: Post / update sticky PR comment
55
+ uses: marocchino/sticky-pull-request-comment@v2
56
+ # Skip on forks: they do not have write permission to comment on the PR.
57
+ if: ${{ github.event.pull_request.head.repo.fork == false }}
58
+ with:
59
+ header: ward-bench-diff
60
+ path: ${{ runner.temp }}/diff.md
61
+
62
+ - name: Upload bench artefacts
63
+ if: always()
64
+ uses: actions/upload-artifact@v4
65
+ with:
66
+ name: bench-diff
67
+ path: |
68
+ ${{ runner.temp }}/base.json
69
+ ${{ runner.temp }}/pr.json
70
+ ${{ runner.temp }}/diff.md
@@ -0,0 +1,77 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ name: test (${{ matrix.os }} / py${{ matrix.python }})
14
+ runs-on: ${{ matrix.os }}
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ os: [ubuntu-latest, macos-latest, windows-latest]
19
+ python: ["3.11", "3.12", "3.13"]
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: ${{ matrix.python }}
27
+ cache: pip
28
+
29
+ - name: Install dev deps
30
+ run: |
31
+ python -m pip install --upgrade pip
32
+ python -m pip install -e ".[dev]"
33
+
34
+ - name: Lint
35
+ run: |
36
+ python -m ruff check src tests
37
+ python -m ruff format --check src tests
38
+
39
+ - name: Type-check
40
+ run: python -m mypy src/ward
41
+
42
+ - name: Tests + coverage
43
+ run: python -m pytest --cov=src/ward --cov-report=term --cov-report=xml
44
+
45
+ self-scan:
46
+ name: ward scans its own repo
47
+ runs-on: ubuntu-latest
48
+ needs: test
49
+ steps:
50
+ - uses: actions/checkout@v4
51
+
52
+ - name: Set up Python
53
+ uses: actions/setup-python@v5
54
+ with:
55
+ python-version: "3.12"
56
+ cache: pip
57
+
58
+ - name: Install
59
+ run: pip install -e .
60
+
61
+ - name: scan-local
62
+ # Self-scan is informational. A security tool that describes the
63
+ # attacks it catches will inevitably trigger its own rules; the
64
+ # ward-allow-file directive handles most of it, but `continue-on-error`
65
+ # keeps any remaining hit informational rather than gating.
66
+ continue-on-error: true
67
+ run: ward scan-local --format sarif > ward-self.sarif
68
+
69
+ - name: Upload SARIF
70
+ if: always()
71
+ uses: actions/upload-artifact@v4
72
+ with:
73
+ name: ward-self-sarif
74
+ path: ward-self.sarif
75
+
76
+ - name: selftest (scripted scenarios, gating)
77
+ run: ward selftest
@@ -0,0 +1,68 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.12"
22
+
23
+ - name: Build distribution
24
+ run: |
25
+ python -m pip install --upgrade pip build
26
+ python -m build
27
+
28
+ - name: Upload distribution artifacts
29
+ uses: actions/upload-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+
34
+ publish-pypi:
35
+ # Only runs once you've set the repo variable PYPI_READY=true AND
36
+ # configured a trusted publisher at https://pypi.org for this repo.
37
+ # Until then this job is skipped so tag pushes don't fail the workflow.
38
+ if: vars.PYPI_READY == 'true'
39
+ needs: build
40
+ runs-on: ubuntu-latest
41
+ environment:
42
+ name: pypi
43
+ url: https://pypi.org/p/ward-scanner
44
+ steps:
45
+ - uses: actions/download-artifact@v4
46
+ with:
47
+ name: dist
48
+ path: dist
49
+
50
+ - name: Publish to PyPI
51
+ uses: pypa/gh-action-pypi-publish@release/v1
52
+ with:
53
+ packages-dir: dist
54
+
55
+ github-release:
56
+ needs: build
57
+ runs-on: ubuntu-latest
58
+ steps:
59
+ - uses: actions/checkout@v4
60
+ - uses: actions/download-artifact@v4
61
+ with:
62
+ name: dist
63
+ path: dist
64
+ - name: Create GitHub Release
65
+ uses: softprops/action-gh-release@v2
66
+ with:
67
+ files: dist/*
68
+ generate_release_notes: true
@@ -0,0 +1,35 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ *.egg
7
+ .eggs/
8
+ build/
9
+ dist/
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ coverage.xml
15
+ htmlcov/
16
+
17
+ # Virtual environments
18
+ .venv/
19
+ venv/
20
+ env/
21
+ .uv/
22
+
23
+ # IDE
24
+ .vscode/
25
+ .idea/
26
+ *.swp
27
+ *.swo
28
+
29
+ # OS
30
+ .DS_Store
31
+ Thumbs.db
32
+
33
+ # Ward output
34
+ *.sarif
35
+ ward-report.json
@@ -0,0 +1,39 @@
1
+ # Pre-commit framework hooks for Ward.
2
+ # Consumers add a block like this to their own .pre-commit-config.yaml:
3
+ #
4
+ # - repo: https://github.com/sonofg0tham/ward
5
+ # rev: v0.1.0
6
+ # hooks:
7
+ # - id: ward-scan-local
8
+ # args: [--fail-on, high]
9
+
10
+ - id: ward-scan-local
11
+ name: Ward scan-local
12
+ description: |
13
+ Scan the current branch name, the most recent commit messages, tag
14
+ names, every tracked file path, and the top of every Markdown / text
15
+ / source file for prompt-injection patterns.
16
+ entry: ward scan-local
17
+ language: python
18
+ pass_filenames: false
19
+ always_run: true
20
+ stages: [pre-commit, pre-push]
21
+
22
+ - id: ward-scan-stdin
23
+ name: Ward scan-stdin (commit message)
24
+ description: |
25
+ Pipe the commit message under composition through Ward. Designed for
26
+ use with `stages: [commit-msg]`.
27
+ entry: bash -c 'cat "$1" | ward scan-stdin --surface commit_message --fail-on high'
28
+ language: python
29
+ pass_filenames: true
30
+ stages: [commit-msg]
31
+
32
+ - id: ward-selftest
33
+ name: Ward selftest
34
+ description: Run the built-in adversarial scenarios. Useful as a CI gate.
35
+ entry: ward selftest
36
+ language: python
37
+ pass_filenames: false
38
+ always_run: true
39
+ stages: [manual]
@@ -0,0 +1,25 @@
1
+ # .wardignore - paths whose content scan-local should skip.
2
+ #
3
+ # Filenames are still scanned (a malicious filename in an ignored directory
4
+ # is still suspicious). Only file_content / code_comment scanning is
5
+ # suppressed.
6
+ #
7
+ # A security tool that documents and tests the attacks it catches will
8
+ # inevitably trigger its own rules. The ward-allow-file directive handles
9
+ # documentation files (README, SECURITY.md). The .wardignore here covers
10
+ # Ward's own source tree and the deliberately-adversarial test fixtures.
11
+
12
+ # Ward's own source tree. Source files contain docstrings describing the
13
+ # attack patterns; rule YAMLs describe the rules; selftest and demo
14
+ # modules embed example attacks.
15
+ src/ward/*
16
+ src/ward/**/*
17
+
18
+ # Test fixtures are intentionally adversarial. Test modules contain attack
19
+ # strings as parametrised inputs.
20
+ tests/fixtures/*
21
+ tests/fixtures/**/*
22
+ tests/test_*.py
23
+
24
+ # The bundled GitHub Action wrapper mentions the surfaces it scans.
25
+ action/*
@@ -0,0 +1,32 @@
1
+ # Ward
2
+
3
+ Pre-agent metadata scanner. Catches prompt injection in branch names, commits, PR titles/bodies and filenames before they reach an AI code reviewer. Python 3.11+ Typer CLI (`ward`) plus a composite GitHub Action.
4
+
5
+ ## Key files
6
+
7
+ - `src/ward/cli.py` - CLI entry. Commands: scan-stdin, scan-branch, scan-commit, scan-local, scan-pr, explain, selftest, attack-demo, update-rules, lab, bench, bench-diff, judge.
8
+ - `src/ward/core/` - engine, rules loader, normalise (homoglyph/unicode/TAG-block/evasion), verdict, github_api, wardignore.
9
+ - `src/ward/detectors/` - one module per attack category (instruction_override, obfuscation, exfiltration, etc.).
10
+ - `src/ward/rules/*.yaml` - bundled rule packs.
11
+ - `src/ward/reporters/` - pretty, json, sarif output.
12
+ - `src/ward/bench/` - benchmark harness (corpora, runner, report, compare, download) + bundled samples.
13
+ - `src/ward/judge/` - optional LLM judge tier (base, mock, anthropic_judge, prompt). Off by default; needs the [judge] extra.
14
+ - `src/ward/lab.py` + `src/ward/lab_reviewer.py` - lab harnesses. `lab attack` (metadata-flow demo, mock) and `lab review` (real reviewer agent: NaiveReviewer offline / AnthropicReviewer via [judge]).
15
+ - `action.yml` - the GitHub Action at repo root (inputs: pr, fail-on, format, upload-sarif...). entrypoint under `action/`.
16
+ - `tests/fixtures/` - numbered YAML attack fixtures; `tests/fixtures/clean/` - false-positive fixtures that must NOT trigger.
17
+
18
+ ## Run / test
19
+
20
+ ```
21
+ pip install -e ".[dev]"
22
+ pytest # coverage must stay >= 75%
23
+ ruff check . && ruff format .
24
+ mypy # strict mode, covers src/ward
25
+ ```
26
+
27
+ ## Conventions
28
+
29
+ - Ruff: line-length 100, py311 target. Mypy strict.
30
+ - RUF001-003 are deliberately ignored in homoglyph-handling files (normalise, obfuscation, demo, selftest and their tests). Never "fix" Cyrillic or ambiguous unicode there - it is the test payload.
31
+ - `class Foo(str, Enum)` is intentional; do not convert to StrEnum (changes str() output).
32
+ - New detection rules need both an attack fixture and, where relevant, a clean fixture proving no false positive.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Craig McCart
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.