codejury 0.14.1__tar.gz → 0.14.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codejury-0.14.1 → codejury-0.14.2}/PKG-INFO +1 -1
- {codejury-0.14.1 → codejury-0.14.2}/codejury/cli.py +10 -83
- {codejury-0.14.1 → codejury-0.14.2}/codejury/diff/debate.py +1 -1
- {codejury-0.14.1 → codejury-0.14.2}/codejury/diff/engine.py +1 -1
- codejury-0.14.2/codejury/diff/runner.py +85 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury.egg-info/PKG-INFO +1 -1
- {codejury-0.14.1 → codejury-0.14.2}/codejury.egg-info/SOURCES.txt +4 -4
- {codejury-0.14.1 → codejury-0.14.2}/pyproject.toml +1 -1
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_cli_audit.py +8 -7
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_json_parse.py +1 -1
- codejury-0.14.1/tests/test_diff_report.py → codejury-0.14.2/tests/test_report.py +1 -1
- codejury-0.14.1/codejury/infrastructure/__init__.py +0 -4
- {codejury-0.14.1 → codejury-0.14.2}/LICENSE +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/README.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/__init__.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/agent/full-review.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/agent/security-review-memory.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/entrypoints.yaml +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/SKILL.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/business-logic.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/code-injection.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/command-injection.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/cross-site-request-forgery.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/cross-site-scripting.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/hardcoded-secrets.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/http-response-splitting.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/improper-authentication.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/information-exposure.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/insecure-cryptography.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/insecure-deserialization.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/insecure-direct-object-reference.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/insecure-transport.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/jwt-validation.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/mass-assignment.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/missing-authorization.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/open-redirect.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/path-traversal.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/race-condition.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/replay-attack.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/server-side-request-forgery.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/server-side-template-injection.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/session-fixation.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/sql-injection.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/data/rules/xml-external-entity.md +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/diff/__init__.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/diff/debate_prompts.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/diff/findings_filter.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/diff/prompts.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/diff/rules.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/domain/__init__.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/domain/finding.py +0 -0
- {codejury-0.14.1/codejury/infrastructure → codejury-0.14.2/codejury}/json_parse.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/__init__.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/anthropic.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/base.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/factory.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/litellm.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/mock.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/openai.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/openai_format.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/providers/retry.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/repo/__init__.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/repo/model.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/repo/scaffold.py +0 -0
- {codejury-0.14.1/codejury/diff → codejury-0.14.2/codejury}/report.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury/resources.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/setup.cfg +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_diff_debate.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_diff_engine.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_litellm_provider.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_openai_format.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_openai_provider.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_repo_model.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_repo_scaffold.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_retry_provider.py +0 -0
- {codejury-0.14.1 → codejury-0.14.2}/tests/test_rules.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.2
|
|
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
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
"""codejury CLI.
|
|
1
|
+
"""codejury CLI: thin argument parsing and dispatch.
|
|
2
2
|
|
|
3
|
-
Two
|
|
3
|
+
Two paths matched to their nature:
|
|
4
4
|
|
|
5
|
-
- ``
|
|
6
|
-
call (standard) or the adversarial Finder/Challenger/Judge pass.
|
|
7
|
-
- ``
|
|
5
|
+
- ``review diff`` runs the coded diff engine over a unified diff: a single
|
|
6
|
+
balanced call (standard) or the adversarial Finder/Challenger/Judge pass.
|
|
7
|
+
- ``review repo`` scaffolds a workspace and prints the methodology for an
|
|
8
8
|
interactive agent to run a whole-repo review (it does not run an LLM pipeline,
|
|
9
9
|
which a single call cannot do for a whole codebase).
|
|
10
10
|
|
|
11
|
-
``dry-run`` exercises the
|
|
11
|
+
``review diff --dry-run`` exercises the engine with a mock provider and no key.
|
|
12
|
+
The audit orchestration itself lives in ``codejury.diff.runner``.
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
from __future__ import annotations
|
|
@@ -17,20 +18,9 @@ import argparse
|
|
|
17
18
|
import subprocess
|
|
18
19
|
import sys
|
|
19
20
|
|
|
20
|
-
import dataclasses
|
|
21
|
-
|
|
22
21
|
from codejury import __version__
|
|
23
|
-
from codejury.
|
|
24
|
-
from codejury.diff.
|
|
25
|
-
from codejury.diff.findings_filter import FindingsFilter
|
|
26
|
-
from codejury.diff.report import gate, render
|
|
27
|
-
from codejury.diff.rules import allowed_categories, normalize_category
|
|
28
|
-
from codejury.domain.finding import Finding
|
|
29
|
-
|
|
30
|
-
# A diff larger than this is audited file-by-file so a big PR does not overflow
|
|
31
|
-
# the model's context and silently truncate the reply.
|
|
32
|
-
_MAX_DIFF_CHARS = 60_000
|
|
33
|
-
from codejury.repo.scaffold import scaffold
|
|
22
|
+
from codejury.report import gate, render
|
|
23
|
+
from codejury.diff.runner import audit_diff
|
|
34
24
|
from codejury.providers.factory import (
|
|
35
25
|
DEFAULT_API_BASE,
|
|
36
26
|
DEFAULT_API_KEY,
|
|
@@ -42,75 +32,12 @@ from codejury.providers.factory import (
|
|
|
42
32
|
make_provider,
|
|
43
33
|
)
|
|
44
34
|
from codejury.providers.mock import MockProvider
|
|
35
|
+
from codejury.repo.scaffold import scaffold
|
|
45
36
|
|
|
46
37
|
_FORMATS = ("text", "markdown", "json", "sarif")
|
|
47
38
|
_FAIL_ON = ("critical", "high", "medium", "low")
|
|
48
39
|
|
|
49
40
|
|
|
50
|
-
def _split_diff_by_file(diff: str) -> list[str]:
|
|
51
|
-
"""Split a unified diff into one diff per file (`diff --git ...` boundaries)."""
|
|
52
|
-
chunks: list[str] = []
|
|
53
|
-
cur: list[str] = []
|
|
54
|
-
for line in diff.splitlines(keepends=True):
|
|
55
|
-
if line.startswith("diff --git ") and cur:
|
|
56
|
-
chunks.append("".join(cur))
|
|
57
|
-
cur = []
|
|
58
|
-
cur.append(line)
|
|
59
|
-
if cur:
|
|
60
|
-
chunks.append("".join(cur))
|
|
61
|
-
return chunks or ([diff] if diff.strip() else [])
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _dedup_findings(findings: list[Finding]) -> list[Finding]:
|
|
65
|
-
seen: set = set()
|
|
66
|
-
out: list[Finding] = []
|
|
67
|
-
for f in findings:
|
|
68
|
-
k = (f.file, f.line, f.category, f.description)
|
|
69
|
-
if k not in seen:
|
|
70
|
-
seen.add(k)
|
|
71
|
-
out.append(f)
|
|
72
|
-
return out
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def audit_diff(
|
|
76
|
-
diff: str,
|
|
77
|
-
*,
|
|
78
|
-
provider,
|
|
79
|
-
model: str,
|
|
80
|
-
mode: str = "standard",
|
|
81
|
-
max_rounds: int = 3,
|
|
82
|
-
filter_findings: bool = True,
|
|
83
|
-
finder_model: str | None = None,
|
|
84
|
-
challenger_model: str | None = None,
|
|
85
|
-
judge_model: str | None = None,
|
|
86
|
-
exclude_paths: tuple[str, ...] = (),
|
|
87
|
-
) -> tuple[list[Finding], list[tuple[Finding, str]]]:
|
|
88
|
-
"""Audit a diff and return (kept findings, dropped (finding, reason)).
|
|
89
|
-
|
|
90
|
-
A diff over the size budget is audited one file at a time so it does not
|
|
91
|
-
overflow the context. Finding categories are normalized to the rule-id set.
|
|
92
|
-
``exclude_paths`` are operator-supplied path substrings to drop."""
|
|
93
|
-
def _run_one(d: str) -> list[Finding]:
|
|
94
|
-
if mode == "adversarial":
|
|
95
|
-
return AdversarialAuditRunner(
|
|
96
|
-
provider=provider, model=model,
|
|
97
|
-
finder_model=finder_model, challenger_model=challenger_model, judge_model=judge_model,
|
|
98
|
-
).run(d, max_rounds=max_rounds).findings
|
|
99
|
-
return AuditRunner(provider=provider, model=model).run(d)
|
|
100
|
-
|
|
101
|
-
if len(diff) > _MAX_DIFF_CHARS:
|
|
102
|
-
chunks = _split_diff_by_file(diff)
|
|
103
|
-
findings = _dedup_findings([f for c in chunks for f in _run_one(c)])
|
|
104
|
-
else:
|
|
105
|
-
findings = _run_one(diff)
|
|
106
|
-
|
|
107
|
-
allowed = set(allowed_categories())
|
|
108
|
-
findings = [dataclasses.replace(f, category=normalize_category(f.category, allowed)) for f in findings]
|
|
109
|
-
if filter_findings:
|
|
110
|
-
return FindingsFilter(exclude_paths=exclude_paths).filter(findings)
|
|
111
|
-
return findings, []
|
|
112
|
-
|
|
113
|
-
|
|
114
41
|
def _read_diff(args) -> str:
|
|
115
42
|
if args.diff_file:
|
|
116
43
|
with open(args.diff_file, encoding="utf-8") as f:
|
|
@@ -21,7 +21,7 @@ from codejury.diff.debate_prompts import (
|
|
|
21
21
|
)
|
|
22
22
|
from codejury.diff.rules import rules_for_diff
|
|
23
23
|
from codejury.domain.finding import Finding, findings_from_list
|
|
24
|
-
from codejury.
|
|
24
|
+
from codejury.json_parse import extract_json_object
|
|
25
25
|
from codejury.providers.base import Message, Provider
|
|
26
26
|
|
|
27
27
|
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from codejury.domain.finding import Finding, findings_from_list
|
|
11
11
|
from codejury.diff.prompts import SYSTEM, standard_audit_prompt
|
|
12
12
|
from codejury.diff.rules import rules_for_diff
|
|
13
|
-
from codejury.
|
|
13
|
+
from codejury.json_parse import extract_json_object
|
|
14
14
|
from codejury.providers.base import Message, Provider
|
|
15
15
|
|
|
16
16
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Diff-audit orchestration: run a diff through the engine and clean the result.
|
|
2
|
+
|
|
3
|
+
The library entry point behind `review diff`. Picks the standard or adversarial
|
|
4
|
+
engine, audits a large diff one file at a time so a big PR does not overflow the
|
|
5
|
+
model context, normalizes finding categories onto the rule-id set, and applies
|
|
6
|
+
the false-positive filter. Kept out of the CLI so it can be called as a library.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import dataclasses
|
|
12
|
+
|
|
13
|
+
from codejury.diff.debate import AdversarialAuditRunner
|
|
14
|
+
from codejury.diff.engine import AuditRunner
|
|
15
|
+
from codejury.diff.findings_filter import FindingsFilter
|
|
16
|
+
from codejury.diff.rules import allowed_categories, normalize_category
|
|
17
|
+
from codejury.domain.finding import Finding
|
|
18
|
+
|
|
19
|
+
# A diff larger than this is audited file-by-file so a big PR does not overflow
|
|
20
|
+
# the model's context and silently truncate the reply.
|
|
21
|
+
_MAX_DIFF_CHARS = 60_000
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def split_diff_by_file(diff: str) -> list[str]:
|
|
25
|
+
"""Split a unified diff into one diff per file (`diff --git ...` boundaries)."""
|
|
26
|
+
chunks: list[str] = []
|
|
27
|
+
cur: list[str] = []
|
|
28
|
+
for line in diff.splitlines(keepends=True):
|
|
29
|
+
if line.startswith("diff --git ") and cur:
|
|
30
|
+
chunks.append("".join(cur))
|
|
31
|
+
cur = []
|
|
32
|
+
cur.append(line)
|
|
33
|
+
if cur:
|
|
34
|
+
chunks.append("".join(cur))
|
|
35
|
+
return chunks or ([diff] if diff.strip() else [])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def dedup_findings(findings: list[Finding]) -> list[Finding]:
|
|
39
|
+
seen: set = set()
|
|
40
|
+
out: list[Finding] = []
|
|
41
|
+
for f in findings:
|
|
42
|
+
k = (f.file, f.line, f.category, f.description)
|
|
43
|
+
if k not in seen:
|
|
44
|
+
seen.add(k)
|
|
45
|
+
out.append(f)
|
|
46
|
+
return out
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def audit_diff(
|
|
50
|
+
diff: str,
|
|
51
|
+
*,
|
|
52
|
+
provider,
|
|
53
|
+
model: str,
|
|
54
|
+
mode: str = "standard",
|
|
55
|
+
max_rounds: int = 3,
|
|
56
|
+
filter_findings: bool = True,
|
|
57
|
+
finder_model: str | None = None,
|
|
58
|
+
challenger_model: str | None = None,
|
|
59
|
+
judge_model: str | None = None,
|
|
60
|
+
exclude_paths: tuple[str, ...] = (),
|
|
61
|
+
) -> tuple[list[Finding], list[tuple[Finding, str]]]:
|
|
62
|
+
"""Audit a diff and return (kept findings, dropped (finding, reason)).
|
|
63
|
+
|
|
64
|
+
A diff over the size budget is audited one file at a time so it does not
|
|
65
|
+
overflow the context. Finding categories are normalized to the rule-id set.
|
|
66
|
+
``exclude_paths`` are operator-supplied path substrings to drop."""
|
|
67
|
+
def _run_one(d: str) -> list[Finding]:
|
|
68
|
+
if mode == "adversarial":
|
|
69
|
+
return AdversarialAuditRunner(
|
|
70
|
+
provider=provider, model=model,
|
|
71
|
+
finder_model=finder_model, challenger_model=challenger_model, judge_model=judge_model,
|
|
72
|
+
).run(d, max_rounds=max_rounds).findings
|
|
73
|
+
return AuditRunner(provider=provider, model=model).run(d)
|
|
74
|
+
|
|
75
|
+
if len(diff) > _MAX_DIFF_CHARS:
|
|
76
|
+
chunks = split_diff_by_file(diff)
|
|
77
|
+
findings = dedup_findings([f for c in chunks for f in _run_one(c)])
|
|
78
|
+
else:
|
|
79
|
+
findings = _run_one(diff)
|
|
80
|
+
|
|
81
|
+
allowed = set(allowed_categories())
|
|
82
|
+
findings = [dataclasses.replace(f, category=normalize_category(f.category, allowed)) for f in findings]
|
|
83
|
+
if filter_findings:
|
|
84
|
+
return FindingsFilter(exclude_paths=exclude_paths).filter(findings)
|
|
85
|
+
return findings, []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.2
|
|
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
|
|
@@ -3,6 +3,8 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
codejury/__init__.py
|
|
5
5
|
codejury/cli.py
|
|
6
|
+
codejury/json_parse.py
|
|
7
|
+
codejury/report.py
|
|
6
8
|
codejury/resources.py
|
|
7
9
|
codejury.egg-info/PKG-INFO
|
|
8
10
|
codejury.egg-info/SOURCES.txt
|
|
@@ -45,12 +47,10 @@ codejury/diff/debate_prompts.py
|
|
|
45
47
|
codejury/diff/engine.py
|
|
46
48
|
codejury/diff/findings_filter.py
|
|
47
49
|
codejury/diff/prompts.py
|
|
48
|
-
codejury/diff/report.py
|
|
49
50
|
codejury/diff/rules.py
|
|
51
|
+
codejury/diff/runner.py
|
|
50
52
|
codejury/domain/__init__.py
|
|
51
53
|
codejury/domain/finding.py
|
|
52
|
-
codejury/infrastructure/__init__.py
|
|
53
|
-
codejury/infrastructure/json_parse.py
|
|
54
54
|
codejury/providers/__init__.py
|
|
55
55
|
codejury/providers/anthropic.py
|
|
56
56
|
codejury/providers/base.py
|
|
@@ -67,12 +67,12 @@ tests/test_anthropic_provider.py
|
|
|
67
67
|
tests/test_cli_audit.py
|
|
68
68
|
tests/test_diff_debate.py
|
|
69
69
|
tests/test_diff_engine.py
|
|
70
|
-
tests/test_diff_report.py
|
|
71
70
|
tests/test_json_parse.py
|
|
72
71
|
tests/test_litellm_provider.py
|
|
73
72
|
tests/test_openai_format.py
|
|
74
73
|
tests/test_openai_provider.py
|
|
75
74
|
tests/test_repo_model.py
|
|
76
75
|
tests/test_repo_scaffold.py
|
|
76
|
+
tests/test_report.py
|
|
77
77
|
tests/test_retry_provider.py
|
|
78
78
|
tests/test_rules.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codejury"
|
|
3
|
-
version = "0.14.
|
|
3
|
+
version = "0.14.2"
|
|
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"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Diff-audit orchestration (codejury.diff.runner) plus the thin CLI surface.
|
|
2
2
|
|
|
3
3
|
A diff over the size budget is split per file and audited one file at a time so a
|
|
4
4
|
big PR does not overflow the model context and silently truncate the reply; the
|
|
@@ -7,7 +7,8 @@ per-file findings are then de-duplicated.
|
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
|
-
from codejury.cli import
|
|
10
|
+
from codejury.cli import main
|
|
11
|
+
from codejury.diff.runner import audit_diff, dedup_findings, split_diff_by_file
|
|
11
12
|
from codejury.domain.finding import Finding
|
|
12
13
|
from codejury.providers.mock import MockProvider
|
|
13
14
|
|
|
@@ -16,14 +17,14 @@ _FILE_B = "diff --git a/b.py b/b.py\n@@ -0,0 +1 @@\n+y = 2\n"
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def test_split_diff_by_file():
|
|
19
|
-
chunks =
|
|
20
|
+
chunks = split_diff_by_file(_FILE_A + _FILE_B)
|
|
20
21
|
assert chunks == [_FILE_A, _FILE_B]
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def test_split_diff_empty_and_unbounded():
|
|
24
|
-
assert
|
|
25
|
+
assert split_diff_by_file("") == []
|
|
25
26
|
# a fragment with no `diff --git` boundary is returned as one chunk
|
|
26
|
-
assert
|
|
27
|
+
assert split_diff_by_file("just text\n") == ["just text\n"]
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def test_dedup_findings_collapses_identical():
|
|
@@ -31,12 +32,12 @@ def test_dedup_findings_collapses_identical():
|
|
|
31
32
|
description="d", confidence=0.9)
|
|
32
33
|
g = Finding(file="a.py", line=2, severity="HIGH", category="sql-injection",
|
|
33
34
|
description="d", confidence=0.9)
|
|
34
|
-
assert
|
|
35
|
+
assert dedup_findings([f, f, g]) == [f, g]
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def test_large_diff_is_audited_per_file(monkeypatch):
|
|
38
39
|
# force the chunking path on a small diff
|
|
39
|
-
monkeypatch.setattr("codejury.
|
|
40
|
+
monkeypatch.setattr("codejury.diff.runner._MAX_DIFF_CHARS", 1)
|
|
40
41
|
resp = ('{"findings": [{"file": "a.py", "line": 1, "severity": "HIGH", '
|
|
41
42
|
'"category": "sql_injection", "description": "x", "confidence": 0.9}]}')
|
|
42
43
|
provider = MockProvider(default=resp)
|
|
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
|