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.
Files changed (80) hide show
  1. {codejury-0.14.2 → codejury-0.14.4}/PKG-INFO +1 -1
  2. {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/debate.py +34 -9
  3. {codejury-0.14.2 → codejury-0.14.4}/codejury/repo/model.py +4 -1
  4. {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/PKG-INFO +1 -1
  5. {codejury-0.14.2 → codejury-0.14.4}/pyproject.toml +1 -1
  6. {codejury-0.14.2 → codejury-0.14.4}/tests/test_diff_debate.py +23 -0
  7. {codejury-0.14.2 → codejury-0.14.4}/tests/test_repo_model.py +21 -0
  8. {codejury-0.14.2 → codejury-0.14.4}/LICENSE +0 -0
  9. {codejury-0.14.2 → codejury-0.14.4}/README.md +0 -0
  10. {codejury-0.14.2 → codejury-0.14.4}/codejury/__init__.py +0 -0
  11. {codejury-0.14.2 → codejury-0.14.4}/codejury/cli.py +0 -0
  12. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/agent/full-review.md +0 -0
  13. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/agent/security-review-memory.md +0 -0
  14. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/entrypoints.yaml +0 -0
  15. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/SKILL.md +0 -0
  16. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/business-logic.md +0 -0
  17. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/code-injection.md +0 -0
  18. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/command-injection.md +0 -0
  19. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/cross-site-request-forgery.md +0 -0
  20. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/cross-site-scripting.md +0 -0
  21. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/hardcoded-secrets.md +0 -0
  22. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/http-response-splitting.md +0 -0
  23. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/improper-authentication.md +0 -0
  24. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/information-exposure.md +0 -0
  25. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/insecure-cryptography.md +0 -0
  26. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/insecure-deserialization.md +0 -0
  27. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/insecure-direct-object-reference.md +0 -0
  28. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/insecure-transport.md +0 -0
  29. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/jwt-validation.md +0 -0
  30. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/mass-assignment.md +0 -0
  31. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/missing-authorization.md +0 -0
  32. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/open-redirect.md +0 -0
  33. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/path-traversal.md +0 -0
  34. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/race-condition.md +0 -0
  35. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/replay-attack.md +0 -0
  36. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/server-side-request-forgery.md +0 -0
  37. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/server-side-template-injection.md +0 -0
  38. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/session-fixation.md +0 -0
  39. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/sql-injection.md +0 -0
  40. {codejury-0.14.2 → codejury-0.14.4}/codejury/data/rules/xml-external-entity.md +0 -0
  41. {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/__init__.py +0 -0
  42. {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/debate_prompts.py +0 -0
  43. {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/engine.py +0 -0
  44. {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/findings_filter.py +0 -0
  45. {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/prompts.py +0 -0
  46. {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/rules.py +0 -0
  47. {codejury-0.14.2 → codejury-0.14.4}/codejury/diff/runner.py +0 -0
  48. {codejury-0.14.2 → codejury-0.14.4}/codejury/domain/__init__.py +0 -0
  49. {codejury-0.14.2 → codejury-0.14.4}/codejury/domain/finding.py +0 -0
  50. {codejury-0.14.2 → codejury-0.14.4}/codejury/json_parse.py +0 -0
  51. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/__init__.py +0 -0
  52. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/anthropic.py +0 -0
  53. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/base.py +0 -0
  54. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/factory.py +0 -0
  55. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/litellm.py +0 -0
  56. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/mock.py +0 -0
  57. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/openai.py +0 -0
  58. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/openai_format.py +0 -0
  59. {codejury-0.14.2 → codejury-0.14.4}/codejury/providers/retry.py +0 -0
  60. {codejury-0.14.2 → codejury-0.14.4}/codejury/repo/__init__.py +0 -0
  61. {codejury-0.14.2 → codejury-0.14.4}/codejury/repo/scaffold.py +0 -0
  62. {codejury-0.14.2 → codejury-0.14.4}/codejury/report.py +0 -0
  63. {codejury-0.14.2 → codejury-0.14.4}/codejury/resources.py +0 -0
  64. {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/SOURCES.txt +0 -0
  65. {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/dependency_links.txt +0 -0
  66. {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/entry_points.txt +0 -0
  67. {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/requires.txt +0 -0
  68. {codejury-0.14.2 → codejury-0.14.4}/codejury.egg-info/top_level.txt +0 -0
  69. {codejury-0.14.2 → codejury-0.14.4}/setup.cfg +0 -0
  70. {codejury-0.14.2 → codejury-0.14.4}/tests/test_anthropic_provider.py +0 -0
  71. {codejury-0.14.2 → codejury-0.14.4}/tests/test_cli_audit.py +0 -0
  72. {codejury-0.14.2 → codejury-0.14.4}/tests/test_diff_engine.py +0 -0
  73. {codejury-0.14.2 → codejury-0.14.4}/tests/test_json_parse.py +0 -0
  74. {codejury-0.14.2 → codejury-0.14.4}/tests/test_litellm_provider.py +0 -0
  75. {codejury-0.14.2 → codejury-0.14.4}/tests/test_openai_format.py +0 -0
  76. {codejury-0.14.2 → codejury-0.14.4}/tests/test_openai_provider.py +0 -0
  77. {codejury-0.14.2 → codejury-0.14.4}/tests/test_repo_scaffold.py +0 -0
  78. {codejury-0.14.2 → codejury-0.14.4}/tests/test_report.py +0 -0
  79. {codejury-0.14.2 → codejury-0.14.4}/tests/test_retry_provider.py +0 -0
  80. {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.2
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
- verdict, judge_ok = self._ask(
120
- JUDGE_SYSTEM,
121
- judge_prompt(diff, finder_findings, rebuttals, new_findings, context=context),
122
- self._judge_model,
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
- # the judge reply was unusable (provider error, blocked request,
126
- # prose): do not let that silently drop the candidate findings;
127
- # fall back to the unjudged finder + challenger set, flagged degraded
128
- fallback = _dedup(finder_findings + new_findings)
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
- entrypoints.extend(_entrypoints_in(path, tree, sigs))
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.2
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.2"
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