codejury 0.14.2__tar.gz → 0.14.4__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.2 → codejury-0.14.4}/PKG-INFO +1 -1
- {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/debate.py +34 -9
- {codejury-0.14.2 → codejury-0.14.4}/codejury/repo/model.py +4 -1
- {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/PKG-INFO +1 -1
- {codejury-0.14.2 → codejury-0.14.4}/pyproject.toml +1 -1
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_diff_debate.py +23 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_repo_model.py +21 -0
- {codejury-0.14.2 → codejury-0.14.4}/LICENSE +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/README.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/__init__.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/cli.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/agent/full-review.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/agent/security-review-memory.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/entrypoints.yaml +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/SKILL.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/business-logic.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/code-injection.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/command-injection.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/cross-site-request-forgery.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/cross-site-scripting.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/hardcoded-secrets.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/http-response-splitting.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/improper-authentication.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/information-exposure.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/insecure-cryptography.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/insecure-deserialization.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/insecure-direct-object-reference.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/insecure-transport.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/jwt-validation.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/mass-assignment.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/missing-authorization.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/open-redirect.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/path-traversal.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/race-condition.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/replay-attack.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/server-side-request-forgery.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/server-side-template-injection.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/session-fixation.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/sql-injection.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/xml-external-entity.md +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/__init__.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/debate_prompts.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/engine.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/findings_filter.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/prompts.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/rules.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/runner.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/domain/__init__.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/domain/finding.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/json_parse.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/__init__.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/anthropic.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/base.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/factory.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/litellm.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/mock.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/openai.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/openai_format.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/retry.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/repo/__init__.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/repo/scaffold.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/report.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury/resources.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/SOURCES.txt +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/setup.cfg +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_cli_audit.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_diff_engine.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_json_parse.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_litellm_provider.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_openai_format.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_openai_provider.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_repo_scaffold.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_report.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/tests/test_retry_provider.py +0 -0
- {codejury-0.14.2 → codejury-0.14.4}/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.4
|
|
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
|
|
@@ -56,6 +56,28 @@ def _dedup(items: list[dict]) -> list[dict]:
|
|
|
56
56
|
return out
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
_DISMISS_VERDICTS = frozenset({"dismiss", "dismissed", "false_positive", "reject", "rejected"})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _loc(d: dict) -> str:
|
|
63
|
+
line = d.get("line")
|
|
64
|
+
return f"{d.get('file')}:{line}" if line else str(d.get("file") or "")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _apply_dismissals(findings: list[dict], rebuttals: list[dict]) -> list[dict]:
|
|
68
|
+
"""Drop findings the challenger dismissed. The challenger is recall-safe (it
|
|
69
|
+
dismisses only when the diff shows a safe pattern: a parameterized query,
|
|
70
|
+
basename, an allowlist, shell=False), so honoring its dismissals is sound even
|
|
71
|
+
when the judge is unavailable."""
|
|
72
|
+
dismissed = {
|
|
73
|
+
str(r.get("target")) for r in rebuttals
|
|
74
|
+
if str(r.get("verdict", "")).strip().lower() in _DISMISS_VERDICTS
|
|
75
|
+
}
|
|
76
|
+
if not dismissed:
|
|
77
|
+
return findings
|
|
78
|
+
return [f for f in findings if _loc(f) not in dismissed and str(f.get("file") or "") not in dismissed]
|
|
79
|
+
|
|
80
|
+
|
|
59
81
|
class AdversarialAuditRunner:
|
|
60
82
|
def __init__(
|
|
61
83
|
self,
|
|
@@ -116,16 +138,19 @@ class AdversarialAuditRunner:
|
|
|
116
138
|
rebuttals = _dicts(challenger.get("rebuttals"))
|
|
117
139
|
new_findings = _dicts(challenger.get("new_findings"))
|
|
118
140
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
jp = judge_prompt(diff, finder_findings, rebuttals, new_findings, context=context)
|
|
142
|
+
verdict, judge_ok = self._ask(JUDGE_SYSTEM, jp, self._judge_model)
|
|
143
|
+
if not judge_ok:
|
|
144
|
+
# the judge is the filter that controls false positives, and an
|
|
145
|
+
# unusable reply is often transient (a flaky proxy, a blocked
|
|
146
|
+
# request); re-ask once before giving up on it
|
|
147
|
+
verdict, judge_ok = self._ask(JUDGE_SYSTEM, jp, self._judge_model)
|
|
124
148
|
if not judge_ok:
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
|
|
149
|
+
# judge still unusable: degrade, but apply the recall-safe
|
|
150
|
+
# challenger's dismissals so a transient judge outage does not pass
|
|
151
|
+
# through findings the challenger already showed are safe (this is
|
|
152
|
+
# what otherwise inflates false positives in degraded runs)
|
|
153
|
+
fallback = _dedup(_apply_dismissals(finder_findings, rebuttals) + new_findings)
|
|
129
154
|
judged = AdversarialResult(findings=findings_from_list(fallback), rounds=rounds, degraded=True)
|
|
130
155
|
degraded = True
|
|
131
156
|
break
|
|
@@ -86,7 +86,10 @@ def build_repo_model(root: str | Path, files: dict[str, str], *, signatures: _Si
|
|
|
86
86
|
tree = ast.parse(content)
|
|
87
87
|
except SyntaxError:
|
|
88
88
|
continue
|
|
89
|
-
|
|
89
|
+
# an entrypoint must name a reviewable handler function; drop entries
|
|
90
|
+
# with no resolvable function (a Django include() mount, or a view the
|
|
91
|
+
# AST scan cannot resolve) so the seeded inventory stays clean
|
|
92
|
+
entrypoints.extend(e for e in _entrypoints_in(path, tree, sigs) if e.function)
|
|
90
93
|
return RepoModel(root=str(root), files=tuple(sorted(files)), entrypoints=tuple(entrypoints))
|
|
91
94
|
|
|
92
95
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.4
|
|
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,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codejury"
|
|
3
|
-
version = "0.14.
|
|
3
|
+
version = "0.14.4"
|
|
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"
|
|
@@ -156,6 +156,29 @@ def test_provider_exception_degrades_rather_than_crashes():
|
|
|
156
156
|
assert out.degraded is True
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
def test_judge_retry_recovers_from_a_transient_unusable_reply():
|
|
160
|
+
# the judge's first reply is unusable, the retry succeeds: not degraded,
|
|
161
|
+
# and the judge verdict is applied normally
|
|
162
|
+
_, out = _run([_finder([_VULN]), _challenger(), "blocked by waf", _judge([_VULN])], max_rounds=1)
|
|
163
|
+
assert out.degraded is False
|
|
164
|
+
assert [f.category for f in out.findings] == ["sql_injection"]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_degraded_fallback_drops_challenger_dismissed_findings():
|
|
168
|
+
# judge stays unusable (both the call and its retry); the degraded fallback
|
|
169
|
+
# must still honor the challenger's recall-safe dismissals rather than pass
|
|
170
|
+
# every finder finding through, which is what inflates false positives
|
|
171
|
+
second = {**_VULN, "line": 5, "category": "xss"}
|
|
172
|
+
_, out = _run(
|
|
173
|
+
[_finder([_VULN, second]),
|
|
174
|
+
_challenger(rebuttals=[{"target": "app.py:5", "verdict": "dismiss", "reason": "output is escaped"}]),
|
|
175
|
+
"blocked", "blocked"], # judge call + its one retry both unusable
|
|
176
|
+
max_rounds=1,
|
|
177
|
+
)
|
|
178
|
+
assert out.degraded is True
|
|
179
|
+
assert [f.category for f in out.findings] == ["sql_injection"] # the dismissed xss at app.py:5 is dropped
|
|
180
|
+
|
|
181
|
+
|
|
159
182
|
def test_per_role_models_are_used():
|
|
160
183
|
provider = MockProvider(responses=[_finder([]), _challenger(), _judge([])], default="{}")
|
|
161
184
|
AdversarialAuditRunner(
|
|
@@ -59,6 +59,19 @@ urlpatterns = [
|
|
|
59
59
|
]
|
|
60
60
|
'''
|
|
61
61
|
|
|
62
|
+
# include() mounts and class-based views resolve to no handler function: noise
|
|
63
|
+
DJANGO_NOISE = '''
|
|
64
|
+
from django.urls import path, include
|
|
65
|
+
from .views import DashboardView
|
|
66
|
+
|
|
67
|
+
urlpatterns = [
|
|
68
|
+
path("", include("app.urls")),
|
|
69
|
+
path("accounts/", include("allauth.urls")),
|
|
70
|
+
path("profile/", views.profile),
|
|
71
|
+
path("dash/", DashboardView.as_view()),
|
|
72
|
+
]
|
|
73
|
+
'''
|
|
74
|
+
|
|
62
75
|
PLAIN = '''
|
|
63
76
|
def helper(x):
|
|
64
77
|
return x + 1
|
|
@@ -102,6 +115,14 @@ def test_django_path_calls_capture_view_and_route():
|
|
|
102
115
|
}
|
|
103
116
|
|
|
104
117
|
|
|
118
|
+
def test_django_include_and_cbv_with_no_resolvable_function_are_dropped():
|
|
119
|
+
# include() mounts and an unresolved .as_view() carry no handler function;
|
|
120
|
+
# only the plain function-based view survives, no empty `- -` noise entries
|
|
121
|
+
model = _model({"urls.py": DJANGO_NOISE})
|
|
122
|
+
assert _routes(model) == {("http", "django", "profile", "profile/", "")}
|
|
123
|
+
assert all(e.function for e in model.entrypoints)
|
|
124
|
+
|
|
125
|
+
|
|
105
126
|
def test_plain_module_has_no_entrypoints():
|
|
106
127
|
model = _model({"util.py": PLAIN})
|
|
107
128
|
assert model.entrypoints == ()
|
|
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
|