codejury 0.14.7__tar.gz → 0.16.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.14.7 → codejury-0.16.0}/PKG-INFO +12 -11
- {codejury-0.14.7 → codejury-0.16.0}/README.md +11 -10
- {codejury-0.14.7 → codejury-0.16.0}/codejury/cli.py +3 -1
- codejury-0.14.7/codejury/data/agent/full-review.md → codejury-0.16.0/codejury/data/agent/repo-review.md +17 -14
- codejury-0.16.0/codejury/data/frameworks/django.md +30 -0
- codejury-0.16.0/codejury/data/languages/python.md +24 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/SKILL.md +1 -1
- codejury-0.16.0/codejury/repo/guides.py +79 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/repo/scaffold.py +48 -7
- {codejury-0.14.7 → codejury-0.16.0}/codejury/resources.py +4 -2
- {codejury-0.14.7 → codejury-0.16.0}/codejury.egg-info/PKG-INFO +12 -11
- {codejury-0.14.7 → codejury-0.16.0}/codejury.egg-info/SOURCES.txt +5 -1
- {codejury-0.14.7 → codejury-0.16.0}/pyproject.toml +1 -1
- codejury-0.16.0/tests/test_guides.py +34 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_repo_scaffold.py +24 -4
- {codejury-0.14.7 → codejury-0.16.0}/LICENSE +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/__init__.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/agent/security-review-memory.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/entrypoints.yaml +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/business-logic.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/code-injection.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/command-injection.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/cross-site-request-forgery.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/cross-site-scripting.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/hardcoded-secrets.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/http-response-splitting.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/improper-authentication.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/information-exposure.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/insecure-cryptography.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/insecure-deserialization.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/insecure-direct-object-reference.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/insecure-transport.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/jwt-validation.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/mass-assignment.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/missing-authorization.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/open-redirect.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/path-traversal.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/race-condition.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/replay-attack.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/server-side-request-forgery.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/server-side-template-injection.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/session-fixation.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/sql-injection.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/data/rules/xml-external-entity.md +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/diff/__init__.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/diff/debate.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/diff/debate_prompts.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/diff/engine.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/diff/findings_filter.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/diff/prompts.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/diff/rules.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/diff/runner.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/domain/__init__.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/domain/finding.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/json_parse.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/__init__.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/anthropic.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/base.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/factory.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/litellm.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/mock.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/openai.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/openai_format.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/providers/retry.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/repo/__init__.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/repo/model.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury/report.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/setup.cfg +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_cli_audit.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_diff_debate.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_diff_engine.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_json_parse.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_litellm_provider.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_openai_format.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_openai_provider.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_repo_model.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_report.py +0 -0
- {codejury-0.14.7 → codejury-0.16.0}/tests/test_retry_provider.py +0 -0
- {codejury-0.14.7 → codejury-0.16.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.16.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
|
|
@@ -38,10 +38,10 @@ AI code security review, in two paths matched to their nature:
|
|
|
38
38
|
Finder/Challenger/Judge pass that trades roughly 3x the cost for extra recall
|
|
39
39
|
on subtle, cross-cutting flaws.
|
|
40
40
|
- **Whole-repo review** (agent-driven): a methodology an interactive agent
|
|
41
|
-
(Claude Code, Codex) runs to
|
|
42
|
-
verify issues with a real PoC, and iterate over rounds with
|
|
43
|
-
memory. Too large for a single LLM call, so codejury ships the
|
|
44
|
-
scaffolds the workspace rather than running a pipeline.
|
|
41
|
+
(Claude Code, Codex) runs to map a codebase's attack surface, trace inputs to
|
|
42
|
+
sinks across files, verify issues with a real PoC, and iterate over rounds with
|
|
43
|
+
a persistent memory. Too large for a single LLM call, so codejury ships the
|
|
44
|
+
methodology and scaffolds the workspace rather than running a pipeline.
|
|
45
45
|
|
|
46
46
|
Security knowledge lives in **rich rules** (`codejury/data/rules/*.md`, with
|
|
47
47
|
per-language vulnerable/secure examples), injected into the audit prompt, not
|
|
@@ -113,12 +113,13 @@ when the gate fails, so findings always show up on the PR.
|
|
|
113
113
|
codejury review repo /path/to/your/repo
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
This scaffolds a review workspace (`
|
|
117
|
-
`security-review-memory.md`), seeds the
|
|
118
|
-
and prints the methodology. Run it with an interactive
|
|
119
|
-
methodology and the rules,
|
|
120
|
-
high-confidence issues with a PoC, and
|
|
121
|
-
positives along the way. Nothing runs
|
|
116
|
+
This scaffolds a review workspace (`entrypoints/`, `issues/`, `analysis/`, and a
|
|
117
|
+
`security-review-memory.md`), seeds the entrypoint inventory from a
|
|
118
|
+
deterministic scan, and prints the methodology. Run it with an interactive
|
|
119
|
+
agent: it reads the methodology and the rules, maps the attack surface, traces
|
|
120
|
+
inputs to sinks across files, records high-confidence issues with a PoC, and
|
|
121
|
+
asks you to confirm credentials or false positives along the way. Nothing runs
|
|
122
|
+
against production.
|
|
122
123
|
|
|
123
124
|
## Findings
|
|
124
125
|
|
|
@@ -7,10 +7,10 @@ AI code security review, in two paths matched to their nature:
|
|
|
7
7
|
Finder/Challenger/Judge pass that trades roughly 3x the cost for extra recall
|
|
8
8
|
on subtle, cross-cutting flaws.
|
|
9
9
|
- **Whole-repo review** (agent-driven): a methodology an interactive agent
|
|
10
|
-
(Claude Code, Codex) runs to
|
|
11
|
-
verify issues with a real PoC, and iterate over rounds with
|
|
12
|
-
memory. Too large for a single LLM call, so codejury ships the
|
|
13
|
-
scaffolds the workspace rather than running a pipeline.
|
|
10
|
+
(Claude Code, Codex) runs to map a codebase's attack surface, trace inputs to
|
|
11
|
+
sinks across files, verify issues with a real PoC, and iterate over rounds with
|
|
12
|
+
a persistent memory. Too large for a single LLM call, so codejury ships the
|
|
13
|
+
methodology and scaffolds the workspace rather than running a pipeline.
|
|
14
14
|
|
|
15
15
|
Security knowledge lives in **rich rules** (`codejury/data/rules/*.md`, with
|
|
16
16
|
per-language vulnerable/secure examples), injected into the audit prompt, not
|
|
@@ -82,12 +82,13 @@ when the gate fails, so findings always show up on the PR.
|
|
|
82
82
|
codejury review repo /path/to/your/repo
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
This scaffolds a review workspace (`
|
|
86
|
-
`security-review-memory.md`), seeds the
|
|
87
|
-
and prints the methodology. Run it with an interactive
|
|
88
|
-
methodology and the rules,
|
|
89
|
-
high-confidence issues with a PoC, and
|
|
90
|
-
positives along the way. Nothing runs
|
|
85
|
+
This scaffolds a review workspace (`entrypoints/`, `issues/`, `analysis/`, and a
|
|
86
|
+
`security-review-memory.md`), seeds the entrypoint inventory from a
|
|
87
|
+
deterministic scan, and prints the methodology. Run it with an interactive
|
|
88
|
+
agent: it reads the methodology and the rules, maps the attack surface, traces
|
|
89
|
+
inputs to sinks across files, records high-confidence issues with a PoC, and
|
|
90
|
+
asks you to confirm credentials or false positives along the way. Nothing runs
|
|
91
|
+
against production.
|
|
91
92
|
|
|
92
93
|
## Findings
|
|
93
94
|
|
|
@@ -132,7 +132,9 @@ def _dispatch(args, parser) -> int:
|
|
|
132
132
|
if args.command == "review" and scope == "repo":
|
|
133
133
|
res = scaffold(args.directory, args.workspace)
|
|
134
134
|
print(f"Workspace: {res.workspace}", file=sys.stderr)
|
|
135
|
-
print(f"Seeded {res.entrypoints} entrypoints into {res.workspace}/
|
|
135
|
+
print(f"Seeded {res.entrypoints} entrypoints into {res.workspace}/entrypoints/_entrypoints.md", file=sys.stderr)
|
|
136
|
+
if res.guides:
|
|
137
|
+
print(f"Detected stack ({', '.join(res.guides)}); notes in {res.workspace}/_stack.md", file=sys.stderr)
|
|
136
138
|
print(f"Memory: {res.memory_path}", file=sys.stderr)
|
|
137
139
|
print("\nRun this review with an interactive agent (Claude Code / Codex) using the methodology below.\n")
|
|
138
140
|
print(res.methodology)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Repo Security Review — Agent Methodology
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Code, Codex, etc.), not a one-shot LLM call. It maps the
|
|
5
|
-
inputs to sinks across files, verifies issues with a real
|
|
6
|
-
iterates over multiple rounds with a persistent memory. One round is
|
|
7
|
-
30 minutes; run as many rounds as needed.
|
|
3
|
+
The `review repo` path: a whole-repository security audit, run by an interactive
|
|
4
|
+
coding agent (Claude Code, Codex, etc.), not a one-shot LLM call. It maps the
|
|
5
|
+
attack surface, traces inputs to sinks across files, verifies issues with a real
|
|
6
|
+
PoC, and iterates over multiple rounds with a persistent memory. One round is
|
|
7
|
+
roughly 30 minutes; run as many rounds as needed.
|
|
8
8
|
|
|
9
9
|
Target repository: the directory you were given.
|
|
10
|
-
Workspace: `<workspace>/<project>/` (created for you), holding `
|
|
10
|
+
Workspace: `<workspace>/<project>/` (created for you), holding `entrypoints/`,
|
|
11
11
|
`issues/`, `analysis/`, and `security-review-memory.md`.
|
|
12
12
|
|
|
13
13
|
---
|
|
@@ -18,17 +18,20 @@ Workspace: `<workspace>/<project>/` (created for you), holding `api/`,
|
|
|
18
18
|
- skip every pattern under "Confirmed false positives";
|
|
19
19
|
- do not re-report anything under "Fixed";
|
|
20
20
|
- weight the files under "High-risk areas" more heavily.
|
|
21
|
-
2. Read `
|
|
22
|
-
*starting* map of the attack surface. It lists HTTP routes and CLI
|
|
23
|
-
only,
|
|
24
|
-
3. Read
|
|
21
|
+
2. Read `entrypoints/_entrypoints.md` (seeded for you from a deterministic AST
|
|
22
|
+
scan) as a *starting* map of the attack surface. It lists HTTP routes and CLI
|
|
23
|
+
commands only, a subset, not the whole surface (see "Map the attack surface").
|
|
24
|
+
3. Read `_stack.md` (seeded): the detected languages and frameworks and review
|
|
25
|
+
notes for them, so you know where this stack's entrypoints, sinks, and auth
|
|
26
|
+
checks live. If it matched nothing, lean on your own knowledge of the stack.
|
|
27
|
+
4. Read the relevant rule files under the shipped `rules/` for the target's stack
|
|
25
28
|
(sql-injection, idor, ssrf, authentication-jwt, insecure-deserialization, ...).
|
|
26
29
|
|
|
27
30
|
## Map the attack surface
|
|
28
31
|
|
|
29
32
|
The seeded inventory lists HTTP routes and CLI commands only. Before analysing,
|
|
30
33
|
complete the surface: untrusted input enters at more than HTTP. Enumerate every
|
|
31
|
-
source the attacker can influence and add it to `
|
|
34
|
+
source the attacker can influence and add it to `entrypoints/`:
|
|
32
35
|
|
|
33
36
|
- HTTP routes, GraphQL resolvers, gRPC / RPC handlers, WebSocket handlers;
|
|
34
37
|
- CLI commands, scheduled jobs / cron, queue and topic consumers, webhooks and
|
|
@@ -40,8 +43,8 @@ source the attacker can influence and add it to `api/`:
|
|
|
40
43
|
inter-service calls.
|
|
41
44
|
|
|
42
45
|
`pickle.loads(cookie)` and `yaml.load(upload)` are entrypoints just as much as a
|
|
43
|
-
route is. Record the inventory in `
|
|
44
|
-
method + review status ✅/⚠️/❌).
|
|
46
|
+
route is. Record the inventory in `entrypoints/` (one file per module: source +
|
|
47
|
+
auth method + review status ✅/⚠️/❌).
|
|
45
48
|
|
|
46
49
|
## Analyse each source
|
|
47
50
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: django
|
|
3
|
+
title: Django
|
|
4
|
+
kind: framework
|
|
5
|
+
detect:
|
|
6
|
+
files: ["*urls.py", "manage.py", "*settings.py"]
|
|
7
|
+
manifest: ["django"]
|
|
8
|
+
imports: ["from django", "import django"]
|
|
9
|
+
---
|
|
10
|
+
# Django — review notes
|
|
11
|
+
|
|
12
|
+
## Entrypoints
|
|
13
|
+
- Routes live in `urls.py`: `path()` / `re_path()` map a URL to a view.
|
|
14
|
+
`include('app.urls')` mounts a sub-urlconf and the URL prefix accumulates.
|
|
15
|
+
Class-based views are wired as `SomeView.as_view()`.
|
|
16
|
+
- Also: Django REST Framework viewsets/routers/serializers, management commands,
|
|
17
|
+
signals, and middleware.
|
|
18
|
+
|
|
19
|
+
## Authorization / IDOR
|
|
20
|
+
- Auth is enforced by decorators (`@login_required`), DRF permission classes, or
|
|
21
|
+
middleware. Note where it is and where it is missing.
|
|
22
|
+
- Classic IDOR shape: `Model.objects.get(pk=<user input>)` (or `filter(id=...)`)
|
|
23
|
+
with no owner/tenant scoping, then returned to the caller. Inspect every object
|
|
24
|
+
fetch keyed by a user-supplied id.
|
|
25
|
+
|
|
26
|
+
## Common sinks / gotchas
|
|
27
|
+
- SQL: `.raw()`, `.extra()`, `RawSQL`, or string-built SQL via `connection.cursor()`.
|
|
28
|
+
- Templates: `mark_safe`, `|safe`, `format_html` on unescaped user input; autoescape off.
|
|
29
|
+
- `pickle` / `yaml.load` on a cookie or upload; `DEBUG=True` leaking internals;
|
|
30
|
+
a hardcoded `SECRET_KEY`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: python
|
|
3
|
+
title: Python
|
|
4
|
+
kind: language
|
|
5
|
+
detect:
|
|
6
|
+
files: ["*.py"]
|
|
7
|
+
manifest: []
|
|
8
|
+
imports: []
|
|
9
|
+
---
|
|
10
|
+
# Python — review notes
|
|
11
|
+
|
|
12
|
+
Where untrusted input enters (beyond web routes, which the framework guide covers):
|
|
13
|
+
CLI (`argparse`/`click`), scheduled jobs, queue consumers, and any function fed an
|
|
14
|
+
external value. Non-HTTP sources matter as much as routes:
|
|
15
|
+
|
|
16
|
+
- deserialization: `pickle.loads`, `yaml.load` without `SafeLoader`, `marshal`;
|
|
17
|
+
- code execution: `eval`, `exec`, `subprocess(..., shell=True)`, `os.system`;
|
|
18
|
+
- XML/XXE: `lxml`/`xml.etree` parsing attacker XML;
|
|
19
|
+
- filesystem: `open()` / `os.path.join` on a path built from user input;
|
|
20
|
+
- network: `requests.get(user_url)` and friends (SSRF).
|
|
21
|
+
|
|
22
|
+
Common sinks: string-built SQL handed to a DB cursor or ORM `.raw()`/`.extra()`,
|
|
23
|
+
a shell command, `eval`/`exec`, a user-controlled file path, a fetch of a
|
|
24
|
+
user-controlled URL, and template rendering of user input.
|
|
@@ -8,7 +8,7 @@ description: "Application security rules for reviewing code for exploitable vuln
|
|
|
8
8
|
Application-security rules, one file per weakness class under `rules/`, named by
|
|
9
9
|
the specific weakness (CWE-style). Each rule states impact, the markers to hunt
|
|
10
10
|
(`triggers`), and vulnerable-vs-secure examples. The diff-audit engine injects the
|
|
11
|
-
rules relevant to a change into the prompt; the
|
|
11
|
+
rules relevant to a change into the prompt; the repo-review agent reads them for
|
|
12
12
|
the target's stack. A finding's `category` is one of these ids.
|
|
13
13
|
|
|
14
14
|
## Rules by OWASP category
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Language and framework review guides as data.
|
|
2
|
+
|
|
3
|
+
Each `data/languages/*.md` and `data/frameworks/*.md` is a knowledge unit: YAML
|
|
4
|
+
frontmatter declaring how to detect the language or framework in a target repo
|
|
5
|
+
(file-name globs, dependency-manifest substrings, import markers), and a body of
|
|
6
|
+
review guidance (where input enters, common sinks, auth conventions, gotchas).
|
|
7
|
+
|
|
8
|
+
Selection is generic: a guide applies when its detect signals fire on the repo.
|
|
9
|
+
Adding a language or framework is a drop-in file under the right directory, no
|
|
10
|
+
code change, which keeps the unbounded language/framework axis out of code.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import fnmatch
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from codejury.resources import FRAMEWORKS_DIR, LANGUAGES_DIR
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, kw_only=True)
|
|
25
|
+
class Guide:
|
|
26
|
+
id: str
|
|
27
|
+
kind: str # "language" or "framework"
|
|
28
|
+
title: str
|
|
29
|
+
detect_files: tuple[str, ...]
|
|
30
|
+
detect_manifest: tuple[str, ...]
|
|
31
|
+
detect_imports: tuple[str, ...]
|
|
32
|
+
body: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _parse(path: Path, kind: str) -> Guide:
|
|
36
|
+
text = path.read_text(encoding="utf-8")
|
|
37
|
+
meta: dict = {}
|
|
38
|
+
body = text
|
|
39
|
+
if text.startswith("---"):
|
|
40
|
+
parts = text.split("---", 2)
|
|
41
|
+
if len(parts) == 3:
|
|
42
|
+
meta = yaml.safe_load(parts[1]) or {}
|
|
43
|
+
body = parts[2].strip()
|
|
44
|
+
detect = meta.get("detect", {}) or {}
|
|
45
|
+
return Guide(
|
|
46
|
+
id=str(meta.get("id", path.stem)),
|
|
47
|
+
kind=kind,
|
|
48
|
+
title=str(meta.get("title", path.stem)),
|
|
49
|
+
detect_files=tuple(str(f) for f in detect.get("files", [])),
|
|
50
|
+
detect_manifest=tuple(str(m).lower() for m in detect.get("manifest", [])),
|
|
51
|
+
detect_imports=tuple(str(i) for i in detect.get("imports", [])),
|
|
52
|
+
body=body,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def load_guides(languages_dir=LANGUAGES_DIR, frameworks_dir=FRAMEWORKS_DIR) -> list[Guide]:
|
|
57
|
+
out: list[Guide] = []
|
|
58
|
+
for directory, kind in ((languages_dir, "language"), (frameworks_dir, "framework")):
|
|
59
|
+
root = Path(directory)
|
|
60
|
+
if root.is_dir():
|
|
61
|
+
out += [_parse(p, kind) for p in sorted(root.glob("*.md")) if p.name != "SKILL.md"]
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _matches(guide: Guide, files: list[str], manifest: str) -> bool:
|
|
66
|
+
if any(fnmatch.fnmatch(f, pat) for pat in guide.detect_files for f in files):
|
|
67
|
+
return True
|
|
68
|
+
if any(m in manifest for m in guide.detect_manifest):
|
|
69
|
+
return True
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def select_guides(files, *, manifest_text: str = "", guides: list[Guide] | None = None) -> list[Guide]:
|
|
74
|
+
"""The guides whose detect signals fire on the repo (its file paths and the
|
|
75
|
+
concatenated text of its dependency manifests), languages first then frameworks."""
|
|
76
|
+
pool = load_guides() if guides is None else guides
|
|
77
|
+
file_list = list(files)
|
|
78
|
+
manifest = manifest_text.lower()
|
|
79
|
+
return [g for g in pool if _matches(g, file_list, manifest)]
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
The `review repo` path. Whole-repo review is too large for a single LLM call, so
|
|
4
4
|
codejury does not run it as a pipeline. Instead it scaffolds a workspace for an
|
|
5
5
|
interactive agent (Claude Code, Codex) and hands over the methodology: it creates
|
|
6
|
-
the
|
|
7
|
-
|
|
8
|
-
text to print.
|
|
6
|
+
the entrypoints/issues/analysis directories, copies the review-memory template,
|
|
7
|
+
seeds the entrypoint inventory from a deterministic RepoModel scan, and returns
|
|
8
|
+
the methodology text to print.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
@@ -13,12 +13,19 @@ from __future__ import annotations
|
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
+
from codejury.repo.guides import Guide, select_guides
|
|
16
17
|
from codejury.repo.model import build_repo_model_from_dir
|
|
17
18
|
from codejury.resources import AGENT_DIR
|
|
18
19
|
|
|
19
|
-
_METHODOLOGY = AGENT_DIR / "
|
|
20
|
+
_METHODOLOGY = AGENT_DIR / "repo-review.md"
|
|
20
21
|
_MEMORY_TEMPLATE = AGENT_DIR / "security-review-memory.md"
|
|
21
22
|
|
|
23
|
+
# top-level dependency manifests scanned to detect the stack (content, not names)
|
|
24
|
+
_MANIFESTS = (
|
|
25
|
+
"requirements.txt", "requirements-dev.txt", "pyproject.toml", "setup.py", "Pipfile",
|
|
26
|
+
"package.json", "go.mod", "Gemfile", "pom.xml", "build.gradle", "Cargo.toml", "composer.json",
|
|
27
|
+
)
|
|
28
|
+
|
|
22
29
|
|
|
23
30
|
@dataclass(frozen=True, kw_only=True)
|
|
24
31
|
class ScaffoldResult:
|
|
@@ -27,13 +34,42 @@ class ScaffoldResult:
|
|
|
27
34
|
methodology: str
|
|
28
35
|
memory_path: Path
|
|
29
36
|
entrypoints: int
|
|
37
|
+
guides: tuple[str, ...] = ()
|
|
30
38
|
created: list[str] = field(default_factory=list)
|
|
31
39
|
|
|
32
40
|
|
|
41
|
+
def _read_manifests(target: Path) -> str:
|
|
42
|
+
parts: list[str] = []
|
|
43
|
+
for name in _MANIFESTS:
|
|
44
|
+
p = target / name
|
|
45
|
+
try:
|
|
46
|
+
if p.is_file():
|
|
47
|
+
parts.append(p.read_text(encoding="utf-8"))
|
|
48
|
+
except OSError:
|
|
49
|
+
pass
|
|
50
|
+
return "\n".join(parts)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _stack_md(guides: list[Guide]) -> str:
|
|
54
|
+
if not guides:
|
|
55
|
+
return ("# Detected stack — review notes\n\n"
|
|
56
|
+
"(no language or framework guide matched; rely on the methodology and "
|
|
57
|
+
"your own knowledge of the stack)\n")
|
|
58
|
+
langs = [g.id for g in guides if g.kind == "language"]
|
|
59
|
+
fws = [g.id for g in guides if g.kind == "framework"]
|
|
60
|
+
lines = ["# Detected stack — review notes", "",
|
|
61
|
+
f"Languages: {', '.join(langs) or '-'}",
|
|
62
|
+
f"Frameworks: {', '.join(fws) or '-'}", ""]
|
|
63
|
+
for g in guides:
|
|
64
|
+
lines += ["---", "", g.body, ""]
|
|
65
|
+
return "\n".join(lines) + "\n"
|
|
66
|
+
|
|
67
|
+
|
|
33
68
|
def _entrypoints_md(model) -> str:
|
|
34
69
|
http = [e for e in model.entrypoints if e.kind == "http"]
|
|
35
70
|
cli = [e for e in model.entrypoints if e.kind == "cli"]
|
|
36
|
-
lines = ["#
|
|
71
|
+
lines = ["# Entrypoints (seeded from a deterministic scan)", "",
|
|
72
|
+
"HTTP routes and CLI commands only; add non-HTTP sources here too.", "",
|
|
37
73
|
"Status legend: ❌ not reviewed · ⚠️ to deepen · ✅ reviewed", ""]
|
|
38
74
|
if http:
|
|
39
75
|
lines += ["## HTTP routes", ""]
|
|
@@ -53,7 +89,7 @@ def scaffold(target: str | Path, workspace: str | Path) -> ScaffoldResult:
|
|
|
53
89
|
project = target.name
|
|
54
90
|
ws = Path(workspace) / project
|
|
55
91
|
created: list[str] = []
|
|
56
|
-
for sub in ("
|
|
92
|
+
for sub in ("entrypoints", "issues", "analysis"):
|
|
57
93
|
d = ws / sub
|
|
58
94
|
if not d.exists():
|
|
59
95
|
d.mkdir(parents=True, exist_ok=True)
|
|
@@ -66,7 +102,11 @@ def scaffold(target: str | Path, workspace: str | Path) -> ScaffoldResult:
|
|
|
66
102
|
created.append(str(memory_path))
|
|
67
103
|
|
|
68
104
|
model = build_repo_model_from_dir(target)
|
|
69
|
-
(ws / "
|
|
105
|
+
(ws / "entrypoints" / "_entrypoints.md").write_text(_entrypoints_md(model), encoding="utf-8")
|
|
106
|
+
|
|
107
|
+
# detect the stack and seed its review guides (languages + frameworks)
|
|
108
|
+
guides = select_guides(model.files, manifest_text=_read_manifests(target))
|
|
109
|
+
(ws / "_stack.md").write_text(_stack_md(guides), encoding="utf-8")
|
|
70
110
|
|
|
71
111
|
return ScaffoldResult(
|
|
72
112
|
project=project,
|
|
@@ -74,5 +114,6 @@ def scaffold(target: str | Path, workspace: str | Path) -> ScaffoldResult:
|
|
|
74
114
|
methodology=_METHODOLOGY.read_text(encoding="utf-8"),
|
|
75
115
|
memory_path=memory_path,
|
|
76
116
|
entrypoints=len(model.entrypoints),
|
|
117
|
+
guides=tuple(g.id for g in guides),
|
|
77
118
|
created=created,
|
|
78
119
|
)
|
|
@@ -8,6 +8,8 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
_DATA = Path(__file__).resolve().parent / "data"
|
|
10
10
|
|
|
11
|
-
RULES_DIR = _DATA / "rules" #
|
|
12
|
-
|
|
11
|
+
RULES_DIR = _DATA / "rules" # vulnerability-class detection rules (what to find)
|
|
12
|
+
LANGUAGES_DIR = _DATA / "languages" # per-language review guides (how the target works)
|
|
13
|
+
FRAMEWORKS_DIR = _DATA / "frameworks" # per-framework review guides (how the target works)
|
|
14
|
+
AGENT_DIR = _DATA / "agent" # repo-review methodology and memory template
|
|
13
15
|
ENTRYPOINTS_FILE = _DATA / "entrypoints.yaml" # framework signatures for RepoModel
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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
|
|
@@ -38,10 +38,10 @@ AI code security review, in two paths matched to their nature:
|
|
|
38
38
|
Finder/Challenger/Judge pass that trades roughly 3x the cost for extra recall
|
|
39
39
|
on subtle, cross-cutting flaws.
|
|
40
40
|
- **Whole-repo review** (agent-driven): a methodology an interactive agent
|
|
41
|
-
(Claude Code, Codex) runs to
|
|
42
|
-
verify issues with a real PoC, and iterate over rounds with
|
|
43
|
-
memory. Too large for a single LLM call, so codejury ships the
|
|
44
|
-
scaffolds the workspace rather than running a pipeline.
|
|
41
|
+
(Claude Code, Codex) runs to map a codebase's attack surface, trace inputs to
|
|
42
|
+
sinks across files, verify issues with a real PoC, and iterate over rounds with
|
|
43
|
+
a persistent memory. Too large for a single LLM call, so codejury ships the
|
|
44
|
+
methodology and scaffolds the workspace rather than running a pipeline.
|
|
45
45
|
|
|
46
46
|
Security knowledge lives in **rich rules** (`codejury/data/rules/*.md`, with
|
|
47
47
|
per-language vulnerable/secure examples), injected into the audit prompt, not
|
|
@@ -113,12 +113,13 @@ when the gate fails, so findings always show up on the PR.
|
|
|
113
113
|
codejury review repo /path/to/your/repo
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
This scaffolds a review workspace (`
|
|
117
|
-
`security-review-memory.md`), seeds the
|
|
118
|
-
and prints the methodology. Run it with an interactive
|
|
119
|
-
methodology and the rules,
|
|
120
|
-
high-confidence issues with a PoC, and
|
|
121
|
-
positives along the way. Nothing runs
|
|
116
|
+
This scaffolds a review workspace (`entrypoints/`, `issues/`, `analysis/`, and a
|
|
117
|
+
`security-review-memory.md`), seeds the entrypoint inventory from a
|
|
118
|
+
deterministic scan, and prints the methodology. Run it with an interactive
|
|
119
|
+
agent: it reads the methodology and the rules, maps the attack surface, traces
|
|
120
|
+
inputs to sinks across files, records high-confidence issues with a PoC, and
|
|
121
|
+
asks you to confirm credentials or false positives along the way. Nothing runs
|
|
122
|
+
against production.
|
|
122
123
|
|
|
123
124
|
## Findings
|
|
124
125
|
|
|
@@ -13,8 +13,10 @@ codejury.egg-info/entry_points.txt
|
|
|
13
13
|
codejury.egg-info/requires.txt
|
|
14
14
|
codejury.egg-info/top_level.txt
|
|
15
15
|
codejury/data/entrypoints.yaml
|
|
16
|
-
codejury/data/agent/
|
|
16
|
+
codejury/data/agent/repo-review.md
|
|
17
17
|
codejury/data/agent/security-review-memory.md
|
|
18
|
+
codejury/data/frameworks/django.md
|
|
19
|
+
codejury/data/languages/python.md
|
|
18
20
|
codejury/data/rules/SKILL.md
|
|
19
21
|
codejury/data/rules/business-logic.md
|
|
20
22
|
codejury/data/rules/code-injection.md
|
|
@@ -61,12 +63,14 @@ codejury/providers/openai.py
|
|
|
61
63
|
codejury/providers/openai_format.py
|
|
62
64
|
codejury/providers/retry.py
|
|
63
65
|
codejury/repo/__init__.py
|
|
66
|
+
codejury/repo/guides.py
|
|
64
67
|
codejury/repo/model.py
|
|
65
68
|
codejury/repo/scaffold.py
|
|
66
69
|
tests/test_anthropic_provider.py
|
|
67
70
|
tests/test_cli_audit.py
|
|
68
71
|
tests/test_diff_debate.py
|
|
69
72
|
tests/test_diff_engine.py
|
|
73
|
+
tests/test_guides.py
|
|
70
74
|
tests/test_json_parse.py
|
|
71
75
|
tests/test_litellm_provider.py
|
|
72
76
|
tests/test_openai_format.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codejury"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.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"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Language/framework review guides load and are selected by detection signals
|
|
2
|
+
(file globs + dependency-manifest substrings), so adding one is a drop-in file."""
|
|
3
|
+
|
|
4
|
+
from codejury.repo.guides import Guide, load_guides, select_guides
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_shipped_guides_load():
|
|
8
|
+
by_id = {g.id: g for g in load_guides()}
|
|
9
|
+
assert {"python", "django"} <= set(by_id)
|
|
10
|
+
assert by_id["python"].kind == "language"
|
|
11
|
+
assert by_id["django"].kind == "framework"
|
|
12
|
+
assert "IDOR" in by_id["django"].body or "idor" in by_id["django"].body.lower()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_select_by_file_glob():
|
|
16
|
+
matched = {g.id for g in select_guides(["app/urls.py", "app/views.py", "manage.py"])}
|
|
17
|
+
assert "python" in matched # *.py
|
|
18
|
+
assert "django" in matched # *urls.py / manage.py
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_select_by_manifest_substring():
|
|
22
|
+
matched = {g.id for g in select_guides(["main.py"], manifest_text="Django==4.2\nrequests\n")}
|
|
23
|
+
assert "django" in matched and "python" in matched
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_no_signal_no_match():
|
|
27
|
+
assert select_guides(["index.html", "style.css"]) == [] # no .py, no django manifest
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_select_respects_injected_pool():
|
|
31
|
+
only = [Guide(id="x", kind="framework", title="X", detect_files=("*.xyz",),
|
|
32
|
+
detect_manifest=(), detect_imports=(), body="b")]
|
|
33
|
+
assert [g.id for g in select_guides(["a.xyz"], guides=only)] == ["x"]
|
|
34
|
+
assert select_guides(["a.py"], guides=only) == []
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""RW-4: the
|
|
1
|
+
"""RW-4: the repo-review scaffold sets up the agent workspace (entrypoints/issues/
|
|
2
2
|
analysis + memory + seeded entrypoints) and returns the methodology. It does not
|
|
3
3
|
run an LLM pipeline."""
|
|
4
4
|
|
|
@@ -32,7 +32,7 @@ def test_scaffold_creates_workspace(tmp_path):
|
|
|
32
32
|
|
|
33
33
|
assert res.project == "myservice"
|
|
34
34
|
assert res.workspace == ws_root / "myservice"
|
|
35
|
-
for sub in ("
|
|
35
|
+
for sub in ("entrypoints", "issues", "analysis"):
|
|
36
36
|
assert (res.workspace / sub).is_dir()
|
|
37
37
|
assert res.memory_path.is_file()
|
|
38
38
|
assert "Security Review Memory" in res.memory_path.read_text()
|
|
@@ -42,11 +42,31 @@ def test_scaffold_creates_workspace(tmp_path):
|
|
|
42
42
|
def test_scaffold_seeds_entrypoints_from_repomodel(tmp_path):
|
|
43
43
|
res = scaffold(_target(tmp_path), tmp_path / "work")
|
|
44
44
|
assert res.entrypoints == 2
|
|
45
|
-
seeded = (res.workspace / "
|
|
45
|
+
seeded = (res.workspace / "entrypoints" / "_entrypoints.md").read_text()
|
|
46
46
|
assert "/users" in seeded and "/admin/users/<uid>" in seeded
|
|
47
47
|
assert "list_users" in seeded and "❌" in seeded
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def test_scaffold_seeds_stack_guides(tmp_path):
|
|
51
|
+
# the Flask target is Python; the python language guide should be detected and
|
|
52
|
+
# its notes written to _stack.md
|
|
53
|
+
res = scaffold(_target(tmp_path), tmp_path / "work")
|
|
54
|
+
assert "python" in res.guides
|
|
55
|
+
stack = (res.workspace / "_stack.md").read_text()
|
|
56
|
+
assert "python" in stack.lower()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_scaffold_detects_framework_from_files_and_manifest(tmp_path):
|
|
60
|
+
d = tmp_path / "dj"
|
|
61
|
+
d.mkdir()
|
|
62
|
+
(d / "manage.py").write_text("import django\n")
|
|
63
|
+
(d / "urls.py").write_text("from django.urls import path\nurlpatterns = []\n")
|
|
64
|
+
(d / "requirements.txt").write_text("Django==4.2\n")
|
|
65
|
+
res = scaffold(d, tmp_path / "work")
|
|
66
|
+
assert "django" in res.guides
|
|
67
|
+
assert "Django" in (res.workspace / "_stack.md").read_text()
|
|
68
|
+
|
|
69
|
+
|
|
50
70
|
def test_methodology_is_returned(tmp_path):
|
|
51
71
|
# the methodology text ships and is handed back for the agent to follow
|
|
52
72
|
res = scaffold(_target(tmp_path), tmp_path / "work")
|
|
@@ -71,4 +91,4 @@ def test_no_python_entrypoints_still_scaffolds(tmp_path):
|
|
|
71
91
|
(d / "notes.txt").write_text("hi")
|
|
72
92
|
res = scaffold(d, tmp_path / "work")
|
|
73
93
|
assert res.entrypoints == 0
|
|
74
|
-
assert "enumerate them manually" in (res.workspace / "
|
|
94
|
+
assert "enumerate them manually" in (res.workspace / "entrypoints" / "_entrypoints.md").read_text()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|