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.
- ward_scanner-0.2.2/.gitattributes +20 -0
- ward_scanner-0.2.2/.github/dependabot.yml +44 -0
- ward_scanner-0.2.2/.github/workflows/bench.yml +70 -0
- ward_scanner-0.2.2/.github/workflows/ci.yml +77 -0
- ward_scanner-0.2.2/.github/workflows/release.yml +68 -0
- ward_scanner-0.2.2/.gitignore +35 -0
- ward_scanner-0.2.2/.pre-commit-hooks.yaml +39 -0
- ward_scanner-0.2.2/.wardignore +25 -0
- ward_scanner-0.2.2/CLAUDE.md +32 -0
- ward_scanner-0.2.2/LICENSE +21 -0
- ward_scanner-0.2.2/PKG-INFO +596 -0
- ward_scanner-0.2.2/README.md +535 -0
- ward_scanner-0.2.2/RELEASING.md +82 -0
- ward_scanner-0.2.2/SECURITY.md +133 -0
- ward_scanner-0.2.2/action/entrypoint.sh +74 -0
- ward_scanner-0.2.2/action.yml +85 -0
- ward_scanner-0.2.2/benchmark/README.md +43 -0
- ward_scanner-0.2.2/benchmark/v0.1.1-inaugural.json +85 -0
- ward_scanner-0.2.2/benchmark/v0.1.1-inaugural.md +58 -0
- ward_scanner-0.2.2/benchmark/v0.1.2.json +85 -0
- ward_scanner-0.2.2/benchmark/v0.1.2.md +58 -0
- ward_scanner-0.2.2/benchmark/v0.1.3-full.json +89 -0
- ward_scanner-0.2.2/benchmark/v0.1.3-full.md +62 -0
- ward_scanner-0.2.2/benchmark/v0.1.3-smoke.json +85 -0
- ward_scanner-0.2.2/benchmark/v0.1.3-smoke.md +58 -0
- ward_scanner-0.2.2/benchmark/v0.1.4-full.json +89 -0
- ward_scanner-0.2.2/benchmark/v0.1.4-full.md +62 -0
- ward_scanner-0.2.2/benchmark/v0.1.4-smoke.json +85 -0
- ward_scanner-0.2.2/benchmark/v0.1.4-smoke.md +58 -0
- ward_scanner-0.2.2/benchmark/v0.2.0-full.json +116 -0
- ward_scanner-0.2.2/benchmark/v0.2.0-full.md +62 -0
- ward_scanner-0.2.2/benchmark/v0.2.0-smoke.json +112 -0
- ward_scanner-0.2.2/benchmark/v0.2.0-smoke.md +58 -0
- ward_scanner-0.2.2/benchmark/v0.2.1-full.json +116 -0
- ward_scanner-0.2.2/benchmark/v0.2.1-full.md +62 -0
- ward_scanner-0.2.2/benchmark/v0.2.1-smoke.json +112 -0
- ward_scanner-0.2.2/benchmark/v0.2.1-smoke.md +58 -0
- ward_scanner-0.2.2/benchmark/v0.2.2-full.json +120 -0
- ward_scanner-0.2.2/benchmark/v0.2.2-full.md +62 -0
- ward_scanner-0.2.2/benchmark/v0.2.2-smoke.json +116 -0
- ward_scanner-0.2.2/benchmark/v0.2.2-smoke.md +58 -0
- ward_scanner-0.2.2/docs/launch-writeup.md +123 -0
- ward_scanner-0.2.2/pyproject.toml +131 -0
- ward_scanner-0.2.2/src/ward/__init__.py +35 -0
- ward_scanner-0.2.2/src/ward/bench/__init__.py +22 -0
- ward_scanner-0.2.2/src/ward/bench/compare.py +95 -0
- ward_scanner-0.2.2/src/ward/bench/corpora.py +126 -0
- ward_scanner-0.2.2/src/ward/bench/download.py +176 -0
- ward_scanner-0.2.2/src/ward/bench/report.py +182 -0
- ward_scanner-0.2.2/src/ward/bench/runner.py +220 -0
- ward_scanner-0.2.2/src/ward/bench/samples/README.md +25 -0
- ward_scanner-0.2.2/src/ward/bench/samples/advbench_harmful_behaviors.jsonl +50 -0
- ward_scanner-0.2.2/src/ward/bench/samples/deepset_prompt_injections.jsonl +50 -0
- ward_scanner-0.2.2/src/ward/bench/samples/lakera_ignore_instructions.jsonl +50 -0
- ward_scanner-0.2.2/src/ward/bench/samples/spikee_jailbreaks.jsonl +50 -0
- ward_scanner-0.2.2/src/ward/cli.py +1034 -0
- ward_scanner-0.2.2/src/ward/core/__init__.py +1 -0
- ward_scanner-0.2.2/src/ward/core/engine.py +116 -0
- ward_scanner-0.2.2/src/ward/core/git_metadata.py +153 -0
- ward_scanner-0.2.2/src/ward/core/github_api.py +110 -0
- ward_scanner-0.2.2/src/ward/core/models.py +133 -0
- ward_scanner-0.2.2/src/ward/core/normalise.py +470 -0
- ward_scanner-0.2.2/src/ward/core/rules.py +122 -0
- ward_scanner-0.2.2/src/ward/core/verdict.py +36 -0
- ward_scanner-0.2.2/src/ward/core/wardignore.py +64 -0
- ward_scanner-0.2.2/src/ward/demo.py +184 -0
- ward_scanner-0.2.2/src/ward/detectors/__init__.py +30 -0
- ward_scanner-0.2.2/src/ward/detectors/ai_tool_specific.py +10 -0
- ward_scanner-0.2.2/src/ward/detectors/base.py +86 -0
- ward_scanner-0.2.2/src/ward/detectors/exfiltration.py +10 -0
- ward_scanner-0.2.2/src/ward/detectors/instruction_override.py +10 -0
- ward_scanner-0.2.2/src/ward/detectors/obfuscation.py +279 -0
- ward_scanner-0.2.2/src/ward/detectors/role_manipulation.py +10 -0
- ward_scanner-0.2.2/src/ward/detectors/tool_call_injection.py +10 -0
- ward_scanner-0.2.2/src/ward/judge/__init__.py +40 -0
- ward_scanner-0.2.2/src/ward/judge/anthropic_judge.py +101 -0
- ward_scanner-0.2.2/src/ward/judge/base.py +54 -0
- ward_scanner-0.2.2/src/ward/judge/mock.py +56 -0
- ward_scanner-0.2.2/src/ward/judge/prompt.py +132 -0
- ward_scanner-0.2.2/src/ward/lab.py +253 -0
- ward_scanner-0.2.2/src/ward/lab_reviewer.py +407 -0
- ward_scanner-0.2.2/src/ward/reporters/__init__.py +7 -0
- ward_scanner-0.2.2/src/ward/reporters/json.py +60 -0
- ward_scanner-0.2.2/src/ward/reporters/pretty.py +65 -0
- ward_scanner-0.2.2/src/ward/reporters/sarif.py +111 -0
- ward_scanner-0.2.2/src/ward/rules/__init__.py +1 -0
- ward_scanner-0.2.2/src/ward/rules/ai_tool_quirks.yaml +82 -0
- ward_scanner-0.2.2/src/ward/rules/exfiltration.yaml +82 -0
- ward_scanner-0.2.2/src/ward/rules/instruction_overrides.yaml +152 -0
- ward_scanner-0.2.2/src/ward/rules/multilingual.yaml +99 -0
- ward_scanner-0.2.2/src/ward/rules/obfuscation.yaml +23 -0
- ward_scanner-0.2.2/src/ward/rules/role_manipulation.yaml +54 -0
- ward_scanner-0.2.2/src/ward/rules/tool_calls.yaml +76 -0
- ward_scanner-0.2.2/src/ward/selftest.py +134 -0
- ward_scanner-0.2.2/tests/__init__.py +0 -0
- ward_scanner-0.2.2/tests/conftest.py +12 -0
- ward_scanner-0.2.2/tests/fixtures/01_branch_ignore_previous.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/02_pr_title_role_takeover.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/03_pr_body_tokenizer_tag.yaml +10 -0
- ward_scanner-0.2.2/tests/fixtures/04_commit_message_disregard.yaml +10 -0
- ward_scanner-0.2.2/tests/fixtures/05_filename_rtl_override.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/06_pr_body_zero_width.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/07_pr_body_fake_tool_call.yaml +8 -0
- ward_scanner-0.2.2/tests/fixtures/08_pr_body_mcp_uri.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/09_branch_shell_injection.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/10_pr_body_webhook_exfil.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/11_pr_body_include_secrets.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/12_pr_body_dns_exfil.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/13_pr_body_anthropic_tags.yaml +9 -0
- ward_scanner-0.2.2/tests/fixtures/14_pr_body_cursor_command.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/15_filename_ignore_instructions.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/16_readme_priority_escalation.yaml +10 -0
- ward_scanner-0.2.2/tests/fixtures/17_pr_body_markdown_comment.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/18_pr_body_fake_system_block.yaml +8 -0
- ward_scanner-0.2.2/tests/fixtures/19_commit_message_jailbreak.yaml +9 -0
- ward_scanner-0.2.2/tests/fixtures/20_clean_pr.yaml +15 -0
- ward_scanner-0.2.2/tests/fixtures/21_data_uri_in_branch.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/22_pr_body_fake_json_tool_call.yaml +8 -0
- ward_scanner-0.2.2/tests/fixtures/23_base64_in_commit.yaml +8 -0
- ward_scanner-0.2.2/tests/fixtures/24_homoglyph_cyrillic_i.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/25_russian_multilingual_evasion.yaml +9 -0
- ward_scanner-0.2.2/tests/fixtures/26_markdown_image_exfil.yaml +8 -0
- ward_scanner-0.2.2/tests/fixtures/27_pretend_chat_turn.yaml +9 -0
- ward_scanner-0.2.2/tests/fixtures/28_note_to_ai.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/29_telegram_exfil.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/30_aider_add_dotenv.yaml +9 -0
- ward_scanner-0.2.2/tests/fixtures/31_fenced_system_block.yaml +11 -0
- ward_scanner-0.2.2/tests/fixtures/32_chinese_ignore_previous.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/33_leetspeak_evasion.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/34_intra_word_dots.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/35_suppression_bypass_in_source.yaml +10 -0
- ward_scanner-0.2.2/tests/fixtures/36_url_encoded_payload.yaml +7 -0
- ward_scanner-0.2.2/tests/fixtures/37_html_entity_payload.yaml +7 -0
- ward_scanner-0.2.2/tests/fixtures/38_short_base64_payload.yaml +9 -0
- ward_scanner-0.2.2/tests/fixtures/39_all_confusable_payload.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/40_unicode_tag_smuggle.yaml +6 -0
- ward_scanner-0.2.2/tests/fixtures/README.md +20 -0
- ward_scanner-0.2.2/tests/fixtures/clean/README.md +10 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c01_typo_fix.yaml +13 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c02_feature_with_long_description.yaml +22 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c03_docs_system_requirements.yaml +14 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c04_security_research_with_directive.yaml +14 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c05_chinese_localisation.yaml +10 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c06_config_role_admin.yaml +21 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c07_changelog_with_attack_history.yaml +14 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c08_signed_commit_base64.yaml +7 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c09_clean_screenshot_markdown.yaml +9 -0
- ward_scanner-0.2.2/tests/fixtures/clean/c10_developer_mode_browser_extension.yaml +17 -0
- ward_scanner-0.2.2/tests/test_bench.py +298 -0
- ward_scanner-0.2.2/tests/test_clean_fixtures.py +36 -0
- ward_scanner-0.2.2/tests/test_cli.py +71 -0
- ward_scanner-0.2.2/tests/test_demo.py +34 -0
- ward_scanner-0.2.2/tests/test_detectors.py +180 -0
- ward_scanner-0.2.2/tests/test_fixtures.py +43 -0
- ward_scanner-0.2.2/tests/test_github_api.py +28 -0
- ward_scanner-0.2.2/tests/test_judge.py +212 -0
- ward_scanner-0.2.2/tests/test_lab.py +66 -0
- ward_scanner-0.2.2/tests/test_lab_reviewer.py +195 -0
- ward_scanner-0.2.2/tests/test_normalise.py +218 -0
- ward_scanner-0.2.2/tests/test_reporters.py +67 -0
- ward_scanner-0.2.2/tests/test_scan_local.py +216 -0
- ward_scanner-0.2.2/tests/test_selftest.py +29 -0
- ward_scanner-0.2.2/tests/test_suppression.py +82 -0
- ward_scanner-0.2.2/tests/test_verdict.py +49 -0
- 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.
|