codejury 1.3.0__tar.gz → 1.4.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-1.3.0 → codejury-1.4.0}/PKG-INFO +6 -4
- {codejury-1.3.0 → codejury-1.4.0}/README.md +5 -3
- {codejury-1.3.0 → codejury-1.4.0}/codejury/cli.py +61 -33
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/base.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/__init__.py +10 -3
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/facts/slither.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/index.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/access-control.md +10 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/accounting-precision.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/bad-randomness.md +7 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/denial-of-service.md +2 -1
- codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/front-running.md +50 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/oracle-price-manipulation.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/proxy-delegatecall.md +1 -0
- codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/reentrancy.md +61 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/signature-replay.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/unchecked-low-level-call.md +8 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/unsafe-math.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/weird-erc20.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/playbook/slash-command.md +36 -2
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/playbook/unit-review.md +5 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/poc.py +2 -2
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/__init__.py +32 -4
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/django.md +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/flask.md +6 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/go.md +6 -0
- codejury-1.4.0/codejury/domains/web/knowledge/guides/protocols/graphql.md +62 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/index.md +3 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/business-logic.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/code-injection.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/command-injection.md +11 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/cors-misconfiguration.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-request-forgery.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-scripting.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/hardcoded-secrets.md +1 -0
- codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/http-request-smuggling.md +49 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/http-response-splitting.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/improper-authentication.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/information-exposure.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-cryptography.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-deserialization.md +1 -0
- codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/insecure-direct-object-reference.md +32 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-session-management.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-transport.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/jwt-validation.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/mass-assignment.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/missing-authorization.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/nosql-injection.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/open-redirect.md +1 -0
- codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/path-traversal.md +41 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/prototype-pollution.md +2 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/race-condition.md +7 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/replay-attack.md +1 -0
- codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/resource-exhaustion.md +45 -0
- codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/security-misconfiguration.md +48 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-request-forgery.md +13 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-template-injection.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/sql-injection.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/unrestricted-file-upload.md +1 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/xml-external-entity.md +6 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/playbook/slash-command.md +36 -2
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/playbook/unit-review.md +6 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/diff/adversarial.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/diff/engine.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/engine.py +61 -58
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/model.py +59 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/reviewer.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/scaffold.py +51 -17
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/union.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/verifier.py +2 -2
- {codejury-1.3.0 → codejury-1.4.0}/codejury.egg-info/PKG-INFO +6 -4
- {codejury-1.3.0 → codejury-1.4.0}/codejury.egg-info/SOURCES.txt +5 -0
- {codejury-1.3.0 → codejury-1.4.0}/pyproject.toml +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_anthropic_provider.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_claude_agent_provider.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_cli.py +6 -3
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_diff_adversarial.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_domains.py +84 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_evals.py +171 -5
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_litellm_provider.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_openai_provider.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_engine.py +52 -6
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_scaffold.py +19 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_union.py +1 -1
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_vulnerabilities.py +5 -3
- codejury-1.3.0/codejury/domains/evm/knowledge/vulnerabilities/reentrancy.md +0 -38
- codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/insecure-direct-object-reference.md +0 -25
- codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/path-traversal.md +0 -35
- {codejury-1.3.0 → codejury-1.4.0}/LICENSE +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/__init__.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/__main__.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/detection.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/__init__.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/detection.yaml +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/facts/__init__.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/facts/call_path.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/guides/languages/solidity.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/guides/protocols/defi-primitives.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/guides/protocols/erc-standards.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/playbook/false-positive-traps.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/playbook/methodology.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/playbook/severity-rubric.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/registry.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/detection.yaml +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/go/echo.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/go/gin.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/express.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/nestjs.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/celery.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/fastapi.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/javascript.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/python.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/typescript.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/protocols/oauth.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/playbook/false-positive-traps.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/playbook/methodology.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/domains/web/playbook/severity-rubric.md +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/finding.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/guides.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/json_parse.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/markdown_docs.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/__init__.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/anthropic.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/base.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/chat_format.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/claude_agent.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/factory.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/litellm.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/mock.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/openai.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/providers/retry.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/report.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/resources.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/__init__.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/diff/__init__.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/diff/audit.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/diff/filter.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/diff/prompts.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/diff/vulnerabilities.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/__init__.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/agent.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/gate.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/pass_loop.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/paths.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/severity.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/review/repo/shapes.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury/severity.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/setup.cfg +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_chat_format.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_detection.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_diff_audit.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_guides.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_json_parse.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_markdown_docs.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_provider_factory.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_agent.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_gate.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_model.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_pass_loop.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_paths.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_severity.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_repo_verifier.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_report.py +0 -0
- {codejury-1.3.0 → codejury-1.4.0}/tests/test_retry_provider.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: AI-assisted security review for code diffs and whole repositories.
|
|
5
5
|
Author: AISecLabs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -84,8 +84,10 @@ codejury install-slash-command # Claude Code
|
|
|
84
84
|
codejury install-slash-command --agent codex # Codex
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
`install-slash-command` copies `/codejury-review
|
|
88
|
-
directory.
|
|
87
|
+
`install-slash-command` copies `/codejury-review` into the selected agent's command
|
|
88
|
+
directory. The one command dispatches by argument: a directory runs the Repo Review
|
|
89
|
+
fan-out, a diff file or git range runs the coded Diff Review. Pass `--dir` to install it
|
|
90
|
+
somewhere else.
|
|
89
91
|
|
|
90
92
|
## Configure a Model Backend
|
|
91
93
|
|
|
@@ -232,7 +234,7 @@ _pocs.md PoC reconciliation, planned versus delivered
|
|
|
232
234
|
Then run the interactive slash command in Claude Code or Codex:
|
|
233
235
|
|
|
234
236
|
```text
|
|
235
|
-
/codejury-review
|
|
237
|
+
/codejury-review /path/to/repo
|
|
236
238
|
```
|
|
237
239
|
|
|
238
240
|
The agent maps the attack surface, fills the authorization model, runs one focused
|
|
@@ -42,8 +42,10 @@ codejury install-slash-command # Claude Code
|
|
|
42
42
|
codejury install-slash-command --agent codex # Codex
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
`install-slash-command` copies `/codejury-review
|
|
46
|
-
directory.
|
|
45
|
+
`install-slash-command` copies `/codejury-review` into the selected agent's command
|
|
46
|
+
directory. The one command dispatches by argument: a directory runs the Repo Review
|
|
47
|
+
fan-out, a diff file or git range runs the coded Diff Review. Pass `--dir` to install it
|
|
48
|
+
somewhere else.
|
|
47
49
|
|
|
48
50
|
## Configure a Model Backend
|
|
49
51
|
|
|
@@ -190,7 +192,7 @@ _pocs.md PoC reconciliation, planned versus delivered
|
|
|
190
192
|
Then run the interactive slash command in Claude Code or Codex:
|
|
191
193
|
|
|
192
194
|
```text
|
|
193
|
-
/codejury-review
|
|
195
|
+
/codejury-review /path/to/repo
|
|
194
196
|
```
|
|
195
197
|
|
|
196
198
|
The agent maps the attack surface, fills the authorization model, runs one focused
|
|
@@ -366,8 +366,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
366
366
|
_add_role_backend_args(roles, role)
|
|
367
367
|
|
|
368
368
|
tuning = repo.add_argument_group("run tuning (advanced)", "only affect --run, sane defaults otherwise")
|
|
369
|
-
tuning.add_argument("--max-passes", type=int, default=
|
|
370
|
-
help="cap on diverse passes before stopping"
|
|
369
|
+
tuning.add_argument("--max-passes", type=int, default=None, dest="max_passes",
|
|
370
|
+
help="cap on diverse passes before stopping, default scales to the domain: "
|
|
371
|
+
"(min-lens-shots + 1) * number of lenses, so every lens meets its shot "
|
|
372
|
+
"floor with a cycle of headroom for convergence")
|
|
371
373
|
tuning.add_argument("--converge-after", type=int, default=2, dest="converge_after",
|
|
372
374
|
help="stop once this many consecutive passes add no new finding")
|
|
373
375
|
tuning.add_argument("--min-lens-shots", type=int, default=2, dest="min_lens_shots",
|
|
@@ -382,12 +384,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
382
384
|
_add_domain_arg(repo)
|
|
383
385
|
|
|
384
386
|
inst = sub.add_parser("install-slash-command",
|
|
385
|
-
help="install the /codejury-review
|
|
387
|
+
help="install the /codejury-review slash command for an agent")
|
|
386
388
|
inst.add_argument("--agent", choices=("claude", "codex"), default="claude",
|
|
387
389
|
help="which agent's command directory to install into")
|
|
388
390
|
inst.add_argument("--dir", default=None, help="explicit target directory, overrides --agent")
|
|
389
391
|
inst.add_argument("--force", action="store_true",
|
|
390
|
-
help="overwrite an existing codejury-review
|
|
392
|
+
help="overwrite an existing codejury-review.md at the destination")
|
|
391
393
|
inst.add_argument("--domain", default="web", metavar="DOMAIN",
|
|
392
394
|
help="which domain's slash command to install, one of: " + ", ".join(available_domains()))
|
|
393
395
|
|
|
@@ -411,6 +413,49 @@ def _diff_provider(args, spec, kind: str):
|
|
|
411
413
|
return _role_provider(args, spec)
|
|
412
414
|
|
|
413
415
|
|
|
416
|
+
def build_diff_providers(args):
|
|
417
|
+
"""Resolve the diff seats into providers exactly as `review diff` does, so a non-CLI caller
|
|
418
|
+
such as the eval runs a case through the same wiring a user gets. Returns the base provider
|
|
419
|
+
and model the audit needs plus the per-role finder, challenger, and judge providers and
|
|
420
|
+
models, the role fields None in standard mode. The single source the CLI and the eval share,
|
|
421
|
+
so the probe cannot drift from the product on which model or seat reviews a diff."""
|
|
422
|
+
base = _base_spec(args)
|
|
423
|
+
if args.mode == "adversarial":
|
|
424
|
+
roles = {r: _role_spec(args, r, base) for r in ("finder", "challenger", "judge")}
|
|
425
|
+
kinds = {r: _seat_backend(s, args.executor) for r, s in roles.items()}
|
|
426
|
+
agent_roles = [r for r, k in kinds.items() if k == "agent"]
|
|
427
|
+
_warn_roles_under_agent(args, agent_roles)
|
|
428
|
+
if args.executor == "auto":
|
|
429
|
+
_note_subscription_fallback(agent_roles)
|
|
430
|
+
fp = _diff_provider(args, roles["finder"], kinds["finder"])
|
|
431
|
+
cp = _diff_provider(args, roles["challenger"], kinds["challenger"])
|
|
432
|
+
jp = _diff_provider(args, roles["judge"], kinds["judge"])
|
|
433
|
+
return (fp, roles["finder"]["model"], fp, roles["finder"]["model"],
|
|
434
|
+
cp, roles["challenger"]["model"], jp, roles["judge"]["model"])
|
|
435
|
+
base_kind = _seat_backend(base, args.executor)
|
|
436
|
+
if args.executor == "auto" and base_kind == "agent":
|
|
437
|
+
_note_subscription_fallback(("audit",))
|
|
438
|
+
return (_diff_provider(args, base, base_kind), base["model"], None, None, None, None, None, None)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def diff_args_from_env(mode: str, *, executor: str = "auto", rounds: int = 3):
|
|
442
|
+
"""A diff args namespace from the environment defaults, the same values `review diff` reads
|
|
443
|
+
when no flag is passed, so `build_diff_providers` builds the user's real wiring. Lets the eval
|
|
444
|
+
drive the audit through the product path rather than a hardcoded provider."""
|
|
445
|
+
from types import SimpleNamespace
|
|
446
|
+
ns = dict(provider=DEFAULT_PROVIDER, model=DEFAULT_MODEL, api_key=DEFAULT_API_KEY,
|
|
447
|
+
api_base=DEFAULT_API_BASE, retries=DEFAULT_RETRIES, timeout=DEFAULT_TIMEOUT,
|
|
448
|
+
executor=executor, mode=mode, rounds=rounds)
|
|
449
|
+
for role in ROLES:
|
|
450
|
+
d = DEFAULT_ROLE_BACKENDS[role]
|
|
451
|
+
ns[f"{role}_provider"] = d["provider"]
|
|
452
|
+
ns[f"{role}_model"] = d["model"]
|
|
453
|
+
ns[f"{role}_api_key"] = d["api_key"]
|
|
454
|
+
ns[f"{role}_api_base"] = d["api_base"]
|
|
455
|
+
ns[f"{role}_wire_api"] = d["wire_api"]
|
|
456
|
+
return SimpleNamespace(**ns)
|
|
457
|
+
|
|
458
|
+
|
|
414
459
|
def _dispatch(args, parser) -> int:
|
|
415
460
|
scope = getattr(args, "scope", None)
|
|
416
461
|
if args.command == "review" and scope == "diff":
|
|
@@ -424,29 +469,8 @@ def _dispatch(args, parser) -> int:
|
|
|
424
469
|
else:
|
|
425
470
|
diff = _read_diff(args)
|
|
426
471
|
domain = resolve_domain(args.domain, _diff_paths(diff))
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
# each seat resolves on its own key, so a keyless Claude finder and judge ride the
|
|
430
|
-
# subscription while an OpenAI challenger uses its own key. The finder backs the base
|
|
431
|
-
# default audit_diff needs, the per-role providers below override it field by field
|
|
432
|
-
roles = {r: _role_spec(args, r, base) for r in ("finder", "challenger", "judge")}
|
|
433
|
-
kinds = {r: _seat_backend(s, args.executor) for r, s in roles.items()}
|
|
434
|
-
agent_roles = [r for r, k in kinds.items() if k == "agent"]
|
|
435
|
-
_warn_roles_under_agent(args, agent_roles)
|
|
436
|
-
if args.executor == "auto":
|
|
437
|
-
_note_subscription_fallback(agent_roles)
|
|
438
|
-
finder_provider = _diff_provider(args, roles["finder"], kinds["finder"])
|
|
439
|
-
challenger_provider = _diff_provider(args, roles["challenger"], kinds["challenger"])
|
|
440
|
-
judge_provider = _diff_provider(args, roles["judge"], kinds["judge"])
|
|
441
|
-
finder_model = roles["finder"]["model"]
|
|
442
|
-
challenger_model = roles["challenger"]["model"]
|
|
443
|
-
judge_model = roles["judge"]["model"]
|
|
444
|
-
provider, model = finder_provider, roles["finder"]["model"]
|
|
445
|
-
else:
|
|
446
|
-
base_kind = _seat_backend(base, args.executor)
|
|
447
|
-
if args.executor == "auto" and base_kind == "agent":
|
|
448
|
-
_note_subscription_fallback(("audit",))
|
|
449
|
-
provider, model = _diff_provider(args, base, base_kind), base["model"]
|
|
472
|
+
(provider, model, finder_provider, finder_model,
|
|
473
|
+
challenger_provider, challenger_model, judge_provider, judge_model) = build_diff_providers(args)
|
|
450
474
|
kept, _, degraded = audit_diff(
|
|
451
475
|
diff, provider=provider, model=model,
|
|
452
476
|
mode=args.mode, max_rounds=args.rounds, filter_findings=not args.no_filter,
|
|
@@ -457,7 +481,7 @@ def _dispatch(args, parser) -> int:
|
|
|
457
481
|
print(render(args.fmt, kept))
|
|
458
482
|
if degraded:
|
|
459
483
|
# the adversarial judge was unusable and the result fell back to the
|
|
460
|
-
# unjudged set, so this is a failed audit, not a clean pass, invariant
|
|
484
|
+
# unjudged set, so this is a failed audit, not a clean pass, invariant 4
|
|
461
485
|
print("error: the adversarial audit degraded on an unusable judge reply, "
|
|
462
486
|
"the result is incomplete and not a clean pass", file=sys.stderr)
|
|
463
487
|
return 1 if degraded or gate(kept, args.fail_on) else 0
|
|
@@ -518,13 +542,17 @@ def _dispatch(args, parser) -> int:
|
|
|
518
542
|
f"PoC reconciliation in {fr.workspace}/_pocs.md")
|
|
519
543
|
if fr.verify and fr.verify.errors:
|
|
520
544
|
print(f"WARNING: {fr.verify.errors} verification calls failed. Re-run to resume.", file=sys.stderr)
|
|
521
|
-
return 1 # fail loud: an incomplete verification is not a clean finalize, invariant
|
|
545
|
+
return 1 # fail loud: an incomplete verification is not a clean finalize, invariant 4
|
|
522
546
|
return 0
|
|
523
547
|
|
|
524
548
|
if args.command == "review" and scope == "repo" and args.run:
|
|
525
549
|
from codejury.review.repo.engine import run_repo_review
|
|
526
550
|
from codejury.review.repo.verifier import ModelVerifier
|
|
527
551
|
domain = resolve_domain(args.domain, _repo_file_names(args.directory))
|
|
552
|
+
# scale the pass cap to the domain, so the min-lens-shots floor is always meetable and the
|
|
553
|
+
# convergence early-stop can fire, with one lens cycle of headroom above the floor
|
|
554
|
+
if args.max_passes is None:
|
|
555
|
+
args.max_passes = (args.min_lens_shots + 1) * len(domain.lenses)
|
|
528
556
|
_warn_secondary_env()
|
|
529
557
|
base = _base_spec(args)
|
|
530
558
|
finder = _role_spec(args, "finder", base)
|
|
@@ -600,7 +628,7 @@ def _dispatch(args, parser) -> int:
|
|
|
600
628
|
file=sys.stderr)
|
|
601
629
|
print(f"Findings written to {res.scaffold.workspace}/findings/ and {res.scaffold.workspace}/findings.json")
|
|
602
630
|
# fail loud: a partial run or a run still finding issues at the cap must not exit clean,
|
|
603
|
-
# invariant
|
|
631
|
+
# invariant 4 and the stability red line, so a non-converged run is not reported as done
|
|
604
632
|
return 1 if failures or not acc.converged else 0
|
|
605
633
|
|
|
606
634
|
if args.command == "review" and scope == "repo":
|
|
@@ -633,7 +661,7 @@ def _dispatch(args, parser) -> int:
|
|
|
633
661
|
print(
|
|
634
662
|
"This command sets up the review, it does not find anything itself. Next, have an "
|
|
635
663
|
f"interactive agent follow {res.workspace}/METHODOLOGY.md to run the review, or use the "
|
|
636
|
-
"/codejury-review
|
|
664
|
+
"/codejury-review command in Claude Code or Codex. The agent proposes findings in "
|
|
637
665
|
f"{res.workspace}/candidates/, finalize confirms them into {res.workspace}/findings/."
|
|
638
666
|
)
|
|
639
667
|
return 0
|
|
@@ -645,14 +673,14 @@ def _dispatch(args, parser) -> int:
|
|
|
645
673
|
"codex": Path.home() / ".codex" / "prompts",
|
|
646
674
|
}
|
|
647
675
|
target_dir = Path(args.dir) if args.dir else agent_dirs[args.agent]
|
|
648
|
-
dst = target_dir / "codejury-review
|
|
676
|
+
dst = target_dir / "codejury-review.md"
|
|
649
677
|
if dst.exists() and not args.force:
|
|
650
678
|
print(f"{dst} already exists. Re-run with --force to overwrite it.", file=sys.stderr)
|
|
651
679
|
return 1
|
|
652
680
|
target_dir.mkdir(parents=True, exist_ok=True)
|
|
653
681
|
dst.write_text(slash_command_file.read_text(encoding="utf-8"), encoding="utf-8")
|
|
654
682
|
print(f"Installed slash command to {dst}")
|
|
655
|
-
print("Run it in the agent with: /codejury-review
|
|
683
|
+
print("Run it in the agent with: /codejury-review <repository or diff>")
|
|
656
684
|
return 0
|
|
657
685
|
|
|
658
686
|
if args.command == "review":
|
|
@@ -97,7 +97,7 @@ class Domain:
|
|
|
97
97
|
class BackendUnavailable(RuntimeError):
|
|
98
98
|
"""A tool-backed seam was asked to work but its external tool is not installed. Raised,
|
|
99
99
|
never swallowed into an empty result, so a missing toolchain is a loud failure and not a
|
|
100
|
-
silently clean review, invariant
|
|
100
|
+
silently clean review, invariant 4."""
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
@dataclass(frozen=True, kw_only=True)
|
|
@@ -13,15 +13,22 @@ from pathlib import Path
|
|
|
13
13
|
from codejury.domains.base import Domain
|
|
14
14
|
from codejury.domains.evm.facts.slither import SlitherFacts
|
|
15
15
|
|
|
16
|
-
# the repo-review pass lenses for contracts: each pass leads with one, the empty lens
|
|
17
|
-
# reviews every class
|
|
16
|
+
# the repo-review pass lenses for contracts: each pass leads with one class, the empty lens
|
|
17
|
+
# reviews every class. A named lens is a reliable focused pass and the empty catch-all is not,
|
|
18
|
+
# so every shipped contract class gets its own lens rather than relying on the catch-all.
|
|
18
19
|
EVM_LENSES = (
|
|
19
20
|
"access-control",
|
|
20
21
|
"reentrancy",
|
|
21
|
-
"oracle-manipulation",
|
|
22
|
+
"oracle-price-manipulation",
|
|
22
23
|
"accounting-precision",
|
|
23
24
|
"signature-replay",
|
|
24
25
|
"denial-of-service",
|
|
26
|
+
"bad-randomness",
|
|
27
|
+
"front-running",
|
|
28
|
+
"proxy-delegatecall",
|
|
29
|
+
"unchecked-low-level-call",
|
|
30
|
+
"unsafe-math",
|
|
31
|
+
"weird-erc20",
|
|
25
32
|
"",
|
|
26
33
|
)
|
|
27
34
|
|
|
@@ -4,7 +4,7 @@ Solidity compiler, availability is lazy-checked so importing the domain never ne
|
|
|
4
4
|
heavy dependency, and slither itself is imported only inside extract.
|
|
5
5
|
|
|
6
6
|
A backend that cannot run fails loud rather than returning empty facts that would read as a
|
|
7
|
-
clean review, invariant
|
|
7
|
+
clean review, invariant 4. A missing toolchain raises BackendUnavailable, a compile error
|
|
8
8
|
in the target propagates as the native slither error.
|
|
9
9
|
"""
|
|
10
10
|
|
|
@@ -19,6 +19,7 @@ the target. A finding's `category` is one of these ids.
|
|
|
19
19
|
|
|
20
20
|
### Economic and Accounting
|
|
21
21
|
- `oracle-price-manipulation` spot price or balance as price, flash-loan assisted
|
|
22
|
+
- `front-running` no minOut or deadline, sandwich, profitable action front-run
|
|
22
23
|
- `accounting-precision` rounding, division before multiplication, ERC-4626 first-depositor inflation
|
|
23
24
|
- `weird-erc20` fee-on-transfer, rebasing, ERC777 callback, received below the requested amount
|
|
24
25
|
- `unsafe-math` unchecked-block overflow, narrowing downcast truncation
|
{codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/access-control.md
RENAMED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: access-control
|
|
3
3
|
title: Missing or Broken Access Control
|
|
4
|
+
lens: access-control
|
|
4
5
|
impact: CRITICAL
|
|
5
6
|
tags: [swc-105, swc-115, access-control, fund-loss]
|
|
6
7
|
aliases: [missing-access-control, broken-access-control]
|
|
7
|
-
triggers: ["onlyOwner", "function mint", "function burn", "function withdraw", "selfdestruct", "tx.origin", "require(msg.sender", "_mint", "setOwner", "transferOwnership", "external", "public"]
|
|
8
|
+
triggers: ["onlyOwner", "function mint", "function burn", "function withdraw", "selfdestruct", "tx.origin", "require(msg.sender", "_mint", "setOwner", "transferOwnership", "function approve", "blacklist", "isBlacklisted", "whenNotPaused", "external", "public"]
|
|
8
9
|
---
|
|
9
10
|
|
|
10
11
|
## Missing or Broken Access Control
|
|
@@ -16,6 +17,14 @@ The cause is a missing modifier, a modifier on the wrong function, an authorizat
|
|
|
16
17
|
but not the right one. Gate every state-changing privileged function to the exact role,
|
|
17
18
|
and use `msg.sender`.
|
|
18
19
|
|
|
20
|
+
A security gate that some entrypoints enforce and a sibling omits is a broken invariant,
|
|
21
|
+
report the gap even when the omitted path does not itself move value. A token with a
|
|
22
|
+
blacklist, pause, or sanctions check that runs on `transfer` and `transferFrom` but not on
|
|
23
|
+
`approve`, `permit`, or `increaseAllowance` lets a barred account still take part in the
|
|
24
|
+
flow, so the control is not the invariant it claims. Enumerate the sibling entrypoints a
|
|
25
|
+
stated invariant should cover and name the one missing the check, do not clear it because a
|
|
26
|
+
later step looks gated.
|
|
27
|
+
|
|
19
28
|
### Vulnerable
|
|
20
29
|
```solidity
|
|
21
30
|
function mint(address to, uint256 amount) external { // no access control
|
{codejury-1.3.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/bad-randomness.md
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: bad-randomness
|
|
3
3
|
title: Insecure Randomness
|
|
4
|
+
lens: bad-randomness
|
|
4
5
|
impact: HIGH
|
|
5
6
|
tags: [swc-120, randomness, fund-loss]
|
|
6
7
|
aliases: [weak-randomness, predictable-random]
|
|
@@ -16,6 +17,12 @@ a validator can influence or withhold it, so the result is gamed and the payout
|
|
|
16
17
|
Use a verifiable randomness source such as a VRF, or a commit-reveal scheme where the seed
|
|
17
18
|
is fixed before it can be known.
|
|
18
19
|
|
|
20
|
+
The timestamp-dependence variant is the same root cause applied to time, not selection: a
|
|
21
|
+
proposer can nudge `block.timestamp` by a few seconds, so logic that gates an auction close,
|
|
22
|
+
a vesting unlock, a deadline, or a reward rate on an exact `block.timestamp` comparison is
|
|
23
|
+
manipulable at the margin. Use a tolerance band, a block-number gate, or an oracle time
|
|
24
|
+
rather than trusting `block.timestamp` to the second.
|
|
25
|
+
|
|
19
26
|
### Vulnerable
|
|
20
27
|
```solidity
|
|
21
28
|
function drawWinner(address[] calldata players) external {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: denial-of-service
|
|
3
3
|
title: Denial of Service
|
|
4
|
+
lens: denial-of-service
|
|
4
5
|
impact: HIGH
|
|
5
|
-
tags: [swc-113, swc-128, dos, griefing, gas]
|
|
6
|
+
tags: [swc-113, swc-128, dos, griefing, gas, fund-loss]
|
|
6
7
|
aliases: [dos]
|
|
7
8
|
triggers: ["for (", "while (", ".length", "push(", "transfer(", "revert", "external call", "unbounded", "for(uint", "selfdestruct"]
|
|
8
9
|
---
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: front-running
|
|
3
|
+
title: Front-Running and Slippage
|
|
4
|
+
lens: front-running
|
|
5
|
+
impact: HIGH
|
|
6
|
+
tags: [swc-114, front-running, mev, sandwich, slippage, fund-loss]
|
|
7
|
+
aliases: [mev, sandwich, slippage, frontrunning, front-run]
|
|
8
|
+
triggers: ["swap", "minOut", "amountOutMin", "minAmountOut", "amountOutMinimum", "deadline", "getAmountOut", "addLiquidity", "removeLiquidity", "swapExactTokensForTokens", "0, path", "claim", "harvest"]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Front-Running and Slippage
|
|
12
|
+
|
|
13
|
+
A state-changing action is ordered against the victim in the mempool. A swap, deposit,
|
|
14
|
+
or redeem that enforces no minimum output and no deadline executes at an attacker-chosen
|
|
15
|
+
price, or a profitable action is observed and front-run. The classic shape is a sandwich,
|
|
16
|
+
the attacker buys ahead of the victim, lets the victim's trade move the price, then sells
|
|
17
|
+
into it. Bind every trade with a caller-supplied `minOut` and a `deadline`, and treat any
|
|
18
|
+
value the transaction derives from a movable on-chain source as untrusted, see the
|
|
19
|
+
oracle-price-manipulation class.
|
|
20
|
+
|
|
21
|
+
### Vulnerable
|
|
22
|
+
```solidity
|
|
23
|
+
function zapIn(uint256 amountIn) external {
|
|
24
|
+
// minOut hardcoded to 0 and block.timestamp as the deadline, so a sandwich sets the price
|
|
25
|
+
uint256[] memory out = router.swapExactTokensForTokens(
|
|
26
|
+
amountIn, 0, path, address(this), block.timestamp
|
|
27
|
+
);
|
|
28
|
+
deposit(out[out.length - 1]);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Secure
|
|
33
|
+
```solidity
|
|
34
|
+
function zapIn(uint256 amountIn, uint256 minOut, uint256 deadline) external {
|
|
35
|
+
uint256[] memory out = router.swapExactTokensForTokens(
|
|
36
|
+
amountIn, minOut, path, address(this), deadline
|
|
37
|
+
);
|
|
38
|
+
deposit(out[out.length - 1]);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Not a Finding
|
|
43
|
+
|
|
44
|
+
A trade that passes the caller's `minOut` and `deadline` straight through to the router
|
|
45
|
+
is the expected control and is not reportable. Report it only when the slippage bound is
|
|
46
|
+
hardcoded to 0, the deadline is replaced with `block.timestamp` so it can never expire,
|
|
47
|
+
`minOut` is computed from a spot price read in the same call, or a privileged action's
|
|
48
|
+
ordering grants a concrete and quantifiable fund advantage. A pure view function, an
|
|
49
|
+
action with no economic ordering advantage, or generic mempool-visibility commentary is
|
|
50
|
+
not a finding.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: reentrancy
|
|
3
|
+
title: Reentrancy
|
|
4
|
+
lens: reentrancy
|
|
5
|
+
impact: CRITICAL
|
|
6
|
+
tags: [swc-107, reentrancy, fund-loss]
|
|
7
|
+
aliases: [read-only-reentrancy]
|
|
8
|
+
triggers: [".call{value", ".call(", "transfer(", "send(", "external call", "balances[", "withdraw", "nonReentrant", "safeTransfer", "onERC721Received", "tokensToSend", "tokensReceived", "ERC777", "buyout", "before state", "get_virtual_price", "getReserves", "getRate", "sharePrice", "view returns", "balanceOf(address(this))"]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Reentrancy
|
|
12
|
+
|
|
13
|
+
An external call hands control to the callee before the contract finishes updating its
|
|
14
|
+
own state, so the callee can call back in and act on the stale pre-update state. The
|
|
15
|
+
classic form drains a balance by re-entering a withdraw before the balance is zeroed.
|
|
16
|
+
Cross-function reentrancy re-enters a different function that shares the same state, and
|
|
17
|
+
read-only reentrancy reads a view mid-update from another protocol. The read-only form
|
|
18
|
+
needs no state write in the reentered call: a price or share view such as a Curve
|
|
19
|
+
`get_virtual_price`, a Balancer `getRate`, or a `getReserves` or `balanceOf(address(this))`
|
|
20
|
+
spot read returns a stale value while a withdraw or exit has sent value but not yet synced
|
|
21
|
+
its reserves, and a consumer that prices collateral off that view is fooled. Write state
|
|
22
|
+
before the external call, or guard with `nonReentrant`, and remember a guard on the mutating
|
|
23
|
+
function does not stop the cross-contract read-only form, the view must be consistent at the
|
|
24
|
+
moment of the external call.
|
|
25
|
+
|
|
26
|
+
A token transfer is an external call too. An ERC777 token runs a `tokensToSend` hook on
|
|
27
|
+
the sender and a `tokensReceived` hook on the recipient, and other tokens add their own
|
|
28
|
+
transfer callback, so a plain `transfer` or `safeTransfer` of an ERC20 can hand control to
|
|
29
|
+
an attacker exactly like a raw `call`. Treat any token whose address is not a fixed trusted
|
|
30
|
+
constant as possibly hook bearing and check every token-move path for reentrancy, not only
|
|
31
|
+
the `call{value}` paths. Clearing a path because the token is assumed to be a normal ERC20
|
|
32
|
+
with no hook is an assumed off-file control unless the token set is pinned to a known
|
|
33
|
+
allowlist, so keep the finding, see the recall red line.
|
|
34
|
+
|
|
35
|
+
Ordering effects before interactions in the one function under review is not enough on its
|
|
36
|
+
own. A transfer hook can reenter a different function that reads or writes the same position,
|
|
37
|
+
loan, or reserve while this flow is only half finished, the cross-function form, so trace
|
|
38
|
+
where control can go during every transfer and check the whole flow's invariants, not just
|
|
39
|
+
the local ones. In a lending, auction, or buyout flow that pays the current holder before it
|
|
40
|
+
finalizes the position's terms, that payout is the callback window, so audit the order of
|
|
41
|
+
the payout against the state writes.
|
|
42
|
+
|
|
43
|
+
### Vulnerable
|
|
44
|
+
```solidity
|
|
45
|
+
function withdraw() external {
|
|
46
|
+
uint256 bal = balances[msg.sender];
|
|
47
|
+
(bool ok, ) = msg.sender.call{value: bal}(""); // call before the state update
|
|
48
|
+
require(ok);
|
|
49
|
+
balances[msg.sender] = 0; // too late, attacker reentered
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Secure
|
|
54
|
+
```solidity
|
|
55
|
+
function withdraw() external nonReentrant {
|
|
56
|
+
uint256 bal = balances[msg.sender];
|
|
57
|
+
balances[msg.sender] = 0; // effects before interaction
|
|
58
|
+
(bool ok, ) = msg.sender.call{value: bal}("");
|
|
59
|
+
require(ok);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
id: unchecked-low-level-call
|
|
3
3
|
title: Unchecked Low-Level Call
|
|
4
|
+
lens: unchecked-low-level-call
|
|
4
5
|
impact: HIGH
|
|
5
6
|
tags: [swc-104, low-level-call, return-value, fund-loss]
|
|
6
7
|
aliases: [unchecked-call, unchecked-return]
|
|
7
|
-
triggers: [".call(", ".call{value", ".delegatecall(", ".send(", "transfer(", "bool success", "bool ok", "(bool", "safeTransfer", "returndata"]
|
|
8
|
+
triggers: [".call(", ".call{value", ".delegatecall(", ".send(", "transfer(", "bool success", "bool ok", "(bool", "safeTransfer", "returndata", "returndatacopy", "abi.decode(returndata", "excessivelySafeCall"]
|
|
8
9
|
---
|
|
9
10
|
|
|
10
11
|
## Unchecked Low-Level Call
|
|
@@ -17,6 +18,12 @@ raw ERC-20 `transfer` on a token that returns false on failure, or returns nothi
|
|
|
17
18
|
be checked or wrapped with SafeERC20. Check every low-level return, or use a wrapper that
|
|
18
19
|
reverts.
|
|
19
20
|
|
|
21
|
+
The mirror risk is trusting the returned data rather than the boolean: a callee can return
|
|
22
|
+
an enormous bytes blob so that copying it, an implicit `returndatacopy` or an
|
|
23
|
+
`abi.decode(returndata, ...)`, burns all forwarded gas and griefs the caller, the return-bomb.
|
|
24
|
+
When the return payload is attacker-influenced, cap the copied size or use an
|
|
25
|
+
`excessivelySafeCall` style helper, and never let a callee's return data dictate gas.
|
|
26
|
+
|
|
20
27
|
### Vulnerable
|
|
21
28
|
```solidity
|
|
22
29
|
function payout(address to, uint256 amount) external {
|
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Run a codejury
|
|
2
|
+
description: Run a codejury smart contract security review of a diff or a whole repository
|
|
3
3
|
---
|
|
4
|
-
Run a codejury
|
|
4
|
+
Run a codejury smart contract security review of: $ARGUMENTS
|
|
5
|
+
|
|
6
|
+
First decide which path $ARGUMENTS names, the two are different tools, do not mix them.
|
|
7
|
+
|
|
8
|
+
- DIFF REVIEW when $ARGUMENTS is a unified diff file such as a `.diff` or `.patch`, a single
|
|
9
|
+
file of diff text, or a git range such as `origin/main...HEAD`. This path is fully coded,
|
|
10
|
+
you run one command and relay its report, there is no fan-out and no workspace.
|
|
11
|
+
- REPO REVIEW when $ARGUMENTS is a directory, a whole protocol to audit. This path is the
|
|
12
|
+
fan-out you orchestrate, follow the numbered steps under Repo Review.
|
|
13
|
+
|
|
14
|
+
## Diff Review
|
|
15
|
+
|
|
16
|
+
Run the coded engine and relay its report. There is nothing for you to judge, the engine
|
|
17
|
+
chunks the diff, runs its passes, filters, and prints the findings.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
codejury review diff --file <the diff file> --domain evm
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For a git range instead of a file, drop `--file` and pass the range, with `--repo` if the
|
|
24
|
+
repository is not the current directory:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
codejury review diff --repo <repo dir> --git-range origin/main...HEAD --domain evm
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If `codejury` is not on PATH it is a pip-installed console script, so activate the project
|
|
31
|
+
venv first, for example `. .venv/bin/activate`, or run `python -m codejury`.
|
|
32
|
+
|
|
33
|
+
Relay the report as it prints. A failed, rate-limited, blank, or error-exited run is a
|
|
34
|
+
failed review, not a clean pass, surface the error and never report zero findings from a
|
|
35
|
+
broken run. A non-zero exit means a finding hit the severity gate or the audit degraded, say
|
|
36
|
+
which. Then stop, Diff Review does not use the units, the workspace, or the gate below.
|
|
37
|
+
|
|
38
|
+
## Repo Review
|
|
5
39
|
|
|
6
40
|
You are the orchestrator, not the reviewer. Recall comes from fanning out: the tool gives
|
|
7
41
|
you a deterministic unit worklist, you run one focused sub-review per contract in parallel,
|
|
@@ -64,6 +64,11 @@ of a named guard:
|
|
|
64
64
|
moves more of the holder's balance than was approved.
|
|
65
65
|
- **Signatures**: is a signed privileged message bound to a nonce, a chainid, and a domain
|
|
66
66
|
separator, the signer checked nonzero? A signature alone is replayable.
|
|
67
|
+
- **Failure mode**: when a call or a guard fails, does the path fail closed or fail open? A
|
|
68
|
+
low-level `call`, `delegatecall`, or `send` whose returned success bool is not checked lets
|
|
69
|
+
execution continue as if it succeeded, and a `try/catch` whose catch branch proceeds rather
|
|
70
|
+
than reverting leaves the contract in a partly updated state. Confirm a failed external call
|
|
71
|
+
or check reverts the transaction rather than silently continuing.
|
|
67
72
|
- **Trusted-source**: a value is not safe because a caller you treat as trusted set it, if
|
|
68
73
|
that caller is an arbitrary external account or contract.
|
|
69
74
|
- **Seeded invariant**: `inventory/_invariants.md` may name a property the operator asserts
|
|
@@ -4,13 +4,13 @@ compile and run it, turning a model claim into a reproduced fact. Behind the cod
|
|
|
4
4
|
extra and a Foundry toolchain, availability is lazy-checked so importing the domain never
|
|
5
5
|
needs forge.
|
|
6
6
|
|
|
7
|
-
Safety is the hard contract, invariant
|
|
7
|
+
Safety is the hard contract, invariant 6. A PoC runs only against a local fork or a dev
|
|
8
8
|
node, never a production system. It never holds a real private key, never broadcasts a
|
|
9
9
|
transaction, and takes no destructive action without explicit operator approval.
|
|
10
10
|
|
|
11
11
|
The generation and run logic lands once Foundry is validated on a real target. Until then
|
|
12
12
|
this is the wired seam: it reports availability honestly and fails loud when asked to
|
|
13
|
-
verify without the tool, never silently confirming or refuting a finding, invariant
|
|
13
|
+
verify without the tool, never silently confirming or refuting a finding, invariant 4.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|