codejury 1.2.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.2.0 → codejury-1.4.0}/PKG-INFO +9 -7
- {codejury-1.2.0 → codejury-1.4.0}/README.md +8 -6
- {codejury-1.2.0 → codejury-1.4.0}/codejury/__init__.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/cli.py +62 -34
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/base.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/__init__.py +10 -3
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/facts/slither.py +1 -1
- codejury-1.4.0/codejury/domains/evm/knowledge/guides/protocols/defi-primitives.md +41 -0
- codejury-1.4.0/codejury/domains/evm/knowledge/guides/protocols/erc-standards.md +39 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/index.md +6 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/access-control.md +10 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/accounting-precision.md +1 -0
- codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/bad-randomness.md +41 -0
- {codejury-1.2.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.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/oracle-price-manipulation.md +1 -0
- {codejury-1.2.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.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/signature-replay.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/unchecked-low-level-call.md +8 -1
- codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/unsafe-math.md +32 -0
- codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/weird-erc20.md +43 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/methodology.md +8 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/slash-command.md +42 -5
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/unit-review.md +24 -4
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/poc.py +2 -2
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/__init__.py +32 -4
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/django.md +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/flask.md +6 -1
- {codejury-1.2.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.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/index.md +9 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/business-logic.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/code-injection.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/command-injection.md +11 -1
- codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/cors-misconfiguration.md +46 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-request-forgery.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-scripting.md +1 -0
- {codejury-1.2.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.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/http-response-splitting.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/improper-authentication.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/information-exposure.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-cryptography.md +1 -0
- {codejury-1.2.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.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-session-management.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-transport.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/jwt-validation.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/mass-assignment.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/missing-authorization.md +1 -0
- codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/nosql-injection.md +41 -0
- {codejury-1.2.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.4.0/codejury/domains/web/knowledge/vulnerabilities/prototype-pollution.md +46 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/race-condition.md +9 -3
- {codejury-1.2.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.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-request-forgery.md +13 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-template-injection.md +1 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/sql-injection.md +1 -0
- codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/unrestricted-file-upload.md +47 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/xml-external-entity.md +6 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/methodology.md +9 -3
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/slash-command.md +42 -5
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/unit-review.md +19 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/adversarial.py +2 -2
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/audit.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/engine.py +2 -2
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/prompts.py +2 -2
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/vulnerabilities.py +0 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/engine.py +106 -66
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/model.py +59 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/reviewer.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/scaffold.py +87 -18
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/shapes.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/union.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/verifier.py +2 -2
- {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/PKG-INFO +9 -7
- {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/SOURCES.txt +14 -0
- {codejury-1.2.0 → codejury-1.4.0}/pyproject.toml +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_anthropic_provider.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_claude_agent_provider.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_cli.py +6 -3
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_diff_adversarial.py +2 -2
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_diff_audit.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_domains.py +84 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_evals.py +197 -5
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_litellm_provider.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_openai_provider.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_agent.py +2 -2
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_engine.py +80 -6
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_scaffold.py +33 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_union.py +1 -1
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_vulnerabilities.py +6 -3
- codejury-1.2.0/codejury/domains/evm/knowledge/vulnerabilities/reentrancy.md +0 -38
- codejury-1.2.0/codejury/domains/web/knowledge/vulnerabilities/insecure-direct-object-reference.md +0 -25
- codejury-1.2.0/codejury/domains/web/knowledge/vulnerabilities/path-traversal.md +0 -35
- {codejury-1.2.0 → codejury-1.4.0}/LICENSE +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/__main__.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/detection.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/detection.yaml +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/facts/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/facts/call_path.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/guides/languages/solidity.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/false-positive-traps.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/severity-rubric.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/registry.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/detection.yaml +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/go/echo.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/go/gin.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/express.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/nestjs.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/celery.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/fastapi.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/javascript.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/python.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/typescript.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/protocols/oauth.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/false-positive-traps.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/severity-rubric.md +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/finding.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/guides.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/json_parse.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/markdown_docs.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/anthropic.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/base.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/chat_format.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/claude_agent.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/factory.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/litellm.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/mock.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/openai.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/retry.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/report.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/resources.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/filter.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/agent.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/gate.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/pass_loop.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/paths.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/severity.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury/severity.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/setup.cfg +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_chat_format.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_detection.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_guides.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_json_parse.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_markdown_docs.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_provider_factory.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_gate.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_model.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_pass_loop.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_paths.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_severity.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_verifier.py +0 -0
- {codejury-1.2.0 → codejury-1.4.0}/tests/test_report.py +0 -0
- {codejury-1.2.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
|
|
|
@@ -115,10 +117,10 @@ example a Claude base finder challenged by GPT and confirmed by Claude:
|
|
|
115
117
|
|
|
116
118
|
```bash
|
|
117
119
|
export CODEJURY_CHALLENGER_PROVIDER=openai
|
|
118
|
-
export CODEJURY_CHALLENGER_MODEL=...
|
|
120
|
+
export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
|
|
119
121
|
export CODEJURY_CHALLENGER_API_KEY=...
|
|
120
|
-
export CODEJURY_CHALLENGER_WIRE_API=responses
|
|
121
|
-
export CODEJURY_JUDGE_MODEL=...
|
|
122
|
+
export CODEJURY_CHALLENGER_WIRE_API=responses # the gpt-5 reasoning models speak Responses
|
|
123
|
+
export CODEJURY_JUDGE_MODEL=... # a Claude model, the confirmer, distinct from the challenger
|
|
122
124
|
```
|
|
123
125
|
|
|
124
126
|
The same `CODEJURY_FINDER_*` / `CODEJURY_CHALLENGER_*` / `CODEJURY_JUDGE_*` and the matching
|
|
@@ -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
|
|
|
@@ -73,10 +75,10 @@ example a Claude base finder challenged by GPT and confirmed by Claude:
|
|
|
73
75
|
|
|
74
76
|
```bash
|
|
75
77
|
export CODEJURY_CHALLENGER_PROVIDER=openai
|
|
76
|
-
export CODEJURY_CHALLENGER_MODEL=...
|
|
78
|
+
export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
|
|
77
79
|
export CODEJURY_CHALLENGER_API_KEY=...
|
|
78
|
-
export CODEJURY_CHALLENGER_WIRE_API=responses
|
|
79
|
-
export CODEJURY_JUDGE_MODEL=...
|
|
80
|
+
export CODEJURY_CHALLENGER_WIRE_API=responses # the gpt-5 reasoning models speak Responses
|
|
81
|
+
export CODEJURY_JUDGE_MODEL=... # a Claude model, the confirmer, distinct from the challenger
|
|
80
82
|
```
|
|
81
83
|
|
|
82
84
|
The same `CODEJURY_FINDER_*` / `CODEJURY_CHALLENGER_*` / `CODEJURY_JUDGE_*` and the matching
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AI code security review tool.
|
|
2
2
|
|
|
3
|
-
Two paths matched to their nature: a coded diff
|
|
3
|
+
Two paths matched to their nature: a coded diff audit engine, a standard single
|
|
4
4
|
call or an adversarial Finder/Challenger/Judge pass, and a whole-repo review run
|
|
5
5
|
as a methodology by an interactive agent. Security knowledge lives in rich
|
|
6
6
|
markdown vulnerability classes under knowledge/vulnerabilities, injected into the
|
|
@@ -291,7 +291,7 @@ def _add_executor_arg(target) -> None:
|
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
def _add_audit_args(p) -> None:
|
|
294
|
-
"""The diff
|
|
294
|
+
"""The diff audit flags for `review diff`."""
|
|
295
295
|
p.add_argument("--file", default=None, help="unified diff file (default: read stdin)")
|
|
296
296
|
p.add_argument("--repo", default=None, help="repo path for --git-range")
|
|
297
297
|
p.add_argument("--git-range", default=None, help="git range to diff, e.g. origin/main...HEAD")
|
|
@@ -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
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: defi-primitives
|
|
3
|
+
title: DeFi Primitives
|
|
4
|
+
kind: protocol
|
|
5
|
+
detect:
|
|
6
|
+
content: ["getReserves", "getAmountOut", "addLiquidity", "removeLiquidity", "borrow", "repay", "liquidate", "collateral", "healthFactor", "rewardPerToken", "getVotes", "propose", "quorum", "flashLoan"]
|
|
7
|
+
---
|
|
8
|
+
# DeFi Primitive Review Notes
|
|
9
|
+
|
|
10
|
+
Protocol-level invariants for the common money legos, the AMM, the lending market, the
|
|
11
|
+
staking vault, and governance. Read the `languages/solidity` guide for the idioms. The
|
|
12
|
+
high-value bugs here are cross-function and cross-contract invariants, price manipulation,
|
|
13
|
+
and transaction ordering, so confirm each property against the real multi-step flow rather
|
|
14
|
+
than a single function.
|
|
15
|
+
|
|
16
|
+
## AMM and Pricing
|
|
17
|
+
- A price read from spot reserves, `getReserves`, `slot0`, or `balanceOf`, moves in one
|
|
18
|
+
transaction under a flash loan. Use a manipulation-resistant source with a staleness and
|
|
19
|
+
a bounds check, see the oracle-price-manipulation class.
|
|
20
|
+
- A swap, deposit, or redeem that enforces no minimum output and no deadline lets a
|
|
21
|
+
sandwich or a stale transaction execute at an attacker-chosen price. Confirm a `minOut`
|
|
22
|
+
and a deadline bind the trade.
|
|
23
|
+
|
|
24
|
+
## Lending
|
|
25
|
+
- Collateral value and the health factor are read at action time from a sound oracle, a
|
|
26
|
+
borrow cannot exceed the collateral after rounding, and a liquidation cannot be blocked
|
|
27
|
+
or front-run for free.
|
|
28
|
+
- Closing or liquidating a position updates debt and collateral accounting before any
|
|
29
|
+
external transfer or token callback, see the reentrancy class.
|
|
30
|
+
|
|
31
|
+
## Staking and Rewards
|
|
32
|
+
- Reward accounting, the `rewardPerToken` accumulator and the per-user checkpoints, updates
|
|
33
|
+
before a stake, withdraw, or claim changes the balance it is computed from, or rewards are
|
|
34
|
+
over or under paid, see the accounting-precision class.
|
|
35
|
+
|
|
36
|
+
## Governance
|
|
37
|
+
- Voting power is read from a snapshot taken before the proposal, not the live balance, or
|
|
38
|
+
a flash loan borrows votes for one block to pass a proposal. Confirm a checkpoint or
|
|
39
|
+
snapshot and a timelock on execution.
|
|
40
|
+
- A privileged governance or parameter action is gated to the executor and timelocked, see
|
|
41
|
+
the access-control class.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: erc-standards
|
|
3
|
+
title: ERC Token Standards
|
|
4
|
+
kind: protocol
|
|
5
|
+
detect:
|
|
6
|
+
content: ["ERC20", "ERC721", "ERC1155", "ERC4626", "IERC20", "transferFrom", "safeTransferFrom", "onERC721Received", "onERC1155Received", "convertToShares", "totalAssets", "decimals"]
|
|
7
|
+
---
|
|
8
|
+
# ERC Token Standards Review Notes
|
|
9
|
+
|
|
10
|
+
These are token-standard invariants, independent of the surrounding protocol. The way each
|
|
11
|
+
shows up in code differs by stack, so read the `languages/solidity` guide for the concrete
|
|
12
|
+
idioms and confirm each invariant against the real flow. The high-value bugs are accounting
|
|
13
|
+
divergence, share-price manipulation, and callback reentrancy rather than syntax.
|
|
14
|
+
|
|
15
|
+
## ERC20
|
|
16
|
+
- A `transfer` or `transferFrom` return value must be honored or wrapped with SafeERC20,
|
|
17
|
+
a token that returns false or nothing on failure makes the call look successful, see the
|
|
18
|
+
unchecked-low-level-call class.
|
|
19
|
+
- A token address that is not one fixed constant may be fee-on-transfer, deflationary,
|
|
20
|
+
rebasing, or ERC777. Measure the real balance delta across the transfer for any value
|
|
21
|
+
that must be exact, do not credit the requested amount, see the weird-erc20 class.
|
|
22
|
+
- Allowance has the approve then `transferFrom` race, and for a fee token the approval must
|
|
23
|
+
cover the amount plus the fee, not the amount alone.
|
|
24
|
+
|
|
25
|
+
## ERC721 and ERC1155
|
|
26
|
+
- `safeTransferFrom` invokes `onERC721Received` or `onERC1155Received` on the recipient, a
|
|
27
|
+
hook that hands control to a party the caller chooses, a reentrancy vector. Write state
|
|
28
|
+
before the safe transfer, see the reentrancy class.
|
|
29
|
+
- `setApprovalForAll` grants blanket control of every token of an owner. Confirm it is
|
|
30
|
+
scoped to a trusted operator and revocable, and that a transfer checks owner or approval.
|
|
31
|
+
|
|
32
|
+
## ERC4626 Vaults
|
|
33
|
+
- Conversion rounds against the user, shares down on deposit and assets down on withdraw,
|
|
34
|
+
or value leaks to the other side, see the accounting-precision class.
|
|
35
|
+
- First-depositor share-price inflation: an empty vault lets the first depositor donate
|
|
36
|
+
assets to inflate the share price and steal later deposits. Confirm a seed deposit, a
|
|
37
|
+
dead-shares mint, or a virtual-offset defense.
|
|
38
|
+
- `totalAssets` must reflect real holdings, not a spot read an attacker can move in one
|
|
39
|
+
transaction, see the oracle-price-manipulation class.
|
|
@@ -19,11 +19,17 @@ 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
|
|
24
|
+
- `weird-erc20` fee-on-transfer, rebasing, ERC777 callback, received below the requested amount
|
|
25
|
+
- `unsafe-math` unchecked-block overflow, narrowing downcast truncation
|
|
23
26
|
|
|
24
27
|
### Signatures
|
|
25
28
|
- `signature-replay` missing nonce, chainid, or domain separator, ecrecover malleability
|
|
26
29
|
|
|
30
|
+
### Randomness
|
|
31
|
+
- `bad-randomness` block.timestamp, blockhash, or prevrandao as a randomness source
|
|
32
|
+
|
|
27
33
|
Report only real, exploitable, high-confidence issues with a concrete exploit path and a
|
|
28
34
|
fund or control impact. Do not report gas-optimization or style notes, floating-pragma or
|
|
29
35
|
compiler advisories, dependency CVEs, or 0.8 checked-arithmetic overflow outside an
|
{codejury-1.2.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
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: bad-randomness
|
|
3
|
+
title: Insecure Randomness
|
|
4
|
+
lens: bad-randomness
|
|
5
|
+
impact: HIGH
|
|
6
|
+
tags: [swc-120, randomness, fund-loss]
|
|
7
|
+
aliases: [weak-randomness, predictable-random]
|
|
8
|
+
triggers: ["block.timestamp", "blockhash", "block.difficulty", "block.prevrandao", "block.number", "keccak256(abi.encodePacked(block", "random", "% "]
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Insecure Randomness
|
|
12
|
+
|
|
13
|
+
Deriving an outcome that controls value, a lottery winner, an NFT trait, a mint order, or
|
|
14
|
+
a selection, from on-chain data such as `block.timestamp`, `blockhash`, `block.number`, or
|
|
15
|
+
`block.prevrandao` is predictable. A caller computes the same value in the same block, and
|
|
16
|
+
a validator can influence or withhold it, so the result is gamed and the payout is stolen.
|
|
17
|
+
Use a verifiable randomness source such as a VRF, or a commit-reveal scheme where the seed
|
|
18
|
+
is fixed before it can be known.
|
|
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
|
+
|
|
26
|
+
### Vulnerable
|
|
27
|
+
```solidity
|
|
28
|
+
function drawWinner(address[] calldata players) external {
|
|
29
|
+
uint256 i = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))) % players.length;
|
|
30
|
+
_payout(players[i]); // any player predicts i in the same block
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Secure
|
|
35
|
+
```solidity
|
|
36
|
+
function drawWinner(uint256 requestId) external {
|
|
37
|
+
uint256 random = vrf.randomness(requestId); // unpredictable until the VRF fulfills
|
|
38
|
+
uint256 i = random % playerCount;
|
|
39
|
+
_payout(players[i]);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
@@ -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
|
+
```
|