codejury 0.16.0__tar.gz → 0.16.1__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.16.0 → codejury-0.16.1}/PKG-INFO +1 -1
- {codejury-0.16.0 → codejury-0.16.1}/codejury/diff/engine.py +3 -1
- {codejury-0.16.0 → codejury-0.16.1}/codejury/diff/prompts.py +3 -1
- {codejury-0.16.0/codejury/repo → codejury-0.16.1/codejury}/guides.py +25 -7
- {codejury-0.16.0 → codejury-0.16.1}/codejury/repo/scaffold.py +2 -2
- {codejury-0.16.0 → codejury-0.16.1}/codejury.egg-info/PKG-INFO +1 -1
- {codejury-0.16.0 → codejury-0.16.1}/codejury.egg-info/SOURCES.txt +1 -1
- {codejury-0.16.0 → codejury-0.16.1}/pyproject.toml +1 -1
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_diff_engine.py +2 -1
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_guides.py +13 -2
- {codejury-0.16.0 → codejury-0.16.1}/LICENSE +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/README.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/__init__.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/cli.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/agent/repo-review.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/agent/security-review-memory.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/entrypoints.yaml +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/frameworks/django.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/languages/python.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/SKILL.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/business-logic.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/code-injection.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/command-injection.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/cross-site-request-forgery.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/cross-site-scripting.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/hardcoded-secrets.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/http-response-splitting.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/improper-authentication.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/information-exposure.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/insecure-cryptography.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/insecure-deserialization.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/insecure-direct-object-reference.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/insecure-transport.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/jwt-validation.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/mass-assignment.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/missing-authorization.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/open-redirect.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/path-traversal.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/race-condition.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/replay-attack.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/server-side-request-forgery.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/server-side-template-injection.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/session-fixation.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/sql-injection.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/data/rules/xml-external-entity.md +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/diff/__init__.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/diff/debate.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/diff/debate_prompts.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/diff/findings_filter.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/diff/rules.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/diff/runner.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/domain/__init__.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/domain/finding.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/json_parse.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/__init__.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/anthropic.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/base.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/factory.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/litellm.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/mock.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/openai.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/openai_format.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/providers/retry.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/repo/__init__.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/repo/model.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/report.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury/resources.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/setup.cfg +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_cli_audit.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_diff_debate.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_json_parse.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_litellm_provider.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_openai_format.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_openai_provider.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_repo_model.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_repo_scaffold.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_report.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_retry_provider.py +0 -0
- {codejury-0.16.0 → codejury-0.16.1}/tests/test_rules.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.1
|
|
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
|
|
@@ -12,6 +12,7 @@ from codejury.diff.prompts import SYSTEM, standard_audit_prompt
|
|
|
12
12
|
from codejury.diff.rules import rules_for_diff
|
|
13
13
|
from codejury.json_parse import extract_json_object
|
|
14
14
|
from codejury.providers.base import Message, Provider
|
|
15
|
+
from codejury.guides import guides_for_diff
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class AuditError(RuntimeError):
|
|
@@ -33,9 +34,10 @@ class AuditRunner:
|
|
|
33
34
|
def run(self, diff: str, *, rules: str = "", context: str = "") -> list[Finding]:
|
|
34
35
|
if not rules:
|
|
35
36
|
rules = rules_for_diff(diff) # inject the rules relevant to this diff
|
|
37
|
+
stack = guides_for_diff(diff) # inject the target's language/framework conventions
|
|
36
38
|
result = self._provider.complete(
|
|
37
39
|
system=SYSTEM,
|
|
38
|
-
messages=[Message(role="user", content=standard_audit_prompt(diff, rules=rules, context=context))],
|
|
40
|
+
messages=[Message(role="user", content=standard_audit_prompt(diff, rules=rules, context=context, stack=stack))],
|
|
39
41
|
model=self._model,
|
|
40
42
|
max_tokens=self._max_tokens,
|
|
41
43
|
)
|
|
@@ -57,7 +57,8 @@ def category_block() -> str:
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def standard_audit_prompt(diff: str, *, rules: str = "", context: str = "") -> str:
|
|
60
|
+
def standard_audit_prompt(diff: str, *, rules: str = "", context: str = "", stack: str = "") -> str:
|
|
61
|
+
stack_block = f"Conventions of the target's language/framework:\n{stack}\n\n" if stack else ""
|
|
61
62
|
rules_block = f"Relevant security rules for reference:\n{rules}\n\n" if rules else ""
|
|
62
63
|
context_block = (
|
|
63
64
|
f"Surrounding code for tracing where values come from (not under review):\n"
|
|
@@ -69,6 +70,7 @@ def standard_audit_prompt(diff: str, *, rules: str = "", context: str = "") -> s
|
|
|
69
70
|
"Review the following code change for security vulnerabilities.\n\n"
|
|
70
71
|
f"{FOCUS}\n{DO_NOT_REPORT}\n"
|
|
71
72
|
f"{category_block()}"
|
|
73
|
+
f"{stack_block}"
|
|
72
74
|
f"{rules_block}"
|
|
73
75
|
f"Code change (unified diff):\n```diff\n{diff}\n```\n\n"
|
|
74
76
|
f"{context_block}"
|
|
@@ -13,6 +13,7 @@ code change, which keeps the unbounded language/framework axis out of code.
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
import fnmatch
|
|
16
|
+
import re
|
|
16
17
|
from dataclasses import dataclass
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
|
|
@@ -20,6 +21,8 @@ import yaml
|
|
|
20
21
|
|
|
21
22
|
from codejury.resources import FRAMEWORKS_DIR, LANGUAGES_DIR
|
|
22
23
|
|
|
24
|
+
_DIFF_PATH = re.compile(r"^(?:\+\+\+ b/|diff --git a/\S+ b/)(\S+)", re.MULTILINE)
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
@dataclass(frozen=True, kw_only=True)
|
|
25
28
|
class Guide:
|
|
@@ -62,18 +65,33 @@ def load_guides(languages_dir=LANGUAGES_DIR, frameworks_dir=FRAMEWORKS_DIR) -> l
|
|
|
62
65
|
return out
|
|
63
66
|
|
|
64
67
|
|
|
65
|
-
def _matches(guide: Guide, files: list[str],
|
|
68
|
+
def _matches(guide: Guide, files: list[str], text: str) -> bool:
|
|
66
69
|
if any(fnmatch.fnmatch(f, pat) for pat in guide.detect_files for f in files):
|
|
67
70
|
return True
|
|
68
|
-
if any(m in
|
|
71
|
+
if any(m in text for m in guide.detect_manifest):
|
|
72
|
+
return True
|
|
73
|
+
if any(i in text for i in guide.detect_imports):
|
|
69
74
|
return True
|
|
70
75
|
return False
|
|
71
76
|
|
|
72
77
|
|
|
73
|
-
def select_guides(files, *,
|
|
74
|
-
"""The guides whose detect signals fire on the
|
|
75
|
-
|
|
78
|
+
def select_guides(files, *, text: str = "", guides: list[Guide] | None = None) -> list[Guide]:
|
|
79
|
+
"""The guides whose detect signals fire on the target, languages first then
|
|
80
|
+
frameworks. `files` are the target's file paths; `text` is content to scan for
|
|
81
|
+
manifest substrings and import markers (a repo's manifests, or a diff body)."""
|
|
76
82
|
pool = load_guides() if guides is None else guides
|
|
77
83
|
file_list = list(files)
|
|
78
|
-
|
|
79
|
-
return [g for g in pool if _matches(g, file_list,
|
|
84
|
+
blob = text.lower()
|
|
85
|
+
return [g for g in pool if _matches(g, file_list, blob)]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _changed_paths(diff: str) -> list[str]:
|
|
89
|
+
"""The file paths touched by a unified diff (from its `+++ b/` / `diff --git` headers)."""
|
|
90
|
+
return _DIFF_PATH.findall(diff)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def guides_for_diff(diff: str, *, guides: list[Guide] | None = None) -> str:
|
|
94
|
+
"""Concatenated bodies of the language/framework guides relevant to a diff, by
|
|
95
|
+
its changed paths and its content. Empty when nothing matches."""
|
|
96
|
+
selected = select_guides(_changed_paths(diff), text=diff, guides=guides)
|
|
97
|
+
return "\n\n---\n\n".join(g.body for g in selected)
|
|
@@ -13,7 +13,7 @@ from __future__ import annotations
|
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
-
from codejury.
|
|
16
|
+
from codejury.guides import Guide, select_guides
|
|
17
17
|
from codejury.repo.model import build_repo_model_from_dir
|
|
18
18
|
from codejury.resources import AGENT_DIR
|
|
19
19
|
|
|
@@ -105,7 +105,7 @@ def scaffold(target: str | Path, workspace: str | Path) -> ScaffoldResult:
|
|
|
105
105
|
(ws / "entrypoints" / "_entrypoints.md").write_text(_entrypoints_md(model), encoding="utf-8")
|
|
106
106
|
|
|
107
107
|
# detect the stack and seed its review guides (languages + frameworks)
|
|
108
|
-
guides = select_guides(model.files,
|
|
108
|
+
guides = select_guides(model.files, text=_read_manifests(target))
|
|
109
109
|
(ws / "_stack.md").write_text(_stack_md(guides), encoding="utf-8")
|
|
110
110
|
|
|
111
111
|
return ScaffoldResult(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.1
|
|
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,7 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
codejury/__init__.py
|
|
5
5
|
codejury/cli.py
|
|
6
|
+
codejury/guides.py
|
|
6
7
|
codejury/json_parse.py
|
|
7
8
|
codejury/report.py
|
|
8
9
|
codejury/resources.py
|
|
@@ -63,7 +64,6 @@ codejury/providers/openai.py
|
|
|
63
64
|
codejury/providers/openai_format.py
|
|
64
65
|
codejury/providers/retry.py
|
|
65
66
|
codejury/repo/__init__.py
|
|
66
|
-
codejury/repo/guides.py
|
|
67
67
|
codejury/repo/model.py
|
|
68
68
|
codejury/repo/scaffold.py
|
|
69
69
|
tests/test_anthropic_provider.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codejury"
|
|
3
|
-
version = "0.16.
|
|
3
|
+
version = "0.16.1"
|
|
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"
|
|
@@ -85,11 +85,12 @@ def test_engine_raises_on_wrong_shape_json():
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def test_prompt_carries_diff_focus_and_do_not_report():
|
|
88
|
-
p = standard_audit_prompt(_DIFF, rules="RULE-X", context="def caller(): ...")
|
|
88
|
+
p = standard_audit_prompt(_DIFF, rules="RULE-X", context="def caller(): ...", stack="STACK-NOTE")
|
|
89
89
|
assert "SELECT * FROM u" in p # the diff
|
|
90
90
|
assert "Do NOT report" in p # the noise-control list
|
|
91
91
|
assert "IDOR" in p # the focus
|
|
92
92
|
assert "RULE-X" in p # rules excerpt
|
|
93
|
+
assert "STACK-NOTE" in p # language/framework conventions block
|
|
93
94
|
assert "def caller()" in p # context block
|
|
94
95
|
|
|
95
96
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Language/framework review guides load and are selected by detection signals
|
|
2
2
|
(file globs + dependency-manifest substrings), so adding one is a drop-in file."""
|
|
3
3
|
|
|
4
|
-
from codejury.
|
|
4
|
+
from codejury.guides import Guide, guides_for_diff, load_guides, select_guides
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def test_shipped_guides_load():
|
|
@@ -19,7 +19,7 @@ def test_select_by_file_glob():
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def test_select_by_manifest_substring():
|
|
22
|
-
matched = {g.id for g in select_guides(["main.py"],
|
|
22
|
+
matched = {g.id for g in select_guides(["main.py"], text="Django==4.2\nrequests\n")}
|
|
23
23
|
assert "django" in matched and "python" in matched
|
|
24
24
|
|
|
25
25
|
|
|
@@ -32,3 +32,14 @@ def test_select_respects_injected_pool():
|
|
|
32
32
|
detect_manifest=(), detect_imports=(), body="b")]
|
|
33
33
|
assert [g.id for g in select_guides(["a.xyz"], guides=only)] == ["x"]
|
|
34
34
|
assert select_guides(["a.py"], guides=only) == []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_guides_for_diff_by_path_and_content():
|
|
38
|
+
diff = ("diff --git a/app/urls.py b/app/urls.py\n"
|
|
39
|
+
"+from django.urls import path\n+urlpatterns = []\n")
|
|
40
|
+
notes = guides_for_diff(diff)
|
|
41
|
+
assert "Django" in notes and "Python" in notes # urls.py + .py + the django import
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_guides_for_diff_empty_when_irrelevant():
|
|
45
|
+
assert guides_for_diff("+++ b/README.md\n+hello\n") == ""
|
|
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
|