codejury 1.0.0__tar.gz → 1.0.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.
Files changed (125) hide show
  1. {codejury-1.0.0 → codejury-1.0.1}/PKG-INFO +19 -10
  2. {codejury-1.0.0 → codejury-1.0.1}/README.md +18 -9
  3. {codejury-1.0.0 → codejury-1.0.1}/codejury/cli.py +21 -11
  4. {codejury-1.0.0 → codejury-1.0.1}/codejury/guides.py +2 -0
  5. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/languages/python.md +0 -2
  6. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/protocols/oauth.md +1 -1
  7. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/cross-site-request-forgery.md +14 -0
  8. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/server-side-request-forgery.md +2 -2
  9. {codejury-1.0.0 → codejury-1.0.1}/codejury/playbook/false-positive-traps.md +4 -4
  10. {codejury-1.0.0 → codejury-1.0.1}/codejury/playbook/methodology.md +7 -5
  11. {codejury-1.0.0 → codejury-1.0.1}/codejury/playbook/slash-command.md +14 -8
  12. {codejury-1.0.0 → codejury-1.0.1}/codejury/playbook/unit-review.md +14 -7
  13. {codejury-1.0.0 → codejury-1.0.1}/codejury/providers/anthropic.py +39 -9
  14. {codejury-1.0.0 → codejury-1.0.1}/codejury/providers/litellm.py +1 -1
  15. {codejury-1.0.0 → codejury-1.0.1}/codejury/providers/openai.py +1 -1
  16. codejury-1.0.0/codejury/review/diff/engine.py → codejury-1.0.1/codejury/review/diff/audit.py +3 -2
  17. codejury-1.0.0/codejury/review/diff/runner.py → codejury-1.0.1/codejury/review/diff/engine.py +1 -1
  18. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/agent.py +3 -5
  19. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/engine.py +140 -36
  20. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/gate.py +5 -5
  21. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/model.py +0 -1
  22. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/paths.py +1 -1
  23. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/reviewer.py +19 -20
  24. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/scaffold.py +27 -10
  25. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/severity.py +1 -0
  26. codejury-1.0.1/codejury/review/repo/shapes.py +23 -0
  27. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/verifier.py +30 -4
  28. {codejury-1.0.0 → codejury-1.0.1}/codejury.egg-info/PKG-INFO +19 -10
  29. {codejury-1.0.0 → codejury-1.0.1}/codejury.egg-info/SOURCES.txt +8 -4
  30. {codejury-1.0.0 → codejury-1.0.1}/pyproject.toml +1 -1
  31. codejury-1.0.1/tests/test_anthropic_provider.py +108 -0
  32. codejury-1.0.0/tests/test_openai_format.py → codejury-1.0.1/tests/test_chat_format.py +1 -1
  33. {codejury-1.0.0 → codejury-1.0.1}/tests/test_cli.py +16 -7
  34. {codejury-1.0.0 → codejury-1.0.1}/tests/test_diff_adversarial.py +9 -6
  35. codejury-1.0.0/tests/test_diff_engine.py → codejury-1.0.1/tests/test_diff_audit.py +6 -6
  36. codejury-1.0.1/tests/test_evals.py +392 -0
  37. {codejury-1.0.0 → codejury-1.0.1}/tests/test_guides.py +1 -1
  38. codejury-1.0.1/tests/test_json_parse.py +79 -0
  39. {codejury-1.0.0 → codejury-1.0.1}/tests/test_litellm_provider.py +32 -1
  40. {codejury-1.0.0 → codejury-1.0.1}/tests/test_openai_provider.py +40 -1
  41. codejury-1.0.1/tests/test_provider_factory.py +31 -0
  42. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_engine.py +95 -35
  43. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_gate.py +6 -5
  44. codejury-1.0.1/tests/test_repo_paths.py +42 -0
  45. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_scaffold.py +20 -11
  46. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_verifier.py +15 -14
  47. codejury-1.0.0/tests/test_anthropic_provider.py +0 -54
  48. codejury-1.0.0/tests/test_json_parse.py +0 -41
  49. {codejury-1.0.0 → codejury-1.0.1}/LICENSE +0 -0
  50. {codejury-1.0.0 → codejury-1.0.1}/codejury/__init__.py +0 -0
  51. {codejury-1.0.0 → codejury-1.0.1}/codejury/__main__.py +0 -0
  52. {codejury-1.0.0 → codejury-1.0.1}/codejury/detection.py +0 -0
  53. {codejury-1.0.0 → codejury-1.0.1}/codejury/detection.yaml +0 -0
  54. {codejury-1.0.0 → codejury-1.0.1}/codejury/finding.py +0 -0
  55. {codejury-1.0.0 → codejury-1.0.1}/codejury/json_parse.py +0 -0
  56. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/frameworks/go/echo.md +0 -0
  57. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/frameworks/go/gin.md +0 -0
  58. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/frameworks/javascript/express.md +0 -0
  59. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/frameworks/javascript/nestjs.md +0 -0
  60. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/frameworks/python/celery.md +0 -0
  61. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/frameworks/python/django.md +0 -0
  62. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/frameworks/python/fastapi.md +0 -0
  63. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/frameworks/python/flask.md +0 -0
  64. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/languages/go.md +0 -0
  65. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/languages/javascript.md +0 -0
  66. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/guides/languages/typescript.md +0 -0
  67. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/index.md +0 -0
  68. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/business-logic.md +0 -0
  69. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/code-injection.md +0 -0
  70. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/command-injection.md +0 -0
  71. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/cross-site-scripting.md +0 -0
  72. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/hardcoded-secrets.md +0 -0
  73. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/http-response-splitting.md +0 -0
  74. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/improper-authentication.md +0 -0
  75. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/information-exposure.md +0 -0
  76. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/insecure-cryptography.md +0 -0
  77. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/insecure-deserialization.md +0 -0
  78. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/insecure-direct-object-reference.md +0 -0
  79. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/insecure-session-management.md +0 -0
  80. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/insecure-transport.md +0 -0
  81. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/jwt-validation.md +0 -0
  82. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/mass-assignment.md +0 -0
  83. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/missing-authorization.md +0 -0
  84. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/open-redirect.md +0 -0
  85. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/path-traversal.md +0 -0
  86. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/race-condition.md +0 -0
  87. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/replay-attack.md +0 -0
  88. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/server-side-template-injection.md +0 -0
  89. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/sql-injection.md +0 -0
  90. {codejury-1.0.0 → codejury-1.0.1}/codejury/knowledge/vulnerabilities/xml-external-entity.md +0 -0
  91. {codejury-1.0.0 → codejury-1.0.1}/codejury/markdown_docs.py +0 -0
  92. {codejury-1.0.0 → codejury-1.0.1}/codejury/playbook/severity-rubric.md +0 -0
  93. {codejury-1.0.0 → codejury-1.0.1}/codejury/providers/__init__.py +0 -0
  94. {codejury-1.0.0 → codejury-1.0.1}/codejury/providers/base.py +0 -0
  95. /codejury-1.0.0/codejury/providers/openai_format.py → /codejury-1.0.1/codejury/providers/chat_format.py +0 -0
  96. {codejury-1.0.0 → codejury-1.0.1}/codejury/providers/factory.py +0 -0
  97. {codejury-1.0.0 → codejury-1.0.1}/codejury/providers/mock.py +0 -0
  98. {codejury-1.0.0 → codejury-1.0.1}/codejury/providers/retry.py +0 -0
  99. {codejury-1.0.0 → codejury-1.0.1}/codejury/report.py +0 -0
  100. {codejury-1.0.0 → codejury-1.0.1}/codejury/resources.py +0 -0
  101. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/__init__.py +0 -0
  102. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/diff/__init__.py +0 -0
  103. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/diff/adversarial.py +0 -0
  104. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/diff/filter.py +0 -0
  105. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/diff/prompts.py +0 -0
  106. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/diff/vulnerabilities.py +0 -0
  107. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/__init__.py +0 -0
  108. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/pass_loop.py +0 -0
  109. {codejury-1.0.0 → codejury-1.0.1}/codejury/review/repo/union.py +0 -0
  110. {codejury-1.0.0 → codejury-1.0.1}/codejury/severity.py +0 -0
  111. {codejury-1.0.0 → codejury-1.0.1}/codejury.egg-info/dependency_links.txt +0 -0
  112. {codejury-1.0.0 → codejury-1.0.1}/codejury.egg-info/entry_points.txt +0 -0
  113. {codejury-1.0.0 → codejury-1.0.1}/codejury.egg-info/requires.txt +0 -0
  114. {codejury-1.0.0 → codejury-1.0.1}/codejury.egg-info/top_level.txt +0 -0
  115. {codejury-1.0.0 → codejury-1.0.1}/setup.cfg +0 -0
  116. {codejury-1.0.0 → codejury-1.0.1}/tests/test_detection.py +0 -0
  117. {codejury-1.0.0 → codejury-1.0.1}/tests/test_markdown_docs.py +0 -0
  118. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_agent.py +0 -0
  119. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_model.py +0 -0
  120. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_pass_loop.py +0 -0
  121. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_severity.py +0 -0
  122. {codejury-1.0.0 → codejury-1.0.1}/tests/test_repo_union.py +0 -0
  123. {codejury-1.0.0 → codejury-1.0.1}/tests/test_report.py +0 -0
  124. {codejury-1.0.0 → codejury-1.0.1}/tests/test_retry_provider.py +0 -0
  125. {codejury-1.0.0 → codejury-1.0.1}/tests/test_vulnerabilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codejury
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: AI-assisted security review for code diffs and whole repositories.
5
5
  Author: AISecLabs
6
6
  License-Expression: MIT
@@ -35,7 +35,7 @@ Requires-Dist: pytest>=8.0; extra == "dev"
35
35
  Requires-Dist: jsonschema>=4.0; extra == "dev"
36
36
  Dynamic: license-file
37
37
 
38
- ```
38
+ ```text
39
39
  ██████╗ ██████╗ ██████╗ ███████╗ ██╗██╗ ██╗██████╗ ██╗ ██╗
40
40
  ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██║██║ ██║██╔══██╗╚██╗ ██╔╝
41
41
  ██║ ██║ ██║██║ ██║█████╗ ██║██║ ██║██████╔╝ ╚████╔╝
@@ -44,6 +44,10 @@ Dynamic: license-file
44
44
  ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
45
45
  ```
46
46
 
47
+ [![PyPI](https://img.shields.io/pypi/v/codejury)](https://pypi.org/project/codejury/)
48
+ [![Python](https://img.shields.io/pypi/pyversions/codejury)](https://pypi.org/project/codejury/)
49
+ [![License: MIT](https://img.shields.io/pypi/l/codejury)](https://github.com/aiseclabs/codejury/blob/master/LICENSE)
50
+
47
51
  AI-assisted security review for code diffs and whole repositories.
48
52
 
49
53
  The tool has two review paths:
@@ -75,7 +79,7 @@ codejury install-slash-command --agent codex # Codex
75
79
  `install-slash-command` copies `/codejury-review-repo` into the selected agent's command
76
80
  directory. Pass `--dir` to install it somewhere else.
77
81
 
78
- ## Configure A Model Backend
82
+ ## Configure a Model Backend
79
83
 
80
84
  Set a provider key through flags or environment variables:
81
85
 
@@ -156,12 +160,16 @@ codejury review repo /path/to/repo
156
160
  The workspace contains:
157
161
 
158
162
  ```text
159
- inventory/ attack surface, authorization model, candidates, severity rubric
163
+ inventory/ attack surface, authorization model, seeded entrypoints, severity rubric
160
164
  units/ one review unit per candidate entrypoint
161
- issues/ issue write-ups
165
+ candidates/ agent proposals, one write-up per candidate finding
166
+ findings/ confirmed findings, written by finalize
162
167
  pocs/ runnable PoCs, when available
168
+ findings.json ranked machine-readable findings
163
169
  METHODOLOGY.md full review process
164
170
  _stack.md detected stack notes
171
+ _refuted.md refuted candidates and why
172
+ _pocs.md PoC reconciliation, planned versus delivered
165
173
  ```
166
174
 
167
175
  Then run the interactive slash command in Claude Code or Codex:
@@ -181,9 +189,10 @@ codejury review repo /path/to/repo --finalize
181
189
  codejury review repo /path/to/repo --gate
182
190
  ```
183
191
 
184
- `--finalize` deduplicates issue files, verifies survivors, records refuted candidates in
185
- `_refuted.md`, and writes ranked `findings.json`. `--gate` fails until the workspace has
186
- an enumerated surface, reviewed units, and calibrated findings.
192
+ `--finalize` deduplicates candidate files, verifies survivors, writes the confirmed
193
+ `findings/`, records refuted candidates in `_refuted.md` and PoC reconciliation in
194
+ `_pocs.md`, and writes ranked `findings.json`. `--gate` fails until the workspace has an
195
+ enumerated surface, reviewed units, and calibrated candidates.
187
196
 
188
197
  For a headless run, use:
189
198
 
@@ -221,7 +230,7 @@ The tool is intentionally scoped to real exploitable application security issues
221
230
  not report dependency CVEs, style notes, generic best practices, speculation, or risks that
222
231
  only matter if production configuration leaks.
223
232
 
224
- ## Model And Mode Guidance
233
+ ## Model and Mode Guidance
225
234
 
226
235
  Detection quality is dominated by model quality first, then mode.
227
236
 
@@ -241,7 +250,7 @@ cp examples/codejury-pr-review.yml .github/workflows/codejury-pr-review.yml
241
250
  Add `CODEJURY_API_KEY` as a repository secret. The workflow reviews the pull request diff,
242
251
  uploads SARIF to code scanning, and fails on HIGH or CRITICAL findings.
243
252
 
244
- ## Extend The Knowledge
253
+ ## Extend the Knowledge
245
254
 
246
255
  Add security knowledge as markdown:
247
256
 
@@ -1,4 +1,4 @@
1
- ```
1
+ ```text
2
2
  ██████╗ ██████╗ ██████╗ ███████╗ ██╗██╗ ██╗██████╗ ██╗ ██╗
3
3
  ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██║██║ ██║██╔══██╗╚██╗ ██╔╝
4
4
  ██║ ██║ ██║██║ ██║█████╗ ██║██║ ██║██████╔╝ ╚████╔╝
@@ -7,6 +7,10 @@
7
7
  ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
+ [![PyPI](https://img.shields.io/pypi/v/codejury)](https://pypi.org/project/codejury/)
11
+ [![Python](https://img.shields.io/pypi/pyversions/codejury)](https://pypi.org/project/codejury/)
12
+ [![License: MIT](https://img.shields.io/pypi/l/codejury)](https://github.com/aiseclabs/codejury/blob/master/LICENSE)
13
+
10
14
  AI-assisted security review for code diffs and whole repositories.
11
15
 
12
16
  The tool has two review paths:
@@ -38,7 +42,7 @@ codejury install-slash-command --agent codex # Codex
38
42
  `install-slash-command` copies `/codejury-review-repo` into the selected agent's command
39
43
  directory. Pass `--dir` to install it somewhere else.
40
44
 
41
- ## Configure A Model Backend
45
+ ## Configure a Model Backend
42
46
 
43
47
  Set a provider key through flags or environment variables:
44
48
 
@@ -119,12 +123,16 @@ codejury review repo /path/to/repo
119
123
  The workspace contains:
120
124
 
121
125
  ```text
122
- inventory/ attack surface, authorization model, candidates, severity rubric
126
+ inventory/ attack surface, authorization model, seeded entrypoints, severity rubric
123
127
  units/ one review unit per candidate entrypoint
124
- issues/ issue write-ups
128
+ candidates/ agent proposals, one write-up per candidate finding
129
+ findings/ confirmed findings, written by finalize
125
130
  pocs/ runnable PoCs, when available
131
+ findings.json ranked machine-readable findings
126
132
  METHODOLOGY.md full review process
127
133
  _stack.md detected stack notes
134
+ _refuted.md refuted candidates and why
135
+ _pocs.md PoC reconciliation, planned versus delivered
128
136
  ```
129
137
 
130
138
  Then run the interactive slash command in Claude Code or Codex:
@@ -144,9 +152,10 @@ codejury review repo /path/to/repo --finalize
144
152
  codejury review repo /path/to/repo --gate
145
153
  ```
146
154
 
147
- `--finalize` deduplicates issue files, verifies survivors, records refuted candidates in
148
- `_refuted.md`, and writes ranked `findings.json`. `--gate` fails until the workspace has
149
- an enumerated surface, reviewed units, and calibrated findings.
155
+ `--finalize` deduplicates candidate files, verifies survivors, writes the confirmed
156
+ `findings/`, records refuted candidates in `_refuted.md` and PoC reconciliation in
157
+ `_pocs.md`, and writes ranked `findings.json`. `--gate` fails until the workspace has an
158
+ enumerated surface, reviewed units, and calibrated candidates.
150
159
 
151
160
  For a headless run, use:
152
161
 
@@ -184,7 +193,7 @@ The tool is intentionally scoped to real exploitable application security issues
184
193
  not report dependency CVEs, style notes, generic best practices, speculation, or risks that
185
194
  only matter if production configuration leaks.
186
195
 
187
- ## Model And Mode Guidance
196
+ ## Model and Mode Guidance
188
197
 
189
198
  Detection quality is dominated by model quality first, then mode.
190
199
 
@@ -204,7 +213,7 @@ cp examples/codejury-pr-review.yml .github/workflows/codejury-pr-review.yml
204
213
  Add `CODEJURY_API_KEY` as a repository secret. The workflow reviews the pull request diff,
205
214
  uploads SARIF to code scanning, and fails on HIGH or CRITICAL findings.
206
215
 
207
- ## Extend The Knowledge
216
+ ## Extend the Knowledge
208
217
 
209
218
  Add security knowledge as markdown:
210
219
 
@@ -9,19 +9,20 @@ Two paths matched to their nature:
9
9
  which a single call cannot do for a whole codebase.
10
10
 
11
11
  ``review diff --dry-run`` exercises the engine with a mock provider and no key.
12
- The audit orchestration itself lives in ``codejury.review.diff.runner``.
12
+ The audit orchestration itself lives in ``codejury.review.diff.engine``.
13
13
  """
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
17
  import argparse
18
+ import os
18
19
  import subprocess
19
20
  import sys
20
21
  from pathlib import Path
21
22
 
22
23
  from codejury import __version__
23
24
  from codejury.report import gate, render
24
- from codejury.review.diff.runner import audit_diff
25
+ from codejury.review.diff.engine import audit_diff
25
26
  from codejury.providers.factory import (
26
27
  DEFAULT_API_BASE,
27
28
  DEFAULT_API_KEY,
@@ -39,6 +40,12 @@ _FORMATS = ("text", "markdown", "json", "sarif")
39
40
  _FAIL_ON = ("critical", "high", "medium", "low")
40
41
 
41
42
 
43
+ def _default_workspace() -> str:
44
+ """A user-private default, since the workspace holds the auth model, exploit paths, and PoCs."""
45
+ base = os.environ.get("XDG_STATE_HOME") or str(Path.home() / ".local" / "state")
46
+ return str(Path(base) / "codejury" / "reviews")
47
+
48
+
42
49
  def _read_diff(args) -> str:
43
50
  if args.file:
44
51
  with open(args.file, encoding="utf-8") as f:
@@ -102,7 +109,9 @@ def main(argv: list[str] | None = None) -> int:
102
109
  _add_audit_args(rsub.add_parser("diff", help="audit a unified diff (the coded engine)"))
103
110
  repo = rsub.add_parser("repo", help="scaffold a whole-repo review for an interactive agent")
104
111
  repo.add_argument("directory", help="target repository to review")
105
- repo.add_argument("--workspace", default="/var/tmp/codejury-review", help="where to create the review workspace")
112
+ repo.add_argument("--workspace", default=_default_workspace(),
113
+ help="where to create the review workspace, defaults to a user-private "
114
+ "directory under XDG_STATE_HOME or ~/.local/state")
106
115
  repo.add_argument("--fresh", action="store_true",
107
116
  help="clear a previous review's output in the workspace first")
108
117
  repo.add_argument("--gate", action="store_true",
@@ -112,7 +121,7 @@ def main(argv: list[str] | None = None) -> int:
112
121
  help="run the coded multi-pass engine over the repo, not just scaffold, "
113
122
  "covers every unit each pass, cycles lenses, unions until convergence")
114
123
  repo.add_argument("--finalize", action="store_true",
115
- help="post-process an existing workspace's issues in code: dedup, "
124
+ help="post-process an existing workspace's candidates in code: dedup, "
116
125
  "adversarially verify, and write the ranked report, resumable")
117
126
  repo.add_argument("--dry-run", action="store_true",
118
127
  help="run only: drive the engine with a mock provider and no key, to smoke-test the pipeline")
@@ -211,9 +220,10 @@ def _dispatch(args, parser) -> int:
211
220
  )
212
221
  kept = len(fr.verify.confirmed) if fr.verify else fr.deduped
213
222
  refuted = len(fr.verify.refuted) if fr.verify else 0
214
- print(f"Finalize done: parsed {fr.parsed} issues -> {fr.deduped} after dedup -> "
223
+ print(f"Finalize done: parsed {fr.parsed} candidates -> {fr.deduped} after dedup -> "
215
224
  f"{kept} confirmed, {refuted} refuted, see {fr.workspace}/_refuted.md.")
216
- print(f"Ranked report in {fr.workspace}/findings.json")
225
+ print(f"Confirmed findings in {fr.workspace}/findings/ and {fr.workspace}/findings.json, "
226
+ f"PoC reconciliation in {fr.workspace}/_pocs.md")
217
227
  if fr.verify and fr.verify.errors:
218
228
  print(f"WARNING: {fr.verify.errors} verification calls failed. Re-run to resume.", file=sys.stderr)
219
229
  return 1 # fail loud: an incomplete verification is not a clean finalize, invariant 3
@@ -259,7 +269,7 @@ def _dispatch(args, parser) -> int:
259
269
  print(f"WARNING: {failures} model calls failed, e.g. provider errors or rate limits. "
260
270
  "Results may be understated. Lower --concurrency or raise --retries and re-run.",
261
271
  file=sys.stderr)
262
- print(f"Findings written to {res.scaffold.workspace}/issues/ and {res.scaffold.workspace}/findings.json")
272
+ print(f"Findings written to {res.scaffold.workspace}/findings/ and {res.scaffold.workspace}/findings.json")
263
273
  return 1 if failures else 0 # fail loud: a partial run must not exit clean, invariant 3
264
274
 
265
275
  if args.command == "review" and scope == "repo":
@@ -275,13 +285,13 @@ def _dispatch(args, parser) -> int:
275
285
  print(f"Detected stack: {', '.join(res.guides)}, notes in {res.workspace}/_stack.md", file=sys.stderr)
276
286
  print(f"Seeded {len(res.candidate_files)} candidate entrypoint files and "
277
287
  f"{len(res.trace_targets)} logic-layer trace targets into "
278
- f"{res.workspace}/inventory/_candidates.md", file=sys.stderr)
288
+ f"{res.workspace}/inventory/_entrypoints.md", file=sys.stderr)
279
289
  print(f"Methodology: {res.workspace}/METHODOLOGY.md", file=sys.stderr)
280
290
  print(
281
- "This command sets up the review, it does not find the issues itself. Next, have an "
291
+ "This command sets up the review, it does not find anything itself. Next, have an "
282
292
  f"interactive agent follow {res.workspace}/METHODOLOGY.md to run the review, or use the "
283
- "/codejury-review-repo command in Claude Code or Codex. Findings are written to "
284
- f"{res.workspace}/issues/."
293
+ "/codejury-review-repo command in Claude Code or Codex. The agent proposes findings in "
294
+ f"{res.workspace}/candidates/, finalize confirms them into {res.workspace}/findings/."
285
295
  )
286
296
  return 0
287
297
 
@@ -87,6 +87,8 @@ def logic_layer_globs(guides: list[Guide]) -> tuple[str, ...]:
87
87
  def load_guides(languages_dir=LANGUAGES_DIR, frameworks_dir=FRAMEWORKS_DIR, protocols_dir=PROTOCOLS_DIR) -> list[Guide]:
88
88
  # a guide's kind comes from its frontmatter, never from the directory it sits in, so the two cannot drift
89
89
  out: list[Guide] = []
90
+ # languages first, then frameworks, then protocols, select_guides keeps this order so a
91
+ # language guide outranks a framework guide when both fire on the same target
90
92
  for directory in (languages_dir, frameworks_dir, protocols_dir):
91
93
  out += [_guide(path, meta, body) for path, meta, body in iter_md_docs(directory)]
92
94
  return out
@@ -4,8 +4,6 @@ title: Python
4
4
  kind: language
5
5
  detect:
6
6
  files: ["*.py"]
7
- manifest: []
8
- imports: []
9
7
  entrypoint_files: ["*__main__.py", "*main.py", "*cli.py", "*/cli/*.py", "*/commands/*.py"]
10
8
  entrypoint_markers: ["argparse", "ArgumentParser", "click.command", "click.group", "@click.command", "@click.group"]
11
9
  logic_layers: ["*/services/*.py", "*services.py", "*/managers/*.py", "*managers.py", "*/dao/*.py", "*dao.py", "*/repositories/*.py", "*/repository/*.py"]
@@ -3,7 +3,7 @@ id: oauth
3
3
  title: OAuth and OIDC
4
4
  kind: protocol
5
5
  detect:
6
- content: ["grant_type", "authorization_code", "redirect_uri", "code_challenge", "response_type", "client_secret", "openid-configuration"]
6
+ content: ["grant_type", "authorization_code", "redirect_uri", "code_challenge", "response_type", "client_secret", "openid-configuration", "exchange_code"]
7
7
  ---
8
8
  # OAuth and OIDC Review Notes
9
9
 
@@ -21,6 +21,20 @@ def change_email():
21
21
  ```
22
22
  Secure: keep CSRF protection on. Validate the token, or set the session cookie `SameSite="Lax"` and check the Origin header.
23
23
 
24
+ ### OAuth Login CSRF
25
+
26
+ An OAuth callback that redeems the `code` and starts a session is a state-changing request. When it does not check a `state` value bound to the user's session, an attacker can hand the victim a code from the attacker's account and log the victim into it, login CSRF.
27
+
28
+ Vulnerable:
29
+ ```python
30
+ @app.route("/oauth/callback")
31
+ def callback():
32
+ token = exchange_code(request.args["code"]) # no state check, any code is accepted
33
+ login(token)
34
+ return redirect("/")
35
+ ```
36
+ Secure: issue a random `state` at authorize time, keep it in the session, and reject the callback unless the returned `state` matches.
37
+
24
38
  ### Not a Finding
25
39
 
26
40
  An endpoint authenticated by a bearer token read from JavaScript and sent in the `Authorization` header, not by an ambient cookie, is not CSRF. CSRF needs a credential the browser attaches automatically.
@@ -22,8 +22,8 @@ if urlparse(url).hostname not in ALLOWED_HOSTS:
22
22
  return requests.get(url).text
23
23
  ```
24
24
 
25
- A hostname allowlist alone is often not enough. Enforce `https`, reject credentials in the URL, resolve the host and block private, loopback, and link-local ranges, and re-check after each redirect. Prefer an exact destination allowlist.
25
+ Stronger hardening adds defense in depth: enforce `https`, reject credentials in the URL, resolve the host and block private, loopback, and link-local ranges, and re-check after each redirect. Prefer an exact destination allowlist.
26
26
 
27
27
  ### Not a Finding
28
28
 
29
- A URL fetched only after an exact allowlist plus internal-IP blocking, with redirects controlled, is not SSRF. A constant or trusted-config URL is not SSRF.
29
+ A URL fetched only after the parsed hostname is checked against a fixed allowlist by exact equality or membership before the fetch is the expected control and is not reportable without a concrete bypass. Report it only when the check is bypassable, such as a substring, suffix, or `startswith` match, an attacker-controlled allowlist, or a redirect followed with no re-check. Missing internal-IP blocking or redirect re-checks on top of an exact allowlist is hardening advice, not by itself an exploitable finding. A constant or trusted-config URL is not SSRF.
@@ -17,7 +17,7 @@ recurring misjudgement, add it here.
17
17
  - `transaction.atomic()` plus a real row lock serializes concurrent redeems even
18
18
  on READ COMMITTED. The race only exists if no lock is taken on the contended row.
19
19
 
20
- ## Input That Looks Attacker-Controlled But Is Not
20
+ ## Input That Looks Attacker-Controlled but Is Not
21
21
 
22
22
  - An id, ticket, or key read from the session, a signed cookie, or a server-set
23
23
  field is not attacker-controlled even though it arrives in the request object.
@@ -25,7 +25,7 @@ recurring misjudgement, add it here.
25
25
  - A value the framework derives from an authenticated identity, not from the
26
26
  request body, is trusted input.
27
27
 
28
- ## Controls That Live Off the Handler Body
28
+ ## Controls That Live off the Handler Body
29
29
 
30
30
  - The auth, ownership, or signature check may be in a decorator, a middleware, a
31
31
  permission class, a base class, or a wrapper, not in the handler you are reading.
@@ -36,14 +36,14 @@ recurring misjudgement, add it here.
36
36
 
37
37
  - "The caller is authenticated", "the scheme fails closed", or "the token is
38
38
  single-use" do not by themselves make a request non-replayable. Conversely, if a
39
- nonce IS consumed and a freshness window IS enforced, the replay concern is moot.
39
+ nonce is consumed and a freshness window is enforced, the replay concern is moot.
40
40
  Controlling fact: is the exact signed request accepted twice, or is a nonce
41
41
  consumed and a timestamp window checked?
42
42
 
43
43
  ## Trust Boundaries
44
44
 
45
45
  - A cross-service or cross-tenant read is only a finding if the two sides are
46
- actually distinct trust domains. Decide ONCE whether a given principal, an internal
46
+ actually distinct trust domains. Decide once whether a given principal, an internal
47
47
  service role, a sibling tenant, a worker, is inside or outside the boundary, then
48
48
  apply that one answer to every finding touching it. Do not confirm one finding by
49
49
  treating the principal as hostile and refute another by treating the same principal
@@ -31,7 +31,8 @@ The one irreducible human dependency, the credentials and go-ahead to run a PoC
31
31
  safely, is deferred to a separate phase and never asked for mid-run.
32
32
 
33
33
  Workspace: `<workspace>/<project>/`, created for you, holding `inventory/`,
34
- `units/`, `issues/`, and `pocs/`.
34
+ `units/`, `candidates/`, `pocs/`, and `findings/`. You write proposals into
35
+ `candidates/` and PoCs into `pocs/`. Finalize confirms them into `findings/`.
35
36
 
36
37
  ---
37
38
 
@@ -132,7 +133,7 @@ mandate below:
132
133
  owner vs tenant vs service, or only prove the caller is some valid user?
133
134
  Compare the unit's siblings and versions for a dropped or weakened check.
134
135
  - **Replay**: does a signed or authenticated privileged request both consume a
135
- one-time nonce AND enforce a freshness window? A signature alone is not enough.
136
+ one-time nonce and enforce a freshness window? A signature alone is not enough.
136
137
  - **Concurrency**: is a check-then-act serialized by a lock held across the act?
137
138
  Read the real mechanism. A `select_for_update` whose result is discarded still
138
139
  holds the row lock on a production RDBMS inside a transaction, so judge against
@@ -169,8 +170,9 @@ cleared it, so a wrong clear is visible.
169
170
  on a privileged path that looks bounded lands at MEDIUM or higher and is reported,
170
171
  not refuted. When unsure between two levels, report at the higher and say why.
171
172
 
172
- Write each confirmed finding to `issues/<name>.md` with a runnable PoC at
173
- `pocs/<name>.<ext>`:
173
+ Write each candidate finding to `candidates/<name>.md` with a runnable PoC at
174
+ `pocs/<name>.<ext>` under the same `<name>`, so finalize can match the PoC to the
175
+ confirmed finding it writes into `findings/`:
174
176
 
175
177
  ```markdown
176
178
  # <title>
@@ -216,6 +218,6 @@ evidence.
216
218
 
217
219
  The persistent workspace carries the settled work across runs, so a re-run extends
218
220
  coverage instead of restarting. A unit already marked `- Status: reviewed` is not
219
- re-reviewed, the findings in `issues/` are not re-derived, and a finding already
221
+ re-reviewed, the proposals in `candidates/` are not re-derived, and a finding already
220
222
  verified is not re-litigated. A re-run, including after a usage limit, picks up the
221
223
  units still open and stops when the gate passes.
@@ -12,16 +12,20 @@ review in passing here is the shallow whole-repo pass this method exists to repl
12
12
  1. SCAFFOLD. Build the workspace, the deterministic worklist you do not invent:
13
13
 
14
14
  ```
15
- codejury review repo $ARGUMENTS --workspace /var/tmp/codejury-review
15
+ codejury review repo $ARGUMENTS
16
16
  ```
17
17
 
18
+ The workspace defaults to a user-private directory under `XDG_STATE_HOME` or
19
+ `~/.local/state`, the same path for all three steps below, so they share one workspace.
20
+ Pass `--workspace <path>` to override it.
21
+
18
22
  If `codejury` is not on PATH it is a pip-installed console script, so activate the
19
23
  project venv first, for example `. .venv/bin/activate`, or run `python -m codejury`.
20
24
  If it reports a previous review's output in the workspace, ask me whether to clear
21
25
  it, and if I say yes, re-run with `--fresh`.
22
26
 
23
27
  RESUMING. If a previous run was interrupted, for example by a usage limit, just run
24
- this command again WITHOUT clearing the workspace, answer no when it asks to clear. It
28
+ this command again without clearing the workspace, answer no when it asks to clear. It
25
29
  resumes and does not redo finished work: a unit already marked `- Status: reviewed`
26
30
  is skipped in the fan-out, and `--finalize` does not re-verify a finding it already
27
31
  settled. So an interruption loses no completed work, keep resuming in new sessions
@@ -55,19 +59,20 @@ review in passing here is the shallow whole-repo pass this method exists to repl
55
59
  libraries they call, hunts the high-impact classes, verifies each control on the
56
60
  code it actually reads, refutes its own candidates, grades every real finding by
57
61
  the rubric, CRITICAL through LOW, never refuted for low impact, writes each to
58
- `issues/<name>.md`, and flips its unit to `- Status: reviewed`.
62
+ `candidates/<name>.md` with its PoC at `pocs/<name>.<ext>` under the same `<name>`,
63
+ and flips its unit to `- Status: reviewed`.
59
64
 
60
65
  Do not review units in this main context, only orchestrate. After the first pass,
61
66
  run more passes giving the units a different lead lens each time, authorization,
62
67
  then replay, then concurrency, then data exposure, then business logic, adding only
63
- findings not already in `issues/`. The union grows along a different axis each pass.
68
+ findings not already in `candidates/`. The union grows along a different axis each pass.
64
69
  Stop when two consecutive passes add no new issue.
65
70
 
66
71
  4. FINALIZE. In code, do not dedup or verify in prose. Once the fan-out has covered the
67
72
  surface, run:
68
73
 
69
74
  ```
70
- codejury review repo $ARGUMENTS --workspace /var/tmp/codejury-review --finalize
75
+ codejury review repo $ARGUMENTS --finalize
71
76
  ```
72
77
 
73
78
  In Claude Code, add `--reviewer claude-cli` to verify through your Claude Code access
@@ -82,7 +87,7 @@ review in passing here is the shallow whole-repo pass this method exists to repl
82
87
  5. GATE. Let codejury, not your judgment, decide whether the review may stop:
83
88
 
84
89
  ```
85
- codejury review repo $ARGUMENTS --workspace /var/tmp/codejury-review --gate
90
+ codejury review repo $ARGUMENTS --gate
86
91
  ```
87
92
 
88
93
  If it exits non-zero it lists what is unmet: the surface not enumerated, a unit not
@@ -100,5 +105,6 @@ follow-up run I start later, do not pause to ask me mid-run.
100
105
  End with one report and then stop: confirmed findings as a table of title, class,
101
106
  `file:line`, severity, and status, the blocked findings each with its `Needs:`, the
102
107
  consolidated verification-needs list, and the coverage, units reviewed over units in
103
- the inventory. The issue files live in the workspace `issues/`. Do not ask me to
104
- continue, just finish and report.
108
+ the inventory. The proposals live in the workspace `candidates/`, the confirmed
109
+ findings finalize writes in `findings/`. Do not ask me to continue, just finish and
110
+ report.
@@ -27,8 +27,15 @@ For every control on the path, decide on the code you actually read, never on th
27
27
  presence of a named control:
28
28
 
29
29
  - **Authorization granularity**: does the check scope to the right principal, owner
30
- vs tenant vs service, or only prove the caller is some valid user? Compare sibling
31
- endpoints and endpoint versions for a check present on one and dropped on another.
30
+ vs tenant vs service, or only prove the caller is some valid user? Compare siblings
31
+ for a control present on most and dropped on one. Siblings are not only sibling
32
+ endpoints and endpoint versions, but repeated branches in one handler, a query run
33
+ once per object type in a fan out, or one method in a set of similar methods. Where
34
+ most scope to the owner and one does not, that one is the likely IDOR.
35
+ - **Disclosure on a list endpoint**: a list or ReadAll that returns a secret field, a
36
+ hash, token, password, or key, hands it to every caller allowed to list, including one
37
+ meant to see only a less privileged subset. Returning the hash of a write or admin
38
+ share to a viewer with read access is an escalation, not a clean list.
32
39
  - **Replay**: does a signed or authenticated privileged request both consume a
33
40
  one-time nonce and enforce a freshness window? A signature alone is not enough.
34
41
  - **Concurrency**: is a check-then-act serialized by a lock held across the act? A
@@ -47,7 +54,7 @@ report clean. The only things you do not report are dependency or component CVEs
47
54
  of this tool's scope, and a candidate the facts refute, the controlling fact holds when
48
55
  you read the code. Everything else that is real is reported, graded by the rubric.
49
56
 
50
- A weaker signal is a LOWER severity, not a dropped finding. A best-practice or
57
+ A weaker signal is a lower severity, not a dropped finding. A best-practice or
51
58
  hardening gap, a config default, a debug or env-gated path, or a finding whose impact
52
59
  looks bounded is graded LOW or MEDIUM and surfaced, never suppressed. Noise is managed
53
60
  by severity, the reader sorts by it, it is not managed by you hiding findings. An
@@ -68,8 +75,8 @@ non-finding with a plausible word: "it is idempotent", "it yields the same token
68
75
  "it only returns status" lower the severity per the rubric, they do not make the
69
76
  finding disappear.
70
77
 
71
- Write each confirmed or blocked finding to `issues/<name>.md`: Risk, Type, Source as
78
+ Write each confirmed or blocked finding to `candidates/<name>.md`: Risk, Type, Source as
72
79
  `METHOD /path`, Status, Analysis citing `file:line`, Attack Path, and Fix. Save a
73
- runnable PoC to `pocs/<name>.<ext>`. Record any cleared control with the controlling
74
- fact that cleared it, so a wrong clear is visible. Then set this unit's Status to
75
- `reviewed`.
80
+ runnable PoC to `pocs/<name>.<ext>` under the same `<name>`, so finalize can match it.
81
+ Record any cleared control with the controlling fact that cleared it, so a wrong clear
82
+ is visible. Then set this unit's Status to `reviewed`.
@@ -18,11 +18,18 @@ from codejury.providers.base import CompletionResult, Message, Provider
18
18
 
19
19
  class AnthropicProvider(Provider):
20
20
  def __init__(
21
- self, *, api_key: str | None = None, base_url: str | None = None, client: Any | None = None
21
+ self,
22
+ *,
23
+ api_key: str | None = None,
24
+ base_url: str | None = None,
25
+ client: Any | None = None,
26
+ temperature: float | None = 0.0,
22
27
  ) -> None:
23
28
  self._api_key = api_key
24
29
  self._base_url = base_url
25
30
  self._client = client
31
+ # determinism: temperature 0 so the same input yields the same verdicts, invariant 2
32
+ self._temperature = temperature
26
33
 
27
34
  def _get_client(self) -> Any:
28
35
  if self._client is None:
@@ -53,16 +60,39 @@ class AnthropicProvider(Provider):
53
60
  if cache and system:
54
61
  system_param = [{"type": "text", "text": system, "cache_control": {"type": "ephemeral"}}]
55
62
 
56
- response = self._get_client().messages.create(
57
- model=model,
58
- max_tokens=max_tokens,
59
- temperature=0, # determinism: same input gives the same verdicts, invariant 2
60
- timeout=600,
61
- system=system_param,
62
- messages=[{"role": m.role, "content": m.content} for m in messages],
63
- )
63
+ request: dict[str, Any] = {
64
+ "model": model,
65
+ "max_tokens": max_tokens,
66
+ "timeout": 600,
67
+ "system": system_param,
68
+ "messages": [{"role": m.role, "content": m.content} for m in messages],
69
+ }
70
+ response = self._create(request)
64
71
  return CompletionResult(text=_extract_text(response))
65
72
 
73
+ def _create(self, request: dict[str, Any]) -> Any:
74
+ client = self._get_client()
75
+ if self._temperature is None:
76
+ return client.messages.create(**request)
77
+ try:
78
+ return client.messages.create(temperature=self._temperature, **request)
79
+ except Exception as exc:
80
+ if not _is_temperature_rejected(exc):
81
+ raise
82
+ # drop it for this provider so later calls skip the rejected param, no wasted retry
83
+ self._temperature = None
84
+ return client.messages.create(**request)
85
+
86
+
87
+ def _is_temperature_rejected(exc: Exception) -> bool:
88
+ """True when the API refused the call only because this model does not accept the
89
+ temperature param, the one error recovered from by dropping it. Matched on the message,
90
+ not a model name list, so a new reasoning model needs no code change."""
91
+ status = getattr(exc, "status_code", None)
92
+ if status != 400 and "BadRequest" not in type(exc).__name__:
93
+ return False
94
+ return "temperature" in str(exc).lower()
95
+
66
96
 
67
97
  def _extract_text(response: Any) -> str:
68
98
  content = getattr(response, "content", None)
@@ -13,7 +13,7 @@ from __future__ import annotations
13
13
  from typing import Any, Callable
14
14
 
15
15
  from codejury.providers.base import CompletionResult, Message, Provider
16
- from codejury.providers.openai_format import choice_text
16
+ from codejury.providers.chat_format import choice_text
17
17
 
18
18
 
19
19
  class LiteLLMProvider(Provider):
@@ -12,7 +12,7 @@ from __future__ import annotations
12
12
  from typing import Any
13
13
 
14
14
  from codejury.providers.base import CompletionResult, Message, Provider
15
- from codejury.providers.openai_format import choice_text
15
+ from codejury.providers.chat_format import choice_text
16
16
 
17
17
 
18
18
  class OpenAIProvider(Provider):
@@ -1,6 +1,7 @@
1
- """Standard diff-audit engine: one model call over a diff, parsed into Findings.
1
+ """Standard diff audit: one model call over a diff, parsed into Findings. The single-call
2
+ unit the orchestrator in engine.py drives.
2
3
 
3
- The cheap, balanced default. The adversarial Finder/Challenger/Judge engine
4
+ The cheap, balanced default. The adversarial Finder/Challenger/Judge runner
4
5
  builds on the same Finding domain for the cases that need higher
5
6
  coverage and lower false positives.
6
7
  """
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
  import dataclasses
12
12
 
13
13
  from codejury.review.diff.adversarial import AdversarialAuditRunner
14
- from codejury.review.diff.engine import AuditRunner
14
+ from codejury.review.diff.audit import AuditRunner
15
15
  from codejury.review.diff.filter import FindingsFilter
16
16
  from codejury.review.diff.vulnerabilities import allowed_categories, normalize_category
17
17
  from codejury.finding import Finding