antibody 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.
@@ -0,0 +1,49 @@
1
+ name: publish
2
+
3
+ # Publishes to PyPI when a GitHub release is published. It uses trusted publishing, so no
4
+ # token is stored anywhere. One-time setup on PyPI: add a pending publisher with owner
5
+ # "cipheraxat", repository "antibody", workflow "publish.yml" and environment "pypi".
6
+
7
+ on:
8
+ release:
9
+ types: [published]
10
+
11
+ permissions: {}
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ contents: read
18
+ steps:
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
+ with:
21
+ persist-credentials: false
22
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
23
+ with:
24
+ enable-cache: false # no shared cache in a release workflow
25
+ - run: uv sync --frozen
26
+ - run: uv run pytest -q
27
+
28
+ pypi:
29
+ needs: test
30
+ runs-on: ubuntu-latest
31
+ environment:
32
+ name: pypi
33
+ url: https://pypi.org/p/antibody
34
+ permissions:
35
+ contents: read
36
+ id-token: write # trusted publishing
37
+ steps:
38
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
39
+ with:
40
+ persist-credentials: false
41
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
42
+ with:
43
+ enable-cache: false
44
+ - name: Check the release tag matches the package version
45
+ run: |
46
+ version=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
47
+ test "v$version" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME != v$version"; exit 1; }
48
+ - run: uv build
49
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,5 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ dist/
@@ -0,0 +1 @@
1
+ 3.12
antibody-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cipheraxat
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.
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.5
2
+ Name: antibody
3
+ Version: 0.2.0
4
+ Summary: Turn every fix into a verified rule that stops the mistake coming back, enforced on agent edits and in CI.
5
+ Project-URL: Repository, https://github.com/cipheraxat/antibody
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: ruamel-yaml>=0.18
10
+ Provides-Extra: semgrep
11
+ Requires-Dist: semgrep>=1.100; extra == 'semgrep'
12
+ Description-Content-Type: text/markdown
13
+
14
+ # antibody
15
+
16
+ **Every bug you fix, and every mistake you correct your coding agent on, becomes a verified
17
+ rule that stops it coming back.**
18
+
19
+ Agents repeat mistakes. So do teams. A note in `CLAUDE.md` is a suggestion, and the
20
+ lesson from a fix is usually gone the moment it merges. `antibody` keeps it. Your coding
21
+ agent drafts a [Semgrep](https://semgrep.dev)-format rule from the fix. `antibody` *proves*
22
+ the rule fires on the mistake and stays silent on the fix. It then enforces the rule on
23
+ every file the agent edits and in CI, with no LLM, no network and no tokens. Only new
24
+ matches count, so turning it on never breaks a build.
25
+
26
+ The LLM writes the check once. A deterministic tool runs it from then on.
27
+
28
+ ## What it looks like
29
+
30
+ A fix made timestamps timezone-aware. Later, Claude Code writes `datetime.utcnow()` in
31
+ another file, and the edit hook answers right away:
32
+
33
+ ```
34
+ antibody: this edit repeats a mistake that was fixed before
35
+ app/invoices.py:5 [naive-datetime] This makes a timestamp with no timezone, which breaks comparisons and shifts times across servers. Use datetime.now(timezone.utc).
36
+ learned from 95ffe5603abe "Use timezone-aware timestamps in billing"
37
+ Fix the code. Ask the user before suppressing it with `# nosemgrep`.
38
+ ```
39
+
40
+ Claude Code shows that message to Claude, which fixes the code before moving on. An old
41
+ `datetime.now()` elsewhere in the repo was accepted in the baseline, so it doesn't block
42
+ anyone.
43
+
44
+ **In a live session** (Claude Code 2.1.300 with Sonnet), Claude was told to use
45
+ `datetime.utcnow()` *anyway*, "because our serializer expects naive UTC". It made the
46
+ edit and the hook blocked it. Claude named the rule, the line and the commit the lesson
47
+ came from. It didn't silently override the request. It asked whether to keep `utcnow()`
48
+ with `# nosemgrep: naive-datetime` or switch to `datetime.now(timezone.utc)`. Given the
49
+ same task without the insistence, Claude read the antibody's commit and wrote the
50
+ timezone-aware version from the start.
51
+
52
+ ## Quick start
53
+
54
+ ```bash
55
+ # Not on PyPI yet: install from GitHub. The [semgrep] extra brings the engine along.
56
+ uv tool install "antibody[semgrep] @ git+https://github.com/cipheraxat/antibody"
57
+
58
+ cd your-repo
59
+ antibody init --agent claude # .antibodies/, the agent skill, and the edit hook in .claude/settings.json
60
+ ```
61
+
62
+ Then, after a fix, ask your agent to *"make an antibody from that fix"*. The skill walks
63
+ it through these steps, which you can also run yourself:
64
+
65
+ ```bash
66
+ antibody learn # learns from uncommitted changes if there are any, else HEAD
67
+ antibody verify <id> # the proof; iterate on the rule until it passes
68
+ antibody sweep <id> # every match today: fix the real repeats...
69
+ antibody baseline <id> # ...and accept the rest, so only new matches fail
70
+ ```
71
+
72
+ ## Three ways to learn
73
+
74
+ | The fix is | Command | What `verify` proves |
75
+ |---|---|---|
76
+ | a commit | `antibody learn <commit>` | Fires near the change in the fix's parent, and never on lines the fix wrote |
77
+ | uncommitted | `antibody learn --id <name>` | The same, `HEAD` against your working tree. After you commit, `verify` pins it to the commit automatically |
78
+ | not in git (the agent made the mistake and fixed it in one session) | `antibody learn --fixtures-only --id <name>` | The fixture: every `ruleid:` line matches, every `ok:` line doesn't |
79
+
80
+ Tests and `.antibodies/` are left out of the proof. Matches the fix left in place are
81
+ reported as possible repeats.
82
+
83
+ ## Only new matches fail
84
+
85
+ - **The edit hook** compares the edited file with `HEAD`. It blocks only mistakes the
86
+ agent just introduced, never old code it happened to touch.
87
+ - **`antibody baseline`** records today's accepted matches in `.antibodies/baseline.json`,
88
+ keyed by the matched code rather than line numbers. `antibody check` then fails only on
89
+ matches beyond it. A pasted copy of an accepted line still counts as new.
90
+ - **`antibody check --since origin/main`** looks only at files changed since a ref. That
91
+ suits pull requests.
92
+
93
+ ```yaml
94
+ # .github/workflows/antibody.yml (pin actions to commit SHAs in real use)
95
+ on: [pull_request]
96
+ jobs:
97
+ antibody:
98
+ runs-on: ubuntu-latest
99
+ steps:
100
+ - uses: actions/checkout@v7
101
+ with: { fetch-depth: 0 }
102
+ - uses: astral-sh/setup-uv@v10
103
+ - run: uvx --from "antibody[semgrep] @ git+https://github.com/cipheraxat/antibody" antibody check --since origin/${{ github.base_ref }}
104
+ ```
105
+
106
+ ## The hook
107
+
108
+ `antibody init --agent claude` adds this to `.claude/settings.json`, keeping any hooks
109
+ already there:
110
+
111
+ ```json
112
+ {"hooks": {"PostToolUse": [{"matcher": "Edit|Write", "hooks": [
113
+ {"type": "command", "command": "antibody", "args": ["hook", "claude"], "timeout": 60,
114
+ "statusMessage": "antibody: checking edit"}]}]}}
115
+ ```
116
+
117
+ It takes about **0.5 s** on a file an antibody covers with Semgrep 1.181, **1.4 s** with
118
+ Opengrep 1.31, and **0.25 s** on anything else, measured on an M-series Mac. It exits 2
119
+ with the message above on a new match, which Claude Code shows to Claude. It exits 0 when
120
+ clean, and 1 if something is broken, so a broken hook never blocks your work.
121
+
122
+ ## A security example: setuptools CVE-2024-6345
123
+
124
+ setuptools formatted package URLs into `os.system` command strings. From the fix
125
+ [`88807c7`](https://github.com/pypa/setuptools/commit/88807c7062788254f654ea8c03427adc859321f0),
126
+ an agent wrote [`examples/os-system-formatted-command.yml`](examples/os-system-formatted-command.yml).
127
+ `antibody verify` confirmed that it catches all four vulnerable calls and none of the
128
+ fixed code. The sweep found no other instances in setuptools. The rule doesn't mention
129
+ setuptools, so it works on any Python codebase.
130
+
131
+ ## Commands
132
+
133
+ | Command | What it does |
134
+ |---|---|
135
+ | `antibody init [--agent claude] [--skill]` | Create `.antibodies/`, plus the skill and edit hook |
136
+ | `antibody learn [commit] [--id ID] [--fixtures-only]` | Write a skeleton and print a brief for the agent |
137
+ | `antibody verify <id>... \| --all` | Prove each antibody; `--fix COMMIT` records the fix, `--no-stamp` leaves files alone |
138
+ | `antibody sweep <id> [paths]` | List every match of one antibody |
139
+ | `antibody baseline [ids]` | Accept today's matches |
140
+ | `antibody check [paths] [--since REF] [--no-baseline] [--sarif FILE]` | New matches from verified antibodies; exit 1 if any |
141
+ | `antibody list` | Antibodies, how each was proven, and their kind |
142
+ | `antibody hook claude` | The edit hook (reads Claude Code's event on stdin) |
143
+
144
+ All but `hook` take `--repo PATH`. `verify`, `sweep`, `check` and `list` take `--json`.
145
+
146
+ ## The file format
147
+
148
+ An antibody is a plain Semgrep rule with its history under `metadata.antibody`, so
149
+ `semgrep scan --config .antibodies --exclude .antibodies` works without antibody.
150
+
151
+ ```yaml
152
+ rules:
153
+ - id: naive-datetime
154
+ message: >-
155
+ This makes a timestamp with no timezone ... Use datetime.now(timezone.utc).
156
+ severity: ERROR
157
+ languages: [python]
158
+ pattern-either:
159
+ - pattern: datetime.datetime.utcnow()
160
+ - pattern: datetime.datetime.now()
161
+ metadata:
162
+ antibody:
163
+ kind: bug # bug, security or convention
164
+ source:
165
+ commit: "95ffe5603abe..."
166
+ summary: "Use timezone-aware timestamps in billing"
167
+ verified: # written by `antibody verify`
168
+ engine: semgrep 1.181.0
169
+ proof: commit
170
+ vulnerable: 8d7f55689db5
171
+ fixed: 95ffe5603abe
172
+ fires_on:
173
+ - app/billing.py:5
174
+ at: '2026-10-18'
175
+ ```
176
+
177
+ A file next to it with the same name and a code extension (`naive-datetime.py`) is its
178
+ fixture, in Semgrep's annotation style:
179
+
180
+ ```python
181
+ # ruleid: naive-datetime
182
+ issued = datetime.utcnow()
183
+ # ok: naive-datetime
184
+ issued = datetime.now(timezone.utc)
185
+ ```
186
+
187
+ ## Engine
188
+
189
+ Antibody works with Semgrep CE and [Opengrep](https://github.com/opengrep/opengrep). The
190
+ full test suite passes on Semgrep 1.181 and Opengrep 1.31. It uses the engine installed
191
+ with it (the `[semgrep]` extra) first, then Opengrep, then Semgrep from `PATH`. Pass
192
+ `--engine` or set `$ANTIBODY_ENGINE` (a name or a path) to choose. Both run through their
193
+ fast OCaml CLI, and Semgrep always runs with `--metrics=off`. Python and
194
+ JavaScript/TypeScript are the first targets, and any language the engine supports works.
195
+
196
+ ## Status
197
+
198
+ v0.2. Next:
199
+
200
+ - a Claude Code plugin bundling the skill and hook (after a PyPI release)
201
+ - blocking a bad edit *before* it's written (PreToolUse), and hooks for Cursor, Codex and opencode
202
+ - `learn --auto` with the Claude API, for pull-request bots
203
+ - snapshotting agent edits, so corrections are proven against real code rather than fixtures
204
+
205
+ ## Development
206
+
207
+ ```bash
208
+ uv sync
209
+ uv run pytest # runs against real git repos and the real engine (~30 s)
210
+ ```
211
+
212
+ ## License
213
+
214
+ [MIT](LICENSE)
@@ -0,0 +1,201 @@
1
+ # antibody
2
+
3
+ **Every bug you fix, and every mistake you correct your coding agent on, becomes a verified
4
+ rule that stops it coming back.**
5
+
6
+ Agents repeat mistakes. So do teams. A note in `CLAUDE.md` is a suggestion, and the
7
+ lesson from a fix is usually gone the moment it merges. `antibody` keeps it. Your coding
8
+ agent drafts a [Semgrep](https://semgrep.dev)-format rule from the fix. `antibody` *proves*
9
+ the rule fires on the mistake and stays silent on the fix. It then enforces the rule on
10
+ every file the agent edits and in CI, with no LLM, no network and no tokens. Only new
11
+ matches count, so turning it on never breaks a build.
12
+
13
+ The LLM writes the check once. A deterministic tool runs it from then on.
14
+
15
+ ## What it looks like
16
+
17
+ A fix made timestamps timezone-aware. Later, Claude Code writes `datetime.utcnow()` in
18
+ another file, and the edit hook answers right away:
19
+
20
+ ```
21
+ antibody: this edit repeats a mistake that was fixed before
22
+ app/invoices.py:5 [naive-datetime] This makes a timestamp with no timezone, which breaks comparisons and shifts times across servers. Use datetime.now(timezone.utc).
23
+ learned from 95ffe5603abe "Use timezone-aware timestamps in billing"
24
+ Fix the code. Ask the user before suppressing it with `# nosemgrep`.
25
+ ```
26
+
27
+ Claude Code shows that message to Claude, which fixes the code before moving on. An old
28
+ `datetime.now()` elsewhere in the repo was accepted in the baseline, so it doesn't block
29
+ anyone.
30
+
31
+ **In a live session** (Claude Code 2.1.300 with Sonnet), Claude was told to use
32
+ `datetime.utcnow()` *anyway*, "because our serializer expects naive UTC". It made the
33
+ edit and the hook blocked it. Claude named the rule, the line and the commit the lesson
34
+ came from. It didn't silently override the request. It asked whether to keep `utcnow()`
35
+ with `# nosemgrep: naive-datetime` or switch to `datetime.now(timezone.utc)`. Given the
36
+ same task without the insistence, Claude read the antibody's commit and wrote the
37
+ timezone-aware version from the start.
38
+
39
+ ## Quick start
40
+
41
+ ```bash
42
+ # Not on PyPI yet: install from GitHub. The [semgrep] extra brings the engine along.
43
+ uv tool install "antibody[semgrep] @ git+https://github.com/cipheraxat/antibody"
44
+
45
+ cd your-repo
46
+ antibody init --agent claude # .antibodies/, the agent skill, and the edit hook in .claude/settings.json
47
+ ```
48
+
49
+ Then, after a fix, ask your agent to *"make an antibody from that fix"*. The skill walks
50
+ it through these steps, which you can also run yourself:
51
+
52
+ ```bash
53
+ antibody learn # learns from uncommitted changes if there are any, else HEAD
54
+ antibody verify <id> # the proof; iterate on the rule until it passes
55
+ antibody sweep <id> # every match today: fix the real repeats...
56
+ antibody baseline <id> # ...and accept the rest, so only new matches fail
57
+ ```
58
+
59
+ ## Three ways to learn
60
+
61
+ | The fix is | Command | What `verify` proves |
62
+ |---|---|---|
63
+ | a commit | `antibody learn <commit>` | Fires near the change in the fix's parent, and never on lines the fix wrote |
64
+ | uncommitted | `antibody learn --id <name>` | The same, `HEAD` against your working tree. After you commit, `verify` pins it to the commit automatically |
65
+ | not in git (the agent made the mistake and fixed it in one session) | `antibody learn --fixtures-only --id <name>` | The fixture: every `ruleid:` line matches, every `ok:` line doesn't |
66
+
67
+ Tests and `.antibodies/` are left out of the proof. Matches the fix left in place are
68
+ reported as possible repeats.
69
+
70
+ ## Only new matches fail
71
+
72
+ - **The edit hook** compares the edited file with `HEAD`. It blocks only mistakes the
73
+ agent just introduced, never old code it happened to touch.
74
+ - **`antibody baseline`** records today's accepted matches in `.antibodies/baseline.json`,
75
+ keyed by the matched code rather than line numbers. `antibody check` then fails only on
76
+ matches beyond it. A pasted copy of an accepted line still counts as new.
77
+ - **`antibody check --since origin/main`** looks only at files changed since a ref. That
78
+ suits pull requests.
79
+
80
+ ```yaml
81
+ # .github/workflows/antibody.yml (pin actions to commit SHAs in real use)
82
+ on: [pull_request]
83
+ jobs:
84
+ antibody:
85
+ runs-on: ubuntu-latest
86
+ steps:
87
+ - uses: actions/checkout@v7
88
+ with: { fetch-depth: 0 }
89
+ - uses: astral-sh/setup-uv@v10
90
+ - run: uvx --from "antibody[semgrep] @ git+https://github.com/cipheraxat/antibody" antibody check --since origin/${{ github.base_ref }}
91
+ ```
92
+
93
+ ## The hook
94
+
95
+ `antibody init --agent claude` adds this to `.claude/settings.json`, keeping any hooks
96
+ already there:
97
+
98
+ ```json
99
+ {"hooks": {"PostToolUse": [{"matcher": "Edit|Write", "hooks": [
100
+ {"type": "command", "command": "antibody", "args": ["hook", "claude"], "timeout": 60,
101
+ "statusMessage": "antibody: checking edit"}]}]}}
102
+ ```
103
+
104
+ It takes about **0.5 s** on a file an antibody covers with Semgrep 1.181, **1.4 s** with
105
+ Opengrep 1.31, and **0.25 s** on anything else, measured on an M-series Mac. It exits 2
106
+ with the message above on a new match, which Claude Code shows to Claude. It exits 0 when
107
+ clean, and 1 if something is broken, so a broken hook never blocks your work.
108
+
109
+ ## A security example: setuptools CVE-2024-6345
110
+
111
+ setuptools formatted package URLs into `os.system` command strings. From the fix
112
+ [`88807c7`](https://github.com/pypa/setuptools/commit/88807c7062788254f654ea8c03427adc859321f0),
113
+ an agent wrote [`examples/os-system-formatted-command.yml`](examples/os-system-formatted-command.yml).
114
+ `antibody verify` confirmed that it catches all four vulnerable calls and none of the
115
+ fixed code. The sweep found no other instances in setuptools. The rule doesn't mention
116
+ setuptools, so it works on any Python codebase.
117
+
118
+ ## Commands
119
+
120
+ | Command | What it does |
121
+ |---|---|
122
+ | `antibody init [--agent claude] [--skill]` | Create `.antibodies/`, plus the skill and edit hook |
123
+ | `antibody learn [commit] [--id ID] [--fixtures-only]` | Write a skeleton and print a brief for the agent |
124
+ | `antibody verify <id>... \| --all` | Prove each antibody; `--fix COMMIT` records the fix, `--no-stamp` leaves files alone |
125
+ | `antibody sweep <id> [paths]` | List every match of one antibody |
126
+ | `antibody baseline [ids]` | Accept today's matches |
127
+ | `antibody check [paths] [--since REF] [--no-baseline] [--sarif FILE]` | New matches from verified antibodies; exit 1 if any |
128
+ | `antibody list` | Antibodies, how each was proven, and their kind |
129
+ | `antibody hook claude` | The edit hook (reads Claude Code's event on stdin) |
130
+
131
+ All but `hook` take `--repo PATH`. `verify`, `sweep`, `check` and `list` take `--json`.
132
+
133
+ ## The file format
134
+
135
+ An antibody is a plain Semgrep rule with its history under `metadata.antibody`, so
136
+ `semgrep scan --config .antibodies --exclude .antibodies` works without antibody.
137
+
138
+ ```yaml
139
+ rules:
140
+ - id: naive-datetime
141
+ message: >-
142
+ This makes a timestamp with no timezone ... Use datetime.now(timezone.utc).
143
+ severity: ERROR
144
+ languages: [python]
145
+ pattern-either:
146
+ - pattern: datetime.datetime.utcnow()
147
+ - pattern: datetime.datetime.now()
148
+ metadata:
149
+ antibody:
150
+ kind: bug # bug, security or convention
151
+ source:
152
+ commit: "95ffe5603abe..."
153
+ summary: "Use timezone-aware timestamps in billing"
154
+ verified: # written by `antibody verify`
155
+ engine: semgrep 1.181.0
156
+ proof: commit
157
+ vulnerable: 8d7f55689db5
158
+ fixed: 95ffe5603abe
159
+ fires_on:
160
+ - app/billing.py:5
161
+ at: '2026-10-18'
162
+ ```
163
+
164
+ A file next to it with the same name and a code extension (`naive-datetime.py`) is its
165
+ fixture, in Semgrep's annotation style:
166
+
167
+ ```python
168
+ # ruleid: naive-datetime
169
+ issued = datetime.utcnow()
170
+ # ok: naive-datetime
171
+ issued = datetime.now(timezone.utc)
172
+ ```
173
+
174
+ ## Engine
175
+
176
+ Antibody works with Semgrep CE and [Opengrep](https://github.com/opengrep/opengrep). The
177
+ full test suite passes on Semgrep 1.181 and Opengrep 1.31. It uses the engine installed
178
+ with it (the `[semgrep]` extra) first, then Opengrep, then Semgrep from `PATH`. Pass
179
+ `--engine` or set `$ANTIBODY_ENGINE` (a name or a path) to choose. Both run through their
180
+ fast OCaml CLI, and Semgrep always runs with `--metrics=off`. Python and
181
+ JavaScript/TypeScript are the first targets, and any language the engine supports works.
182
+
183
+ ## Status
184
+
185
+ v0.2. Next:
186
+
187
+ - a Claude Code plugin bundling the skill and hook (after a PyPI release)
188
+ - blocking a bad edit *before* it's written (PreToolUse), and hooks for Cursor, Codex and opencode
189
+ - `learn --auto` with the Claude API, for pull-request bots
190
+ - snapshotting agent edits, so corrections are proven against real code rather than fixtures
191
+
192
+ ## Development
193
+
194
+ ```bash
195
+ uv sync
196
+ uv run pytest # runs against real git repos and the real engine (~30 s)
197
+ ```
198
+
199
+ ## License
200
+
201
+ [MIT](LICENSE)
@@ -0,0 +1,15 @@
1
+ import os
2
+ import subprocess
3
+
4
+
5
+ def clone(url, filename, rev):
6
+ # ruleid: os-system-formatted-command
7
+ os.system("git clone --quiet %s %s" % (url, filename))
8
+ # ruleid: os-system-formatted-command
9
+ os.system(f"hg --cwd {filename} up -C -r {rev} -q")
10
+ # ruleid: os-system-formatted-command
11
+ subprocess.call("svn checkout {} {}".format(url, filename), shell=True)
12
+ # ok: os-system-formatted-command
13
+ subprocess.check_call(["git", "clone", "--quiet", url, filename])
14
+ # ok: os-system-formatted-command
15
+ os.system("git --version")
@@ -0,0 +1,40 @@
1
+ rules:
2
+ - id: os-system-formatted-command
3
+ message: >-
4
+ A shell command is built by formatting values into a string and run through a
5
+ shell. A URL, filename or revision containing shell metacharacters runs arbitrary
6
+ commands. Pass an argument list to subprocess instead.
7
+ severity: ERROR
8
+ languages: [python]
9
+ patterns:
10
+ - pattern-either:
11
+ - pattern: os.system($CMD)
12
+ - pattern: os.popen($CMD, ...)
13
+ - pattern: subprocess.$FN($CMD, ..., shell=True, ...)
14
+ - metavariable-pattern:
15
+ metavariable: $CMD
16
+ pattern-either:
17
+ - pattern: '"..." % $ARGS'
18
+ - pattern: f"..."
19
+ - pattern: '"...".format(...)'
20
+ - pattern: '"..." + $X'
21
+ - pattern: '$X + "..."'
22
+ metadata:
23
+ antibody:
24
+ source:
25
+ commit: 88807c7062788254f654ea8c03427adc859321f0
26
+ summary: "Modernize package_index VCS handling"
27
+ repo: "https://github.com/pypa/setuptools"
28
+ advisory: GHSA-cx63-2mw6-8hw5
29
+ cwe: CWE-78
30
+ verified:
31
+ engine: semgrep 1.181.0
32
+ proof: commit
33
+ vulnerable: 4a0a9ce58751
34
+ fixed: 88807c706278
35
+ fires_on:
36
+ - setuptools/package_index.py:888
37
+ - setuptools/package_index.py:892
38
+ - setuptools/package_index.py:907
39
+ - setuptools/package_index.py:911
40
+ at: '2026-10-18'
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "antibody"
3
+ version = "0.2.0"
4
+ description = "Turn every fix into a verified rule that stops the mistake coming back, enforced on agent edits and in CI."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.10"
9
+ dependencies = [
10
+ "ruamel.yaml>=0.18",
11
+ ]
12
+
13
+ [project.urls]
14
+ Repository = "https://github.com/cipheraxat/antibody"
15
+
16
+ [project.optional-dependencies]
17
+ # The rule engine. Skip this extra if you already have opengrep or semgrep on PATH.
18
+ semgrep = ["semgrep>=1.100"]
19
+
20
+ [project.scripts]
21
+ antibody = "antibody.cli:main"
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "pytest>=8",
26
+ "semgrep>=1.100",
27
+ ]
28
+
29
+ [build-system]
30
+ requires = ["hatchling"]
31
+ build-backend = "hatchling.build"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/antibody"]
35
+
36
+ [tool.pytest.ini_options]
37
+ testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ """Antibody: every security fix becomes a verified, deterministic rule."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())