codejury 0.14.5__tar.gz → 0.14.6__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.5 → codejury-0.14.6}/PKG-INFO +1 -1
- {codejury-0.14.5 → codejury-0.14.6}/codejury/repo/model.py +98 -27
- {codejury-0.14.5 → codejury-0.14.6}/codejury.egg-info/PKG-INFO +1 -1
- {codejury-0.14.5 → codejury-0.14.6}/pyproject.toml +1 -1
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_repo_model.py +48 -0
- {codejury-0.14.5 → codejury-0.14.6}/LICENSE +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/README.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/__init__.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/cli.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/agent/full-review.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/agent/security-review-memory.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/entrypoints.yaml +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/SKILL.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/business-logic.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/code-injection.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/command-injection.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/cross-site-request-forgery.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/cross-site-scripting.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/hardcoded-secrets.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/http-response-splitting.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/improper-authentication.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/information-exposure.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/insecure-cryptography.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/insecure-deserialization.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/insecure-direct-object-reference.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/insecure-transport.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/jwt-validation.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/mass-assignment.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/missing-authorization.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/open-redirect.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/path-traversal.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/race-condition.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/replay-attack.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/server-side-request-forgery.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/server-side-template-injection.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/session-fixation.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/sql-injection.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/data/rules/xml-external-entity.md +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/diff/__init__.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/diff/debate.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/diff/debate_prompts.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/diff/engine.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/diff/findings_filter.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/diff/prompts.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/diff/rules.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/diff/runner.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/domain/__init__.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/domain/finding.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/json_parse.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/__init__.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/anthropic.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/base.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/factory.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/litellm.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/mock.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/openai.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/openai_format.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/providers/retry.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/repo/__init__.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/repo/scaffold.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/report.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury/resources.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury.egg-info/SOURCES.txt +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/setup.cfg +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_cli_audit.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_diff_debate.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_diff_engine.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_json_parse.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_litellm_provider.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_openai_format.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_openai_provider.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_repo_scaffold.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_report.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/tests/test_retry_provider.py +0 -0
- {codejury-0.14.5 → codejury-0.14.6}/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.6
|
|
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
|
|
@@ -80,31 +80,32 @@ def build_repo_model_from_dir(root: str | Path, *, signatures: _Signatures | Non
|
|
|
80
80
|
def build_repo_model(root: str | Path, files: dict[str, str], *, signatures: _Signatures | None = None) -> RepoModel:
|
|
81
81
|
"""Build a RepoModel from {path: content}. Files that do not parse are skipped."""
|
|
82
82
|
sigs = signatures or load_entrypoint_signatures()
|
|
83
|
-
|
|
83
|
+
asts: dict[str, ast.Module] = {}
|
|
84
84
|
for path, content in sorted(files.items()):
|
|
85
85
|
try:
|
|
86
|
-
|
|
86
|
+
asts[path] = ast.parse(content)
|
|
87
87
|
except SyntaxError:
|
|
88
|
-
continue
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
continue # unparseable file is skipped, not fatal
|
|
89
|
+
entrypoints: list[Entrypoint] = []
|
|
90
|
+
for path, tree in asts.items():
|
|
91
|
+
entrypoints.extend(_decorator_entrypoints_in(path, tree, sigs.decorators))
|
|
92
|
+
# route-call entrypoints (Django path()): resolved across files so an
|
|
93
|
+
# include() mount contributes its sub-routes under the mount's prefix
|
|
94
|
+
entrypoints.extend(_route_call_entrypoints(asts, sigs.calls))
|
|
95
|
+
# an entrypoint must name a reviewable handler function; drop entries with no
|
|
96
|
+
# resolvable function (an unresolved view) so the seeded inventory stays clean
|
|
97
|
+
entrypoints = [e for e in entrypoints if e.function]
|
|
93
98
|
return RepoModel(root=str(root), files=tuple(sorted(files)), entrypoints=tuple(entrypoints))
|
|
94
99
|
|
|
95
100
|
|
|
96
|
-
def
|
|
101
|
+
def _decorator_entrypoints_in(path: str, tree: ast.Module, deco_sigs) -> list[Entrypoint]:
|
|
97
102
|
out: list[Entrypoint] = []
|
|
98
103
|
for node in ast.walk(tree):
|
|
99
104
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
100
105
|
for dec in node.decorator_list:
|
|
101
|
-
ep = _decorator_entrypoint(path, node, dec,
|
|
106
|
+
ep = _decorator_entrypoint(path, node, dec, deco_sigs)
|
|
102
107
|
if ep is not None:
|
|
103
108
|
out.append(ep)
|
|
104
|
-
elif isinstance(node, ast.Call):
|
|
105
|
-
ep = _call_entrypoint(path, node, sigs.calls)
|
|
106
|
-
if ep is not None:
|
|
107
|
-
out.append(ep)
|
|
108
109
|
return out
|
|
109
110
|
|
|
110
111
|
|
|
@@ -136,21 +137,91 @@ def _decorator_entrypoint(path, func, dec, deco_sigs) -> Entrypoint | None:
|
|
|
136
137
|
)
|
|
137
138
|
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
# names of the call that mounts a sub-urlconf (Django include()); a route call
|
|
141
|
+
# whose view position is one of these recurses into the included module
|
|
142
|
+
_INCLUDE_NAMES = frozenset({"include"})
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _call_name(call: ast.Call) -> str | None:
|
|
146
|
+
f = call.func
|
|
147
|
+
if isinstance(f, ast.Attribute):
|
|
148
|
+
return f.attr
|
|
149
|
+
if isinstance(f, ast.Name):
|
|
150
|
+
return f.id
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _route_calls(tree: ast.Module, names: set[str]) -> list[ast.Call]:
|
|
155
|
+
return [n for n in ast.walk(tree) if isinstance(n, ast.Call) and _call_name(n) in names]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _include_module(call: ast.Call) -> str | None:
|
|
159
|
+
"""If this route call mounts a sub-urlconf via include('a.b.urls'), return the
|
|
160
|
+
dotted module; else None. Handles include('mod') and include(('mod', 'ns'))."""
|
|
161
|
+
if len(call.args) < 2 or not isinstance(call.args[1], ast.Call):
|
|
145
162
|
return None
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
163
|
+
inc = call.args[1]
|
|
164
|
+
if _call_name(inc) not in _INCLUDE_NAMES or not inc.args:
|
|
165
|
+
return None
|
|
166
|
+
a = inc.args[0]
|
|
167
|
+
if isinstance(a, ast.Constant) and isinstance(a.value, str):
|
|
168
|
+
return a.value
|
|
169
|
+
if isinstance(a, ast.Tuple) and a.elts and isinstance(a.elts[0], ast.Constant) and isinstance(a.elts[0].value, str):
|
|
170
|
+
return a.elts[0].value
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _module_to_file(module: str, files: set[str]) -> str | None:
|
|
175
|
+
"""Resolve a dotted module ('app.urls') to a file in the repo, by exact path or
|
|
176
|
+
a suffix match (the dotted path may be rooted above the scanned tree)."""
|
|
177
|
+
candidate = module.replace(".", "/") + ".py"
|
|
178
|
+
if candidate in files:
|
|
179
|
+
return candidate
|
|
180
|
+
matches = sorted(f for f in files if f.endswith("/" + candidate))
|
|
181
|
+
return matches[0] if matches else None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _route_call_entrypoints(asts: dict[str, ast.Module], call_sigs) -> list[Entrypoint]:
|
|
185
|
+
if not call_sigs:
|
|
186
|
+
return []
|
|
187
|
+
names = {n for s in call_sigs for n in s["names"]}
|
|
188
|
+
sig_by_name = {n: s for s in call_sigs for n in s["names"]}
|
|
189
|
+
files = set(asts)
|
|
190
|
+
|
|
191
|
+
# files mounted by another file's include() are not roots; they are emitted
|
|
192
|
+
# only through the include chain, with the mount prefix, never standalone
|
|
193
|
+
mounted: set[str] = set()
|
|
194
|
+
for tree in asts.values():
|
|
195
|
+
for call in _route_calls(tree, names):
|
|
196
|
+
mod = _include_module(call)
|
|
197
|
+
target = _module_to_file(mod, files) if mod else None
|
|
198
|
+
if target:
|
|
199
|
+
mounted.add(target)
|
|
200
|
+
|
|
201
|
+
out: list[Entrypoint] = []
|
|
202
|
+
for path in sorted(asts):
|
|
203
|
+
if path not in mounted:
|
|
204
|
+
_emit_routes(path, "", asts, files, names, sig_by_name, {path}, out)
|
|
205
|
+
return out
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _emit_routes(path, prefix, asts, files, names, sig_by_name, visited, out) -> None:
|
|
209
|
+
for call in _route_calls(asts[path], names):
|
|
210
|
+
seg = _first_str_arg(call)
|
|
211
|
+
mod = _include_module(call)
|
|
212
|
+
if mod:
|
|
213
|
+
target = _module_to_file(mod, files)
|
|
214
|
+
if target and target not in visited:
|
|
215
|
+
_emit_routes(target, prefix + seg, asts, files, names, sig_by_name, visited | {target}, out)
|
|
216
|
+
continue
|
|
217
|
+
view = _view_name(call)
|
|
218
|
+
if not view:
|
|
219
|
+
continue
|
|
220
|
+
sig = sig_by_name[_call_name(call)]
|
|
221
|
+
out.append(Entrypoint(
|
|
222
|
+
file=path, line=call.lineno, function=view,
|
|
223
|
+
kind=sig["kind"], framework=sig["framework"], route=prefix + seg,
|
|
224
|
+
))
|
|
154
225
|
|
|
155
226
|
|
|
156
227
|
def _first_str_arg(call: ast.Call) -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.6
|
|
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.6"
|
|
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"
|
|
@@ -115,6 +115,54 @@ def test_django_path_calls_capture_view_and_route():
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
DJANGO_ROOT = '''
|
|
119
|
+
from django.urls import path, include
|
|
120
|
+
urlpatterns = [
|
|
121
|
+
path("", include("introduction.urls")),
|
|
122
|
+
path("api/", include("api.urls")),
|
|
123
|
+
path("health/", views.health),
|
|
124
|
+
]
|
|
125
|
+
'''
|
|
126
|
+
DJANGO_INTRO = '''
|
|
127
|
+
from django.urls import path
|
|
128
|
+
from . import views
|
|
129
|
+
urlpatterns = [
|
|
130
|
+
path("home/", views.home),
|
|
131
|
+
path("xss", views.xss),
|
|
132
|
+
]
|
|
133
|
+
'''
|
|
134
|
+
DJANGO_API = '''
|
|
135
|
+
from django.urls import path, include
|
|
136
|
+
urlpatterns = [
|
|
137
|
+
path("v1/", include("api.v1.urls")),
|
|
138
|
+
]
|
|
139
|
+
'''
|
|
140
|
+
DJANGO_API_V1 = '''
|
|
141
|
+
from django.urls import path
|
|
142
|
+
urlpatterns = [
|
|
143
|
+
path("users", views.users),
|
|
144
|
+
]
|
|
145
|
+
'''
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_django_include_mounts_subroutes_under_prefix():
|
|
149
|
+
model = _model({
|
|
150
|
+
"urls.py": DJANGO_ROOT,
|
|
151
|
+
"introduction/urls.py": DJANGO_INTRO,
|
|
152
|
+
"api/urls.py": DJANGO_API,
|
|
153
|
+
"api/v1/urls.py": DJANGO_API_V1,
|
|
154
|
+
})
|
|
155
|
+
routes = {(e.function, e.route) for e in model.entrypoints}
|
|
156
|
+
assert routes == {
|
|
157
|
+
("health", "health/"), # direct route on the root urlconf
|
|
158
|
+
("home", "home/"), # included at "" prefix
|
|
159
|
+
("xss", "xss"),
|
|
160
|
+
("users", "api/v1/users"), # nested include: "api/" + "v1/" + "users"
|
|
161
|
+
}
|
|
162
|
+
# the included sub-urlconfs are not also emitted standalone (no bare "users")
|
|
163
|
+
assert all(e.route != "users" for e in model.entrypoints)
|
|
164
|
+
|
|
165
|
+
|
|
118
166
|
def test_django_include_and_cbv_with_no_resolvable_function_are_dropped():
|
|
119
167
|
# include() mounts and an unresolved .as_view() carry no handler function;
|
|
120
168
|
# only the plain function-based view survives, no empty `- -` noise entries
|
|
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
|