codejury 0.13.3__tar.gz → 0.14.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.13.3 → codejury-0.14.0}/PKG-INFO +9 -8
- {codejury-0.13.3 → codejury-0.14.0}/README.md +8 -7
- {codejury-0.13.3 → codejury-0.14.0}/codejury/cli.py +66 -40
- {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/PKG-INFO +9 -8
- {codejury-0.13.3 → codejury-0.14.0}/pyproject.toml +1 -1
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_cli_audit.py +39 -1
- {codejury-0.13.3 → codejury-0.14.0}/LICENSE +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/__init__.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/analysis/__init__.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/analysis/repo_model.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/agent/full-review.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/agent/security-review-memory.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/entrypoints.yaml +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/SKILL.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/business-logic.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/code-injection.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/command-injection.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/cross-site-request-forgery.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/cross-site-scripting.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/hardcoded-secrets.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/http-response-splitting.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/improper-authentication.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/information-exposure.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/insecure-cryptography.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/insecure-deserialization.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/insecure-direct-object-reference.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/insecure-transport.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/jwt-validation.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/mass-assignment.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/missing-authorization.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/open-redirect.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/path-traversal.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/race-condition.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/replay-attack.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/server-side-request-forgery.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/server-side-template-injection.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/session-fixation.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/sql-injection.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/data/rules/xml-external-entity.md +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/__init__.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/debate.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/debate_prompts.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/engine.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/findings_filter.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/prompts.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/report.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/diff/rules.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/domain/__init__.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/domain/finding.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/fullreview/__init__.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/fullreview/scaffold.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/infrastructure/__init__.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/infrastructure/json_parse.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/__init__.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/anthropic.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/base.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/factory.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/litellm.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/mock.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/openai.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/openai_format.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/providers/retry.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury/resources.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/SOURCES.txt +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/setup.cfg +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_diff_debate.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_diff_engine.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_diff_report.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_full_review_scaffold.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_json_parse.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_litellm_provider.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_openai_format.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_openai_provider.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_repo_model.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_retry_provider.py +0 -0
- {codejury-0.13.3 → codejury-0.14.0}/tests/test_rules.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: AI code security review: an adversarial diff-audit engine and an agent-driven whole-repo review methodology, with security knowledge as rich rules
|
|
5
5
|
Author: AISecLabs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -57,29 +57,30 @@ pip install "codejury[anthropic]" # or [openai] / [litellm] for a backend
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
# audit a diff file
|
|
60
|
-
codejury
|
|
60
|
+
codejury review diff --diff-file changes.diff
|
|
61
61
|
|
|
62
62
|
# audit a git range in a repo
|
|
63
|
-
codejury
|
|
63
|
+
codejury review diff --repo /path/to/app --git-range origin/main...HEAD
|
|
64
64
|
|
|
65
65
|
# from stdin
|
|
66
|
-
git diff HEAD~1 | codejury
|
|
66
|
+
git diff HEAD~1 | codejury review diff
|
|
67
67
|
|
|
68
68
|
# adversarial mode: Finder + Challenger + Judge (higher coverage, lower FP, ~3x cost)
|
|
69
|
-
codejury
|
|
69
|
+
codejury review diff --diff-file changes.diff --mode adversarial
|
|
70
70
|
|
|
71
71
|
# CI gate + SARIF
|
|
72
|
-
codejury
|
|
72
|
+
codejury review diff --diff-file changes.diff --format sarif --fail-on high
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
Configure a backend with `--provider`/`--model`/`--api-key`/`--api-base` or the
|
|
76
76
|
`CODEJURY_API_KEY` / `CODEJURY_MODEL` / `CODEJURY_API_BASE` environment variables.
|
|
77
|
-
`codejury dry-run` exercises the engine with a mock provider and
|
|
77
|
+
`codejury review diff --dry-run` exercises the engine with a mock provider and
|
|
78
|
+
no key (it uses a built-in demo diff when you do not pass one).
|
|
78
79
|
|
|
79
80
|
## Whole-repo review
|
|
80
81
|
|
|
81
82
|
```bash
|
|
82
|
-
codejury
|
|
83
|
+
codejury review repo /path/to/your/repo
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
This scaffolds a review workspace (`api/`, `issues/`, `analysis/`, and a
|
|
@@ -26,29 +26,30 @@ pip install "codejury[anthropic]" # or [openai] / [litellm] for a backend
|
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
# audit a diff file
|
|
29
|
-
codejury
|
|
29
|
+
codejury review diff --diff-file changes.diff
|
|
30
30
|
|
|
31
31
|
# audit a git range in a repo
|
|
32
|
-
codejury
|
|
32
|
+
codejury review diff --repo /path/to/app --git-range origin/main...HEAD
|
|
33
33
|
|
|
34
34
|
# from stdin
|
|
35
|
-
git diff HEAD~1 | codejury
|
|
35
|
+
git diff HEAD~1 | codejury review diff
|
|
36
36
|
|
|
37
37
|
# adversarial mode: Finder + Challenger + Judge (higher coverage, lower FP, ~3x cost)
|
|
38
|
-
codejury
|
|
38
|
+
codejury review diff --diff-file changes.diff --mode adversarial
|
|
39
39
|
|
|
40
40
|
# CI gate + SARIF
|
|
41
|
-
codejury
|
|
41
|
+
codejury review diff --diff-file changes.diff --format sarif --fail-on high
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Configure a backend with `--provider`/`--model`/`--api-key`/`--api-base` or the
|
|
45
45
|
`CODEJURY_API_KEY` / `CODEJURY_MODEL` / `CODEJURY_API_BASE` environment variables.
|
|
46
|
-
`codejury dry-run` exercises the engine with a mock provider and
|
|
46
|
+
`codejury review diff --dry-run` exercises the engine with a mock provider and
|
|
47
|
+
no key (it uses a built-in demo diff when you do not pass one).
|
|
47
48
|
|
|
48
49
|
## Whole-repo review
|
|
49
50
|
|
|
50
51
|
```bash
|
|
51
|
-
codejury
|
|
52
|
+
codejury review repo /path/to/your/repo
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
This scaffolds a review workspace (`api/`, `issues/`, `analysis/`, and a
|
|
@@ -19,6 +19,7 @@ import sys
|
|
|
19
19
|
|
|
20
20
|
import dataclasses
|
|
21
21
|
|
|
22
|
+
from codejury import __version__
|
|
22
23
|
from codejury.diff.debate import AdversarialAuditRunner
|
|
23
24
|
from codejury.diff.engine import AuditRunner
|
|
24
25
|
from codejury.diff.findings_filter import FindingsFilter
|
|
@@ -82,11 +83,13 @@ def audit_diff(
|
|
|
82
83
|
finder_model: str | None = None,
|
|
83
84
|
challenger_model: str | None = None,
|
|
84
85
|
judge_model: str | None = None,
|
|
86
|
+
exclude_paths: tuple[str, ...] = (),
|
|
85
87
|
) -> tuple[list[Finding], list[tuple[Finding, str]]]:
|
|
86
88
|
"""Audit a diff and return (kept findings, dropped (finding, reason)).
|
|
87
89
|
|
|
88
90
|
A diff over the size budget is audited one file at a time so it does not
|
|
89
|
-
overflow the context. Finding categories are normalized to the rule-id set.
|
|
91
|
+
overflow the context. Finding categories are normalized to the rule-id set.
|
|
92
|
+
``exclude_paths`` are operator-supplied path substrings to drop."""
|
|
90
93
|
def _run_one(d: str) -> list[Finding]:
|
|
91
94
|
if mode == "adversarial":
|
|
92
95
|
return AdversarialAuditRunner(
|
|
@@ -104,7 +107,7 @@ def audit_diff(
|
|
|
104
107
|
allowed = set(allowed_categories())
|
|
105
108
|
findings = [dataclasses.replace(f, category=normalize_category(f.category, allowed)) for f in findings]
|
|
106
109
|
if filter_findings:
|
|
107
|
-
return FindingsFilter().filter(findings)
|
|
110
|
+
return FindingsFilter(exclude_paths=exclude_paths).filter(findings)
|
|
108
111
|
return findings, []
|
|
109
112
|
|
|
110
113
|
|
|
@@ -124,54 +127,82 @@ def _dry_run_diff() -> str:
|
|
|
124
127
|
return "+++ b/app.py\n@@ -0,0 +1 @@\n+cursor.execute('SELECT * FROM u WHERE n=' + name)\n"
|
|
125
128
|
|
|
126
129
|
|
|
130
|
+
# canned reply for `review diff --dry-run`: a mock provider returns this so the
|
|
131
|
+
# pipeline runs end to end with no key and no backend call
|
|
132
|
+
_MOCK_REPLY = (
|
|
133
|
+
'{"findings": [{"file": "app.py", "line": 1, "severity": "HIGH", '
|
|
134
|
+
'"category": "sql_injection", "description": "[mock] no backend called", '
|
|
135
|
+
'"confidence": 0.9}]}'
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _add_audit_args(p) -> None:
|
|
140
|
+
"""The diff-audit flags for `review diff`."""
|
|
141
|
+
p.add_argument("--diff-file", default=None, help="unified diff file (default: read stdin)")
|
|
142
|
+
p.add_argument("--repo", default=None, help="repo path for --git-range")
|
|
143
|
+
p.add_argument("--git-range", default=None, help="git range to diff, e.g. origin/main...HEAD")
|
|
144
|
+
p.add_argument("--dry-run", action="store_true",
|
|
145
|
+
help="run the engine with a mock provider and no key (a built-in demo diff if none is given)")
|
|
146
|
+
p.add_argument("--exclude", action="append", default=None, metavar="PATH",
|
|
147
|
+
help="drop findings whose file path contains this substring (repeatable)")
|
|
148
|
+
p.add_argument("--mode", choices=("standard", "adversarial"), default="standard")
|
|
149
|
+
p.add_argument("--rounds", type=int, default=3, help="adversarial only: debate rounds")
|
|
150
|
+
p.add_argument("--provider", choices=PROVIDERS, default="anthropic")
|
|
151
|
+
p.add_argument("--model", default=DEFAULT_MODEL)
|
|
152
|
+
p.add_argument("--finder-model", default=DEFAULT_FINDER_MODEL, help="adversarial only: finder role model (default: --model)")
|
|
153
|
+
p.add_argument("--challenger-model", default=DEFAULT_CHALLENGER_MODEL, help="adversarial only: challenger role model")
|
|
154
|
+
p.add_argument("--judge-model", default=DEFAULT_JUDGE_MODEL, help="adversarial only: judge role model")
|
|
155
|
+
p.add_argument("--api-base", default=DEFAULT_API_BASE)
|
|
156
|
+
p.add_argument("--api-key", default=DEFAULT_API_KEY)
|
|
157
|
+
p.add_argument("--retries", type=int, default=2, help="provider retry attempts on transient failure")
|
|
158
|
+
p.add_argument("--format", choices=_FORMATS, default="text", dest="fmt")
|
|
159
|
+
p.add_argument("--no-filter", action="store_true", help="skip the false-positive filter")
|
|
160
|
+
p.add_argument("--fail-on", choices=_FAIL_ON, default=None, dest="fail_on")
|
|
161
|
+
|
|
162
|
+
|
|
127
163
|
def main(argv: list[str] | None = None) -> int:
|
|
128
164
|
parser = argparse.ArgumentParser(prog="codejury")
|
|
165
|
+
parser.add_argument("--version", action="version", version=f"codejury {__version__}")
|
|
129
166
|
sub = parser.add_subparsers(dest="command")
|
|
130
167
|
|
|
131
|
-
sub.add_parser("
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
a.add_argument("--mode", choices=("standard", "adversarial"), default="standard")
|
|
138
|
-
a.add_argument("--rounds", type=int, default=3, help="adversarial debate rounds")
|
|
139
|
-
a.add_argument("--provider", choices=PROVIDERS, default="anthropic")
|
|
140
|
-
a.add_argument("--model", default=DEFAULT_MODEL)
|
|
141
|
-
a.add_argument("--finder-model", default=DEFAULT_FINDER_MODEL, help="adversarial: finder role model (default: --model)")
|
|
142
|
-
a.add_argument("--challenger-model", default=DEFAULT_CHALLENGER_MODEL, help="adversarial: challenger role model")
|
|
143
|
-
a.add_argument("--judge-model", default=DEFAULT_JUDGE_MODEL, help="adversarial: judge role model")
|
|
144
|
-
a.add_argument("--api-base", default=DEFAULT_API_BASE)
|
|
145
|
-
a.add_argument("--api-key", default=DEFAULT_API_KEY)
|
|
146
|
-
a.add_argument("--retries", type=int, default=2, help="provider retry attempts on transient failure")
|
|
147
|
-
a.add_argument("--format", choices=_FORMATS, default="text", dest="fmt")
|
|
148
|
-
a.add_argument("--no-filter", action="store_true", help="skip the false-positive filter")
|
|
149
|
-
a.add_argument("--fail-on", choices=_FAIL_ON, default=None, dest="fail_on")
|
|
150
|
-
|
|
151
|
-
fr = sub.add_parser("full-review", help="scaffold a whole-repo review for an interactive agent")
|
|
152
|
-
fr.add_argument("directory", help="target repository to review")
|
|
153
|
-
fr.add_argument("--workspace", default="codejury-review", help="where to create the review workspace")
|
|
168
|
+
review = sub.add_parser("review", help="review code for security findings")
|
|
169
|
+
rsub = review.add_subparsers(dest="scope")
|
|
170
|
+
_add_audit_args(rsub.add_parser("diff", help="audit a unified diff (the coded engine)"))
|
|
171
|
+
repo = rsub.add_parser("repo", help="scaffold a whole-repo review for an interactive agent")
|
|
172
|
+
repo.add_argument("directory", help="target repository to review")
|
|
173
|
+
repo.add_argument("--workspace", default="codejury-review", help="where to create the review workspace")
|
|
154
174
|
|
|
155
175
|
args = parser.parse_args(argv)
|
|
156
176
|
try:
|
|
157
177
|
return _dispatch(args, parser)
|
|
158
178
|
except Exception as exc:
|
|
159
|
-
|
|
179
|
+
label = getattr(args, "command", None) or "codejury"
|
|
180
|
+
print(f"{label} failed: {exc}", file=sys.stderr)
|
|
160
181
|
return 1
|
|
161
182
|
|
|
162
183
|
|
|
163
184
|
def _dispatch(args, parser) -> int:
|
|
164
|
-
|
|
165
|
-
|
|
185
|
+
scope = getattr(args, "scope", None)
|
|
186
|
+
if args.command == "review" and scope == "diff":
|
|
187
|
+
if args.dry_run:
|
|
188
|
+
provider = MockProvider(default=_MOCK_REPLY)
|
|
189
|
+
model = "mock"
|
|
190
|
+
# zero-config smoke test: fall back to a built-in demo diff when none is supplied
|
|
191
|
+
diff = _read_diff(args) if (args.diff_file or args.git_range) else _dry_run_diff()
|
|
192
|
+
else:
|
|
193
|
+
provider = make_provider(args.provider, api_key=args.api_key, api_base=args.api_base, retries=args.retries)
|
|
194
|
+
model = args.model
|
|
195
|
+
diff = _read_diff(args)
|
|
166
196
|
kept, _ = audit_diff(
|
|
167
|
-
|
|
197
|
+
diff, provider=provider, model=model,
|
|
168
198
|
mode=args.mode, max_rounds=args.rounds, filter_findings=not args.no_filter,
|
|
169
199
|
finder_model=args.finder_model, challenger_model=args.challenger_model, judge_model=args.judge_model,
|
|
200
|
+
exclude_paths=tuple(args.exclude or ()),
|
|
170
201
|
)
|
|
171
202
|
print(render(args.fmt, kept))
|
|
172
203
|
return 1 if gate(kept, args.fail_on) else 0
|
|
173
204
|
|
|
174
|
-
if args.command == "
|
|
205
|
+
if args.command == "review" and scope == "repo":
|
|
175
206
|
res = scaffold(args.directory, args.workspace)
|
|
176
207
|
print(f"Workspace: {res.workspace}", file=sys.stderr)
|
|
177
208
|
print(f"Seeded {res.entrypoints} entrypoints into {res.workspace}/api/_entrypoints.md", file=sys.stderr)
|
|
@@ -180,16 +211,11 @@ def _dispatch(args, parser) -> int:
|
|
|
180
211
|
print(res.methodology)
|
|
181
212
|
return 0
|
|
182
213
|
|
|
183
|
-
if args.command
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
'"confidence": 0.9}]}'),
|
|
189
|
-
model="mock",
|
|
190
|
-
)
|
|
191
|
-
print(render("text", kept))
|
|
192
|
-
return 0
|
|
214
|
+
if args.command == "review": # no scope given
|
|
215
|
+
print("usage: codejury review {diff,repo} ...", file=sys.stderr)
|
|
216
|
+
print(" diff audit a unified diff for security findings", file=sys.stderr)
|
|
217
|
+
print(" repo scaffold a whole-repo review for an interactive agent", file=sys.stderr)
|
|
218
|
+
return 1
|
|
193
219
|
|
|
194
220
|
parser.print_help()
|
|
195
221
|
return 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: AI code security review: an adversarial diff-audit engine and an agent-driven whole-repo review methodology, with security knowledge as rich rules
|
|
5
5
|
Author: AISecLabs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -57,29 +57,30 @@ pip install "codejury[anthropic]" # or [openai] / [litellm] for a backend
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
# audit a diff file
|
|
60
|
-
codejury
|
|
60
|
+
codejury review diff --diff-file changes.diff
|
|
61
61
|
|
|
62
62
|
# audit a git range in a repo
|
|
63
|
-
codejury
|
|
63
|
+
codejury review diff --repo /path/to/app --git-range origin/main...HEAD
|
|
64
64
|
|
|
65
65
|
# from stdin
|
|
66
|
-
git diff HEAD~1 | codejury
|
|
66
|
+
git diff HEAD~1 | codejury review diff
|
|
67
67
|
|
|
68
68
|
# adversarial mode: Finder + Challenger + Judge (higher coverage, lower FP, ~3x cost)
|
|
69
|
-
codejury
|
|
69
|
+
codejury review diff --diff-file changes.diff --mode adversarial
|
|
70
70
|
|
|
71
71
|
# CI gate + SARIF
|
|
72
|
-
codejury
|
|
72
|
+
codejury review diff --diff-file changes.diff --format sarif --fail-on high
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
Configure a backend with `--provider`/`--model`/`--api-key`/`--api-base` or the
|
|
76
76
|
`CODEJURY_API_KEY` / `CODEJURY_MODEL` / `CODEJURY_API_BASE` environment variables.
|
|
77
|
-
`codejury dry-run` exercises the engine with a mock provider and
|
|
77
|
+
`codejury review diff --dry-run` exercises the engine with a mock provider and
|
|
78
|
+
no key (it uses a built-in demo diff when you do not pass one).
|
|
78
79
|
|
|
79
80
|
## Whole-repo review
|
|
80
81
|
|
|
81
82
|
```bash
|
|
82
|
-
codejury
|
|
83
|
+
codejury review repo /path/to/your/repo
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
This scaffolds a review workspace (`api/`, `issues/`, `analysis/`, and a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codejury"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.14.0"
|
|
4
4
|
description = "AI code security review: an adversarial diff-audit engine and an agent-driven whole-repo review methodology, with security knowledge as rich rules"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -5,7 +5,9 @@ big PR does not overflow the model context and silently truncate the reply; the
|
|
|
5
5
|
per-file findings are then de-duplicated.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from codejury.cli import _dedup_findings, _split_diff_by_file, audit_diff, main
|
|
9
11
|
from codejury.domain.finding import Finding
|
|
10
12
|
from codejury.providers.mock import MockProvider
|
|
11
13
|
|
|
@@ -43,3 +45,39 @@ def test_large_diff_is_audited_per_file(monkeypatch):
|
|
|
43
45
|
assert len(provider.calls) == 2
|
|
44
46
|
# category normalized onto the rule-id set
|
|
45
47
|
assert all(f.category == "sql-injection" for f in kept)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_audit_diff_honors_exclude_paths():
|
|
51
|
+
resp = ('{"findings": [{"file": "vendor/lib.py", "line": 1, "severity": "HIGH", '
|
|
52
|
+
'"category": "sql_injection", "description": "x", "confidence": 0.9}]}')
|
|
53
|
+
kept, dropped = audit_diff(
|
|
54
|
+
_FILE_A, provider=MockProvider(default=resp), model="mock", exclude_paths=("vendor/",)
|
|
55
|
+
)
|
|
56
|
+
assert kept == [] and dropped and "excluded path" in dropped[0][1]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# --- CLI surface ---
|
|
60
|
+
|
|
61
|
+
def test_version_flag_exits_zero(capsys):
|
|
62
|
+
with pytest.raises(SystemExit) as exc:
|
|
63
|
+
main(["--version"])
|
|
64
|
+
assert exc.value.code == 0
|
|
65
|
+
assert "codejury" in capsys.readouterr().out
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_review_diff_dry_run_is_zero_config(capsys):
|
|
69
|
+
# no diff input, no key: the built-in demo diff runs through the mock provider
|
|
70
|
+
rc = main(["review", "diff", "--dry-run"])
|
|
71
|
+
assert rc == 0
|
|
72
|
+
assert "sql-injection" in capsys.readouterr().out
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_review_diff_dry_run_respects_exclude(capsys):
|
|
76
|
+
rc = main(["review", "diff", "--dry-run", "--exclude", "app.py"])
|
|
77
|
+
assert rc == 0
|
|
78
|
+
assert "no findings" in capsys.readouterr().out
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_old_audit_command_is_gone(capsys):
|
|
82
|
+
with pytest.raises(SystemExit): # argparse rejects the removed command
|
|
83
|
+
main(["audit", "--dry-run"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|