codejury 0.21.0__tar.gz → 0.23.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.
- {codejury-0.21.0 → codejury-0.23.0}/PKG-INFO +61 -74
- {codejury-0.21.0 → codejury-0.23.0}/README.md +60 -73
- codejury-0.23.0/codejury/__init__.py +15 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/cli.py +12 -5
- codejury-0.23.0/codejury/data/commands/codejury-review-repo.md +42 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/methodologies/repo-review.md +48 -4
- {codejury-0.21.0 → codejury-0.23.0}/codejury/domain/__init__.py +1 -1
- {codejury-0.21.0 → codejury-0.23.0}/codejury/domain/finding.py +1 -1
- {codejury-0.21.0 → codejury-0.23.0}/codejury/json_parse.py +10 -10
- {codejury-0.21.0 → codejury-0.23.0}/codejury/mddoc.py +4 -4
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/openai_format.py +1 -1
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/retry.py +1 -1
- {codejury-0.21.0 → codejury-0.23.0}/codejury/report.py +1 -1
- {codejury-0.21.0 → codejury-0.23.0}/codejury/resources.py +4 -4
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/diff/debate.py +4 -4
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/diff/debate_prompts.py +3 -3
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/diff/engine.py +3 -3
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/diff/findings_filter.py +1 -1
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/diff/runner.py +2 -2
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/diff/vulnerabilities.py +5 -5
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/repo/model.py +5 -5
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/repo/scaffold.py +48 -5
- {codejury-0.21.0 → codejury-0.23.0}/codejury.egg-info/PKG-INFO +61 -74
- {codejury-0.21.0 → codejury-0.23.0}/codejury.egg-info/SOURCES.txt +1 -1
- {codejury-0.21.0 → codejury-0.23.0}/pyproject.toml +2 -2
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_cli_audit.py +3 -1
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_repo_scaffold.py +30 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_vulnerabilities.py +2 -2
- codejury-0.21.0/codejury/__init__.py +0 -15
- codejury-0.21.0/codejury/data/commands/codejury-review-repo.md +0 -29
- {codejury-0.21.0 → codejury-0.23.0}/LICENSE +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/__main__.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/detection.yaml +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/frameworks/go/echo.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/frameworks/go/gin.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/frameworks/javascript/express.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/frameworks/javascript/nestjs.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/frameworks/python/celery.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/frameworks/python/django.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/frameworks/python/fastapi.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/frameworks/python/flask.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/languages/go.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/languages/javascript.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/languages/python.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/languages/typescript.md +0 -0
- /codejury-0.21.0/codejury/data/methodologies/security-review-memory.md → /codejury-0.23.0/codejury/data/methodologies/memory-template.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/protocols/oauth.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/SKILL.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/business-logic.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/code-injection.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/command-injection.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/cross-site-request-forgery.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/cross-site-scripting.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/hardcoded-secrets.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/http-response-splitting.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/improper-authentication.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/information-exposure.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/insecure-cryptography.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/insecure-deserialization.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/insecure-direct-object-reference.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/insecure-transport.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/jwt-validation.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/mass-assignment.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/missing-authorization.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/open-redirect.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/path-traversal.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/race-condition.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/replay-attack.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/server-side-request-forgery.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/server-side-template-injection.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/session-fixation.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/sql-injection.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/data/vulnerabilities/xml-external-entity.md +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/detection.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/guides.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/__init__.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/anthropic.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/base.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/factory.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/litellm.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/mock.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/providers/openai.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/__init__.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/diff/__init__.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/diff/prompts.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury/review/repo/__init__.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/setup.cfg +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_detection.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_diff_debate.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_diff_engine.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_guides.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_json_parse.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_litellm_provider.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_mddoc.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_openai_format.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_openai_provider.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_repo_model.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_report.py +0 -0
- {codejury-0.21.0 → codejury-0.23.0}/tests/test_retry_provider.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: AI code security review for diffs and
|
|
3
|
+
Version: 0.23.0
|
|
4
|
+
Summary: AI code security review for diffs and repos.
|
|
5
5
|
Author: AISecLabs
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/aiseclabs/codejury
|
|
@@ -38,25 +38,15 @@ Dynamic: license-file
|
|
|
38
38
|
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
An AI code security review tool. It works two ways. `review diff` audits a pull
|
|
42
|
+
request diff for newly introduced exploitable risk in one command. `review repo`
|
|
43
|
+
sets up an interactive agent such as Claude Code or Codex to audit a whole
|
|
44
|
+
repository, mapping the attack surface, tracing inputs to sinks across files, and
|
|
45
|
+
verifying each issue with a real PoC.
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
exploitable risk, as a single balanced LLM call or an adversarial Finder,
|
|
47
|
-
Challenger, and Judge pass that trades roughly 3x the cost for extra recall on
|
|
48
|
-
subtle flaws that span files. One command in, findings out.
|
|
49
|
-
- **Repo Review** is agent driven. A whole repository is too large for one LLM
|
|
50
|
-
call, so codejury scaffolds a workspace and hands an interactive agent such as
|
|
51
|
-
Claude Code or Codex a methodology to run. The agent maps the attack surface,
|
|
52
|
-
traces inputs to sinks across files, verifies issues with a real PoC, and
|
|
53
|
-
iterates over rounds with a persistent memory.
|
|
54
|
-
|
|
55
|
-
Security knowledge is data, not code. Vulnerability classes live in
|
|
56
|
-
`codejury/data/vulnerabilities/*.md` with a vulnerable and a secure example per
|
|
57
|
-
language, and the stack guides live under `data/languages`, `data/frameworks`,
|
|
58
|
-
and `data/protocols`. The engine stays language neutral and names no framework,
|
|
59
|
-
so adding a stack is a drop-in markdown file.
|
|
47
|
+
Security knowledge is data, not code. Drop-in markdown guides hold the
|
|
48
|
+
vulnerability classes, languages, frameworks, and protocols, so the engine names
|
|
49
|
+
no language and adding a stack is a new file.
|
|
60
50
|
|
|
61
51
|
## Install
|
|
62
52
|
|
|
@@ -67,19 +57,21 @@ codejury install-slash-command # Claude Code, ~/.claude/commands/
|
|
|
67
57
|
codejury install-slash-command --agent codex # Codex, ~/.codex/prompts/
|
|
68
58
|
```
|
|
69
59
|
|
|
70
|
-
`install-slash-command` copies the `/codejury-review-repo` command into the
|
|
71
|
-
command directory. The command body is the same for every agent, only the
|
|
72
|
-
directory differs, so pass `--dir` for any other agent.
|
|
73
|
-
agent neutral, so even without the command you can run `codejury review repo` and
|
|
74
|
-
tell any agent to follow the methodology it writes.
|
|
60
|
+
`install-slash-command` copies the `/codejury-review-repo` command into the
|
|
61
|
+
agent's command directory. The command body is the same for every agent, only the
|
|
62
|
+
directory differs, so pass `--dir` for any other agent.
|
|
75
63
|
|
|
76
64
|
## Diff Review
|
|
77
65
|
|
|
66
|
+
The coded engine. It audits a diff in one command, as a single balanced LLM call
|
|
67
|
+
or an adversarial Finder, Challenger, and Judge pass that trades roughly 3x the
|
|
68
|
+
cost for extra recall on subtle flaws that span files.
|
|
69
|
+
|
|
78
70
|
```bash
|
|
79
|
-
#
|
|
71
|
+
# a diff file
|
|
80
72
|
codejury review diff --file changes.diff
|
|
81
73
|
|
|
82
|
-
#
|
|
74
|
+
# a git range in a repo
|
|
83
75
|
codejury review diff --repo /path/to/app --git-range origin/main...HEAD
|
|
84
76
|
|
|
85
77
|
# from stdin
|
|
@@ -97,57 +89,25 @@ the `CODEJURY_API_KEY`, `CODEJURY_MODEL`, and `CODEJURY_API_BASE` environment
|
|
|
97
89
|
variables. `codejury review diff --dry-run` exercises the engine with a mock
|
|
98
90
|
provider and no key, and falls back to a built in demo diff when you pass none.
|
|
99
91
|
|
|
100
|
-
### Choosing a Model and Mode
|
|
101
|
-
|
|
102
|
-
Detection quality is dominated by the model first, then the mode. On real diff
|
|
103
|
-
probes:
|
|
104
|
-
|
|
105
|
-
- A strong model at the Claude Sonnet tier in standard mode caught every planted
|
|
106
|
-
vulnerability with almost no false positives. A weaker model raised false
|
|
107
|
-
positives in both modes, so the model is the lever that matters most.
|
|
108
|
-
- Adversarial mode did not lower false positives over standard on those probes
|
|
109
|
-
and costs about 3x. Reach for it to gain recall on subtle logic that spans
|
|
110
|
-
files, not as a way to cut false positives.
|
|
111
|
-
|
|
112
|
-
Default to standard mode with a strong model, set with `--model` or
|
|
113
|
-
`CODEJURY_MODEL`. False positives are held down by the do not report list and the
|
|
114
|
-
post filter, not by the mode.
|
|
115
|
-
|
|
116
|
-
### Use in CI with GitHub Actions
|
|
117
|
-
|
|
118
|
-
Audit every pull request and surface findings in the code scanning tab. Copy
|
|
119
|
-
`examples/codejury-pr-review.yml` into `.github/workflows/`, add a
|
|
120
|
-
`CODEJURY_API_KEY` repository secret, and it will
|
|
121
|
-
|
|
122
|
-
1. diff the pull request against its base with `--git-range origin/<base>...HEAD`,
|
|
123
|
-
2. write SARIF and upload it with `github/codeql-action/upload-sarif`,
|
|
124
|
-
3. fail the check on a HIGH or CRITICAL finding with `--fail-on high`.
|
|
125
|
-
|
|
126
|
-
The job makes one model call per pull request in standard mode. The SARIF is
|
|
127
|
-
uploaded even when the gate fails, so findings always show up on the pull
|
|
128
|
-
request.
|
|
129
|
-
|
|
130
92
|
## Repo Review
|
|
131
93
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
and PoC work that an agent does, not a single call.
|
|
94
|
+
The agent path. A whole repo is too large for one LLM call, so this sets up a
|
|
95
|
+
review for an interactive agent rather than running a pipeline.
|
|
135
96
|
|
|
136
97
|
```bash
|
|
137
98
|
codejury review repo /path/to/your/repo
|
|
138
99
|
```
|
|
139
100
|
|
|
140
|
-
|
|
101
|
+
It detects the stack, seeds the entrypoint inventory and the downstream trace
|
|
141
102
|
targets from a deterministic scan, writes the methodology to
|
|
142
|
-
`<workspace>/METHODOLOGY.md`, and prints a short pointer.
|
|
143
|
-
workspace:
|
|
103
|
+
`<workspace>/METHODOLOGY.md`, and prints a short pointer. The workspace:
|
|
144
104
|
|
|
145
105
|
```
|
|
146
106
|
entrypoints/ the candidate entrypoint files to start from
|
|
147
107
|
issues/ one write-up per confirmed or suspected issue
|
|
148
108
|
pocs/ a runnable PoC per issue, same name as the issue
|
|
149
109
|
analysis/ _trace_targets.md, the round ledger, and trace notes
|
|
150
|
-
|
|
110
|
+
METHODOLOGY.md and MEMORY.md
|
|
151
111
|
```
|
|
152
112
|
|
|
153
113
|
Then run it with an interactive agent. In Claude Code or Codex:
|
|
@@ -157,22 +117,49 @@ Then run it with an interactive agent. In Claude Code or Codex:
|
|
|
157
117
|
```
|
|
158
118
|
|
|
159
119
|
Any agent works, the slash command is just a shortcut. Without it, tell the agent
|
|
160
|
-
to follow the `METHODOLOGY.md` the scaffold wrote.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
a real PoC against a sandbox or dev environment and asks you for any credential or
|
|
168
|
-
test data it needs. Only a reproduced PoC is a confirmed finding, and nothing
|
|
169
|
-
runs against production.
|
|
120
|
+
to follow the `METHODOLOGY.md` the scaffold wrote. The agent maps the attack
|
|
121
|
+
surface including non HTTP sources, traces each input to its sink through the
|
|
122
|
+
downstream layers, runs an Authorization Model pass for missing-auth and IDOR,
|
|
123
|
+
and follows a control into a library when an entrypoint delegates it. It iterates
|
|
124
|
+
until a Completeness Gate passes, confirms each issue with a real PoC against a
|
|
125
|
+
sandbox or dev environment, and asks you for any credential it needs. Only a
|
|
126
|
+
reproduced PoC is a confirmed finding, and nothing runs against production.
|
|
170
127
|
|
|
171
128
|
The supported stacks today are Python with Django, Celery, Flask, and FastAPI,
|
|
172
129
|
Go with Gin and Echo, JavaScript and TypeScript with Express and NestJS, and the
|
|
173
130
|
OAuth and OIDC protocol. The methodology still works on an unguided stack, it just
|
|
174
131
|
leans more on the agent's own knowledge.
|
|
175
132
|
|
|
133
|
+
## Choosing a Model and Mode
|
|
134
|
+
|
|
135
|
+
Detection quality is dominated by the model first, then the mode. On real diff
|
|
136
|
+
probes:
|
|
137
|
+
|
|
138
|
+
- A strong model at the Claude Sonnet tier in standard mode caught every planted
|
|
139
|
+
vulnerability with almost no false positives. A weaker model raised false
|
|
140
|
+
positives in both modes, so the model is the lever that matters most.
|
|
141
|
+
- Adversarial mode did not lower false positives over standard on those probes
|
|
142
|
+
and costs about 3x. Reach for it to gain recall on subtle logic that spans
|
|
143
|
+
files, not as a way to cut false positives.
|
|
144
|
+
|
|
145
|
+
Default to standard mode with a strong model, set with `--model` or
|
|
146
|
+
`CODEJURY_MODEL`. False positives are held down by the do not report list and the
|
|
147
|
+
post filter, not by the mode.
|
|
148
|
+
|
|
149
|
+
## Use in CI with GitHub Actions
|
|
150
|
+
|
|
151
|
+
Audit every pull request and surface findings in the code scanning tab. Copy
|
|
152
|
+
`examples/codejury-pr-review.yml` into `.github/workflows/`, add a
|
|
153
|
+
`CODEJURY_API_KEY` repository secret, and it will
|
|
154
|
+
|
|
155
|
+
1. diff the pull request against its base with `--git-range origin/<base>...HEAD`,
|
|
156
|
+
2. write SARIF and upload it with `github/codeql-action/upload-sarif`,
|
|
157
|
+
3. fail the check on a HIGH or CRITICAL finding with `--fail-on high`.
|
|
158
|
+
|
|
159
|
+
The job makes one model call per pull request in standard mode. The SARIF is
|
|
160
|
+
uploaded even when the gate fails, so findings always show up on the pull
|
|
161
|
+
request.
|
|
162
|
+
|
|
176
163
|
## Findings
|
|
177
164
|
|
|
178
165
|
Each finding carries a file and line, a severity and category, a concrete exploit
|
|
@@ -7,25 +7,15 @@
|
|
|
7
7
|
╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
call, so codejury scaffolds a workspace and hands an interactive agent such as
|
|
20
|
-
Claude Code or Codex a methodology to run. The agent maps the attack surface,
|
|
21
|
-
traces inputs to sinks across files, verifies issues with a real PoC, and
|
|
22
|
-
iterates over rounds with a persistent memory.
|
|
23
|
-
|
|
24
|
-
Security knowledge is data, not code. Vulnerability classes live in
|
|
25
|
-
`codejury/data/vulnerabilities/*.md` with a vulnerable and a secure example per
|
|
26
|
-
language, and the stack guides live under `data/languages`, `data/frameworks`,
|
|
27
|
-
and `data/protocols`. The engine stays language neutral and names no framework,
|
|
28
|
-
so adding a stack is a drop-in markdown file.
|
|
10
|
+
An AI code security review tool. It works two ways. `review diff` audits a pull
|
|
11
|
+
request diff for newly introduced exploitable risk in one command. `review repo`
|
|
12
|
+
sets up an interactive agent such as Claude Code or Codex to audit a whole
|
|
13
|
+
repository, mapping the attack surface, tracing inputs to sinks across files, and
|
|
14
|
+
verifying each issue with a real PoC.
|
|
15
|
+
|
|
16
|
+
Security knowledge is data, not code. Drop-in markdown guides hold the
|
|
17
|
+
vulnerability classes, languages, frameworks, and protocols, so the engine names
|
|
18
|
+
no language and adding a stack is a new file.
|
|
29
19
|
|
|
30
20
|
## Install
|
|
31
21
|
|
|
@@ -36,19 +26,21 @@ codejury install-slash-command # Claude Code, ~/.claude/commands/
|
|
|
36
26
|
codejury install-slash-command --agent codex # Codex, ~/.codex/prompts/
|
|
37
27
|
```
|
|
38
28
|
|
|
39
|
-
`install-slash-command` copies the `/codejury-review-repo` command into the
|
|
40
|
-
command directory. The command body is the same for every agent, only the
|
|
41
|
-
directory differs, so pass `--dir` for any other agent.
|
|
42
|
-
agent neutral, so even without the command you can run `codejury review repo` and
|
|
43
|
-
tell any agent to follow the methodology it writes.
|
|
29
|
+
`install-slash-command` copies the `/codejury-review-repo` command into the
|
|
30
|
+
agent's command directory. The command body is the same for every agent, only the
|
|
31
|
+
directory differs, so pass `--dir` for any other agent.
|
|
44
32
|
|
|
45
33
|
## Diff Review
|
|
46
34
|
|
|
35
|
+
The coded engine. It audits a diff in one command, as a single balanced LLM call
|
|
36
|
+
or an adversarial Finder, Challenger, and Judge pass that trades roughly 3x the
|
|
37
|
+
cost for extra recall on subtle flaws that span files.
|
|
38
|
+
|
|
47
39
|
```bash
|
|
48
|
-
#
|
|
40
|
+
# a diff file
|
|
49
41
|
codejury review diff --file changes.diff
|
|
50
42
|
|
|
51
|
-
#
|
|
43
|
+
# a git range in a repo
|
|
52
44
|
codejury review diff --repo /path/to/app --git-range origin/main...HEAD
|
|
53
45
|
|
|
54
46
|
# from stdin
|
|
@@ -66,57 +58,25 @@ the `CODEJURY_API_KEY`, `CODEJURY_MODEL`, and `CODEJURY_API_BASE` environment
|
|
|
66
58
|
variables. `codejury review diff --dry-run` exercises the engine with a mock
|
|
67
59
|
provider and no key, and falls back to a built in demo diff when you pass none.
|
|
68
60
|
|
|
69
|
-
### Choosing a Model and Mode
|
|
70
|
-
|
|
71
|
-
Detection quality is dominated by the model first, then the mode. On real diff
|
|
72
|
-
probes:
|
|
73
|
-
|
|
74
|
-
- A strong model at the Claude Sonnet tier in standard mode caught every planted
|
|
75
|
-
vulnerability with almost no false positives. A weaker model raised false
|
|
76
|
-
positives in both modes, so the model is the lever that matters most.
|
|
77
|
-
- Adversarial mode did not lower false positives over standard on those probes
|
|
78
|
-
and costs about 3x. Reach for it to gain recall on subtle logic that spans
|
|
79
|
-
files, not as a way to cut false positives.
|
|
80
|
-
|
|
81
|
-
Default to standard mode with a strong model, set with `--model` or
|
|
82
|
-
`CODEJURY_MODEL`. False positives are held down by the do not report list and the
|
|
83
|
-
post filter, not by the mode.
|
|
84
|
-
|
|
85
|
-
### Use in CI with GitHub Actions
|
|
86
|
-
|
|
87
|
-
Audit every pull request and surface findings in the code scanning tab. Copy
|
|
88
|
-
`examples/codejury-pr-review.yml` into `.github/workflows/`, add a
|
|
89
|
-
`CODEJURY_API_KEY` repository secret, and it will
|
|
90
|
-
|
|
91
|
-
1. diff the pull request against its base with `--git-range origin/<base>...HEAD`,
|
|
92
|
-
2. write SARIF and upload it with `github/codeql-action/upload-sarif`,
|
|
93
|
-
3. fail the check on a HIGH or CRITICAL finding with `--fail-on high`.
|
|
94
|
-
|
|
95
|
-
The job makes one model call per pull request in standard mode. The SARIF is
|
|
96
|
-
uploaded even when the gate fails, so findings always show up on the pull
|
|
97
|
-
request.
|
|
98
|
-
|
|
99
61
|
## Repo Review
|
|
100
62
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
and PoC work that an agent does, not a single call.
|
|
63
|
+
The agent path. A whole repo is too large for one LLM call, so this sets up a
|
|
64
|
+
review for an interactive agent rather than running a pipeline.
|
|
104
65
|
|
|
105
66
|
```bash
|
|
106
67
|
codejury review repo /path/to/your/repo
|
|
107
68
|
```
|
|
108
69
|
|
|
109
|
-
|
|
70
|
+
It detects the stack, seeds the entrypoint inventory and the downstream trace
|
|
110
71
|
targets from a deterministic scan, writes the methodology to
|
|
111
|
-
`<workspace>/METHODOLOGY.md`, and prints a short pointer.
|
|
112
|
-
workspace:
|
|
72
|
+
`<workspace>/METHODOLOGY.md`, and prints a short pointer. The workspace:
|
|
113
73
|
|
|
114
74
|
```
|
|
115
75
|
entrypoints/ the candidate entrypoint files to start from
|
|
116
76
|
issues/ one write-up per confirmed or suspected issue
|
|
117
77
|
pocs/ a runnable PoC per issue, same name as the issue
|
|
118
78
|
analysis/ _trace_targets.md, the round ledger, and trace notes
|
|
119
|
-
|
|
79
|
+
METHODOLOGY.md and MEMORY.md
|
|
120
80
|
```
|
|
121
81
|
|
|
122
82
|
Then run it with an interactive agent. In Claude Code or Codex:
|
|
@@ -126,22 +86,49 @@ Then run it with an interactive agent. In Claude Code or Codex:
|
|
|
126
86
|
```
|
|
127
87
|
|
|
128
88
|
Any agent works, the slash command is just a shortcut. Without it, tell the agent
|
|
129
|
-
to follow the `METHODOLOGY.md` the scaffold wrote.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
a real PoC against a sandbox or dev environment and asks you for any credential or
|
|
137
|
-
test data it needs. Only a reproduced PoC is a confirmed finding, and nothing
|
|
138
|
-
runs against production.
|
|
89
|
+
to follow the `METHODOLOGY.md` the scaffold wrote. The agent maps the attack
|
|
90
|
+
surface including non HTTP sources, traces each input to its sink through the
|
|
91
|
+
downstream layers, runs an Authorization Model pass for missing-auth and IDOR,
|
|
92
|
+
and follows a control into a library when an entrypoint delegates it. It iterates
|
|
93
|
+
until a Completeness Gate passes, confirms each issue with a real PoC against a
|
|
94
|
+
sandbox or dev environment, and asks you for any credential it needs. Only a
|
|
95
|
+
reproduced PoC is a confirmed finding, and nothing runs against production.
|
|
139
96
|
|
|
140
97
|
The supported stacks today are Python with Django, Celery, Flask, and FastAPI,
|
|
141
98
|
Go with Gin and Echo, JavaScript and TypeScript with Express and NestJS, and the
|
|
142
99
|
OAuth and OIDC protocol. The methodology still works on an unguided stack, it just
|
|
143
100
|
leans more on the agent's own knowledge.
|
|
144
101
|
|
|
102
|
+
## Choosing a Model and Mode
|
|
103
|
+
|
|
104
|
+
Detection quality is dominated by the model first, then the mode. On real diff
|
|
105
|
+
probes:
|
|
106
|
+
|
|
107
|
+
- A strong model at the Claude Sonnet tier in standard mode caught every planted
|
|
108
|
+
vulnerability with almost no false positives. A weaker model raised false
|
|
109
|
+
positives in both modes, so the model is the lever that matters most.
|
|
110
|
+
- Adversarial mode did not lower false positives over standard on those probes
|
|
111
|
+
and costs about 3x. Reach for it to gain recall on subtle logic that spans
|
|
112
|
+
files, not as a way to cut false positives.
|
|
113
|
+
|
|
114
|
+
Default to standard mode with a strong model, set with `--model` or
|
|
115
|
+
`CODEJURY_MODEL`. False positives are held down by the do not report list and the
|
|
116
|
+
post filter, not by the mode.
|
|
117
|
+
|
|
118
|
+
## Use in CI with GitHub Actions
|
|
119
|
+
|
|
120
|
+
Audit every pull request and surface findings in the code scanning tab. Copy
|
|
121
|
+
`examples/codejury-pr-review.yml` into `.github/workflows/`, add a
|
|
122
|
+
`CODEJURY_API_KEY` repository secret, and it will
|
|
123
|
+
|
|
124
|
+
1. diff the pull request against its base with `--git-range origin/<base>...HEAD`,
|
|
125
|
+
2. write SARIF and upload it with `github/codeql-action/upload-sarif`,
|
|
126
|
+
3. fail the check on a HIGH or CRITICAL finding with `--fail-on high`.
|
|
127
|
+
|
|
128
|
+
The job makes one model call per pull request in standard mode. The SARIF is
|
|
129
|
+
uploaded even when the gate fails, so findings always show up on the pull
|
|
130
|
+
request.
|
|
131
|
+
|
|
145
132
|
## Findings
|
|
146
133
|
|
|
147
134
|
Each finding carries a file and line, a severity and category, a concrete exploit
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""AI code security review tool.
|
|
2
|
+
|
|
3
|
+
Two paths matched to their nature: a coded diff-audit engine, a standard single
|
|
4
|
+
call or an adversarial Finder/Challenger/Judge pass, and a whole-repo review run
|
|
5
|
+
as a methodology by an interactive agent. Security knowledge lives in rich
|
|
6
|
+
markdown vulnerability classes under data/vulnerabilities, injected into the
|
|
7
|
+
audit prompt, not in a rendered schema.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
__version__ = version("codejury")
|
|
14
|
+
except PackageNotFoundError: # running from a source tree without an install
|
|
15
|
+
__version__ = "0.0.0"
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
Two paths matched to their nature:
|
|
4
4
|
|
|
5
5
|
- ``review diff`` runs the coded diff engine over a unified diff: a single
|
|
6
|
-
balanced call
|
|
6
|
+
balanced call in standard mode or the adversarial Finder/Challenger/Judge pass.
|
|
7
7
|
- ``review repo <dir>`` scaffolds a workspace and prints the methodology for an
|
|
8
|
-
interactive agent to run a whole-repo review
|
|
9
|
-
which a single call cannot do for a whole codebase
|
|
8
|
+
interactive agent to run a whole-repo review. It does not run an LLM pipeline,
|
|
9
|
+
which a single call cannot do for a whole codebase.
|
|
10
10
|
|
|
11
11
|
``review diff --dry-run`` exercises the engine with a mock provider and no key.
|
|
12
12
|
The audit orchestration itself lives in ``codejury.review.diff.runner``.
|
|
@@ -98,7 +98,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
98
98
|
_add_audit_args(rsub.add_parser("diff", help="audit a unified diff (the coded engine)"))
|
|
99
99
|
repo = rsub.add_parser("repo", help="scaffold a whole-repo review for an interactive agent")
|
|
100
100
|
repo.add_argument("directory", help="target repository to review")
|
|
101
|
-
repo.add_argument("--workspace", default="codejury-review", help="where to create the review workspace")
|
|
101
|
+
repo.add_argument("--workspace", default="/var/tmp/codejury-review", help="where to create the review workspace")
|
|
102
|
+
repo.add_argument("--fresh", action="store_true",
|
|
103
|
+
help="clear a previous review's output in the workspace first, MEMORY.md included")
|
|
102
104
|
|
|
103
105
|
inst = sub.add_parser("install-slash-command",
|
|
104
106
|
help="install the /codejury-review-repo slash command for an agent")
|
|
@@ -137,8 +139,13 @@ def _dispatch(args, parser) -> int:
|
|
|
137
139
|
return 1 if gate(kept, args.fail_on) else 0
|
|
138
140
|
|
|
139
141
|
if args.command == "review" and scope == "repo":
|
|
140
|
-
res = scaffold(args.directory, args.workspace)
|
|
142
|
+
res = scaffold(args.directory, args.workspace, fresh=args.fresh)
|
|
141
143
|
(Path(res.workspace) / "METHODOLOGY.md").write_text(res.methodology, encoding="utf-8")
|
|
144
|
+
if res.cleared:
|
|
145
|
+
print(f"Cleared {len(res.cleared)} prior-run paths in {res.workspace}, MEMORY.md included", file=sys.stderr)
|
|
146
|
+
elif res.had_prior_run:
|
|
147
|
+
print(f"A previous review's output is in {res.workspace}. Re-run with --fresh to clear it "
|
|
148
|
+
"first, MEMORY.md included.", file=sys.stderr)
|
|
142
149
|
print(f"Workspace ready: {res.workspace}", file=sys.stderr)
|
|
143
150
|
if res.guides:
|
|
144
151
|
print(f"Detected stack: {', '.join(res.guides)}, notes in {res.workspace}/_stack.md", file=sys.stderr)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run a codejury whole-repo security review on a repository, interactively
|
|
3
|
+
---
|
|
4
|
+
Run a codejury whole-repo security review of: $ARGUMENTS
|
|
5
|
+
|
|
6
|
+
1. Scaffold the workspace:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
codejury review repo $ARGUMENTS --workspace /var/tmp/codejury-review
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
If `codejury` is not on PATH it is a pip-installed console script, so activate
|
|
13
|
+
the project venv first, for example `. .venv/bin/activate`, or run it through
|
|
14
|
+
that venv's Python, for example `python -m codejury`.
|
|
15
|
+
|
|
16
|
+
If the output reports that a previous review's output is already in the
|
|
17
|
+
workspace, ask me whether to clear it and start fresh. If I say yes, re-run the
|
|
18
|
+
same command with `--fresh`, which clears the prior issues, PoCs, round ledger,
|
|
19
|
+
and MEMORY.md for a clean slate. If I say no, continue and build on what is
|
|
20
|
+
there.
|
|
21
|
+
|
|
22
|
+
2. Read `<workspace>/METHODOLOGY.md` and follow it to completion. It is the single
|
|
23
|
+
source of truth for how to run the review, the entrypoint map, the trace
|
|
24
|
+
targets, the Authorization Model pass, the dependency-control checks, the round
|
|
25
|
+
ledger, and the Completeness Gate. Do not improvise a different process.
|
|
26
|
+
|
|
27
|
+
Run every round to the Completeness Gate on your own. Do not pause between
|
|
28
|
+
rounds to ask whether to continue, do not stop early because a round felt
|
|
29
|
+
productive, and do not report the review done until the gate passes. The only
|
|
30
|
+
reasons to stop and ask me are in step 3.
|
|
31
|
+
|
|
32
|
+
3. Verify each issue with a real PoC, human in the loop. Stop and ask me only for
|
|
33
|
+
what the PoC genuinely needs: a credential, a test account, an MFA step, or
|
|
34
|
+
go-ahead before a destructive action. Never touch production, never use real
|
|
35
|
+
credentials, and never run a destructive action without my go-ahead. Only a
|
|
36
|
+
reproduced PoC is a confirmed finding, so keep reviewing while a PoC is blocked
|
|
37
|
+
on me rather than waiting.
|
|
38
|
+
|
|
39
|
+
4. Report confirmed findings, the ones with a reproduced PoC, separately from
|
|
40
|
+
suspected ones still blocked on verification, as a table: title, class,
|
|
41
|
+
`file:line`, exploit, verification status. The issue files live in the
|
|
42
|
+
workspace `issues/`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Repo Security Review: Agent Methodology
|
|
2
2
|
|
|
3
|
-
The `review repo` path: a whole-
|
|
3
|
+
The `review repo` path: a whole-repo security audit, run by an interactive
|
|
4
4
|
coding agent such as Claude Code or Codex, not a one-shot LLM call. It maps the
|
|
5
5
|
attack surface, traces inputs to sinks across files, verifies issues with a real
|
|
6
6
|
PoC, and iterates over multiple rounds with a persistent memory. One round is
|
|
@@ -8,13 +8,13 @@ roughly 30 minutes. Run as many rounds as needed.
|
|
|
8
8
|
|
|
9
9
|
Target repository: the directory you were given.
|
|
10
10
|
Workspace: `<workspace>/<project>/`, created for you, holding `entrypoints/`,
|
|
11
|
-
`issues/`, `analysis/`, and `
|
|
11
|
+
`issues/`, `analysis/`, and `MEMORY.md`.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
## On Start
|
|
16
16
|
|
|
17
|
-
1. Read `
|
|
17
|
+
1. Read `MEMORY.md` in the workspace if it exists:
|
|
18
18
|
- skip every pattern under "Confirmed false positives".
|
|
19
19
|
- do not re-report anything under "Fixed".
|
|
20
20
|
- weight the files under "High-risk areas" more heavily.
|
|
@@ -136,6 +136,40 @@ third-party. It is not auditing the library for its own bugs, which belongs to
|
|
|
136
136
|
that library's own review. It is confirming that the control your endpoint relies
|
|
137
137
|
on holds here.
|
|
138
138
|
|
|
139
|
+
## Challenge Every Control, Presence Is Not Sufficiency
|
|
140
|
+
|
|
141
|
+
A control being present is not the same as the control holding. The most common
|
|
142
|
+
way a real review misses a real flaw is clearing a path the moment it sees a
|
|
143
|
+
gate, a signature, or a single-use token, without asking whether that control
|
|
144
|
+
actually defeats the specific attack. For every control you find on a path, do
|
|
145
|
+
not clear on presence. Challenge it on these axes:
|
|
146
|
+
|
|
147
|
+
- **Replay**. A signed or authenticated privileged request is replayable unless
|
|
148
|
+
the control BOTH consumes a one-time nonce AND enforces a freshness window such
|
|
149
|
+
as a timestamp or short expiry. That the caller is authenticated, that the
|
|
150
|
+
scheme fails closed, or that a TOTP is single-use is orthogonal to replay, so
|
|
151
|
+
do not let any of them clear it. Capture one valid signed request and ask: can
|
|
152
|
+
the exact same bytes be sent again and accepted? If nothing is consumed and no
|
|
153
|
+
window is checked, it replays.
|
|
154
|
+
- **Concurrency and state**. A check-then-act is bypassable under concurrent
|
|
155
|
+
requests unless a lock is held across the act, even when the single-request
|
|
156
|
+
path looks single-use. A redeem, a balance debit, or a status transition that
|
|
157
|
+
reads then writes without a row lock or atomic guard double-spends under a race.
|
|
158
|
+
- **Sibling coverage**. A gate on one endpoint does not cover its siblings. When
|
|
159
|
+
you find a missing-authorization or IDOR pattern, enumerate every endpoint
|
|
160
|
+
behind the same controller or gate and report the highest-impact instance, not
|
|
161
|
+
the first one you saw. A read variant flagged MEDIUM often has a create or push
|
|
162
|
+
sibling that is HIGH.
|
|
163
|
+
- **Trusted-source assumptions**. A value is not safe just because a caller you
|
|
164
|
+
treat as trusted set it. If that caller is a distinct tenant or service, the
|
|
165
|
+
value is still attacker-influenced. A self-set `callback_url` that flows into a
|
|
166
|
+
server-side fetch is SSRF or a worker-blocking DoS unless there is an allowlist,
|
|
167
|
+
so do not clear it on "the owner set it".
|
|
168
|
+
|
|
169
|
+
When a control passes one axis, it can still fail another, so run all four before
|
|
170
|
+
you mark a path cleared. Record which axis you checked, not just that a control
|
|
171
|
+
exists.
|
|
172
|
+
|
|
139
173
|
## Scope
|
|
140
174
|
|
|
141
175
|
Report only HIGH / CRITICAL, exploitable, high-confidence issues. **Do not report**
|
|
@@ -205,6 +239,12 @@ from the inventory, trace it through the downstream layers, and log the round in
|
|
|
205
239
|
bugs. The hard classes such as authorization, replay, and broken business state
|
|
206
240
|
usually appear only after several rounds, so keep going.
|
|
207
241
|
|
|
242
|
+
Run the rounds to the Completeness Gate on your own. Do not pause between rounds
|
|
243
|
+
to ask whether to continue, and do not stop early because a round was productive.
|
|
244
|
+
The one place to stop and ask the operator is PoC verification, when a PoC needs a
|
|
245
|
+
credential, test data, or go-ahead for a destructive action. While a PoC is
|
|
246
|
+
blocked on the operator, keep reviewing other sources rather than waiting.
|
|
247
|
+
|
|
208
248
|
## Completeness Gate
|
|
209
249
|
|
|
210
250
|
Do not report the review complete until all of the following hold. A short run
|
|
@@ -217,6 +257,10 @@ reporting it as clean is the failure this gate exists to prevent.
|
|
|
217
257
|
at the view.
|
|
218
258
|
- The Authorization Model pass ran: the access gate is mapped, sibling endpoints
|
|
219
259
|
compared, and IDOR and unauthenticated privileged paths checked.
|
|
260
|
+
- Every control on a cleared path was challenged for sufficiency, not presence:
|
|
261
|
+
replay needs a nonce and a freshness window, check-then-act needs a lock,
|
|
262
|
+
sibling endpoints behind a shared gate were all enumerated, and a trusted-source
|
|
263
|
+
value flowing to a sink was still treated as attacker-influenced.
|
|
220
264
|
- `analysis/_rounds.md` shows two consecutive rounds that added no new source, no
|
|
221
265
|
new traced path, and no new issue.
|
|
222
266
|
|
|
@@ -226,6 +270,6 @@ If any item fails, run another round. State which items pass when you report.
|
|
|
226
270
|
|
|
227
271
|
Report the confirmed findings, the ones with a reproduced PoC, separately from
|
|
228
272
|
the suspected ones still blocked on verification, so the two are never conflated.
|
|
229
|
-
Append a row to the audit history in `
|
|
273
|
+
Append a row to the audit history in `MEMORY.md`, and ask the
|
|
230
274
|
operator which findings were false positives. Record those under "Confirmed false
|
|
231
275
|
positives" so future rounds skip them.
|
|
@@ -45,7 +45,7 @@ def _to_line(value: object) -> int | None:
|
|
|
45
45
|
|
|
46
46
|
def finding_from_dict(data: dict[str, Any]) -> Finding | None:
|
|
47
47
|
"""Map one loosely-typed model finding onto a Finding, or None if it has no
|
|
48
|
-
location
|
|
48
|
+
location, since every finding carries a file, that is the invariant."""
|
|
49
49
|
if not isinstance(data, dict):
|
|
50
50
|
return None
|
|
51
51
|
file = str(data.get("file", "")).strip()
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Best-effort extraction of a JSON object from model output.
|
|
2
2
|
|
|
3
3
|
Models often wrap JSON in prose or code fences, and sometimes emit slightly
|
|
4
|
-
malformed JSON
|
|
5
|
-
token limit
|
|
6
|
-
block, then the first balanced-brace span
|
|
7
|
-
values do not throw off the count
|
|
8
|
-
truncated output. The repair step keeps a single bad
|
|
9
|
-
dropping an entire findings list.
|
|
4
|
+
malformed JSON, for example an unescaped quote, a trailing comma, or a reply
|
|
5
|
+
truncated at the token limit. This recovers the object: a direct parse, then a
|
|
6
|
+
fenced ```json block, then the first balanced-brace span, which is string-aware
|
|
7
|
+
so braces inside string values do not throw off the count, then a json-repair
|
|
8
|
+
fallback for malformed or truncated output. The repair step keeps a single bad
|
|
9
|
+
character from silently dropping an entire findings list.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
@@ -17,7 +17,7 @@ import re
|
|
|
17
17
|
_FENCE = re.compile(r"```(?:json)?\s*(\{.*?\})\s*```", re.DOTALL)
|
|
18
18
|
|
|
19
19
|
# Defensive ceiling: model output is bounded by max_tokens, but never scan an
|
|
20
|
-
# unbounded string
|
|
20
|
+
# unbounded string, the balanced-brace pass is superlinear on pathological input.
|
|
21
21
|
_MAX_SCAN = 1_000_000
|
|
22
22
|
|
|
23
23
|
|
|
@@ -49,7 +49,7 @@ def extract_json_object(text: str) -> dict | None:
|
|
|
49
49
|
|
|
50
50
|
def _first_balanced_object(text: str) -> dict | None:
|
|
51
51
|
"""The first complete top-level {...} span, counting braces only outside of
|
|
52
|
-
string literals so braces inside a value
|
|
52
|
+
string literals so braces inside a value, for example code in a description like "{x}", do not
|
|
53
53
|
corrupt the depth count."""
|
|
54
54
|
depth = 0
|
|
55
55
|
start = -1
|
|
@@ -85,8 +85,8 @@ def _first_balanced_object(text: str) -> dict | None:
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def _repair(text: str) -> dict | None:
|
|
88
|
-
"""Last resort: repair malformed or truncated JSON
|
|
89
|
-
commas, an unterminated reply
|
|
88
|
+
"""Last resort: repair malformed or truncated JSON such as unescaped quotes, trailing
|
|
89
|
+
commas, or an unterminated reply. Optional dependency, a no-op if absent."""
|
|
90
90
|
try:
|
|
91
91
|
from json_repair import repair_json
|
|
92
92
|
except ImportError:
|