codejury 1.2.0__tar.gz → 1.3.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.3.0}/PKG-INFO +4 -4
- {codejury-1.2.0 → codejury-1.3.0}/README.md +3 -3
- {codejury-1.2.0 → codejury-1.3.0}/codejury/__init__.py +1 -1
- {codejury-1.2.0 → codejury-1.3.0}/codejury/cli.py +1 -1
- codejury-1.3.0/codejury/domains/evm/knowledge/guides/protocols/defi-primitives.md +41 -0
- codejury-1.3.0/codejury/domains/evm/knowledge/guides/protocols/erc-standards.md +39 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/index.md +5 -0
- codejury-1.3.0/codejury/domains/evm/knowledge/vulnerabilities/bad-randomness.md +34 -0
- codejury-1.3.0/codejury/domains/evm/knowledge/vulnerabilities/unsafe-math.md +31 -0
- codejury-1.3.0/codejury/domains/evm/knowledge/vulnerabilities/weird-erc20.md +42 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/methodology.md +8 -1
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/slash-command.md +6 -3
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/unit-review.md +19 -4
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/index.md +6 -0
- codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/cors-misconfiguration.md +45 -0
- codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/nosql-injection.md +40 -0
- codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/prototype-pollution.md +45 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/race-condition.md +2 -2
- codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/unrestricted-file-upload.md +46 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/methodology.md +9 -3
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/slash-command.md +6 -3
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/unit-review.md +13 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/adversarial.py +1 -1
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/audit.py +1 -1
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/engine.py +1 -1
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/prompts.py +2 -2
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/vulnerabilities.py +0 -1
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/engine.py +47 -10
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/scaffold.py +36 -1
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/shapes.py +1 -1
- {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/PKG-INFO +4 -4
- {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/SOURCES.txt +9 -0
- {codejury-1.2.0 → codejury-1.3.0}/pyproject.toml +1 -1
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_diff_adversarial.py +1 -1
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_diff_audit.py +1 -1
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_evals.py +26 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_agent.py +2 -2
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_engine.py +28 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_scaffold.py +14 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_vulnerabilities.py +2 -1
- {codejury-1.2.0 → codejury-1.3.0}/LICENSE +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/__main__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/detection.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/base.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/detection.yaml +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/facts/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/facts/call_path.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/facts/slither.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/guides/languages/solidity.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/access-control.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/accounting-precision.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/denial-of-service.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/oracle-price-manipulation.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/proxy-delegatecall.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/reentrancy.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/signature-replay.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/unchecked-low-level-call.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/false-positive-traps.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/severity-rubric.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/poc.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/registry.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/detection.yaml +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/go/echo.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/go/gin.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/express.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/nestjs.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/python/celery.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/python/django.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/python/fastapi.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/python/flask.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/languages/go.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/languages/javascript.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/languages/python.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/languages/typescript.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/protocols/oauth.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/business-logic.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/code-injection.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/command-injection.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-request-forgery.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-scripting.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/hardcoded-secrets.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/http-response-splitting.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/improper-authentication.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/information-exposure.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-cryptography.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-deserialization.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-direct-object-reference.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-session-management.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-transport.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/jwt-validation.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/mass-assignment.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/missing-authorization.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/open-redirect.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/path-traversal.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/replay-attack.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-request-forgery.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-template-injection.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/sql-injection.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/xml-external-entity.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/false-positive-traps.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/severity-rubric.md +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/finding.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/guides.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/json_parse.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/markdown_docs.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/anthropic.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/base.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/chat_format.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/claude_agent.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/factory.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/litellm.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/mock.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/openai.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/retry.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/report.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/resources.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/filter.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/__init__.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/agent.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/gate.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/model.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/pass_loop.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/paths.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/reviewer.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/severity.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/union.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/verifier.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury/severity.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/setup.cfg +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_anthropic_provider.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_chat_format.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_claude_agent_provider.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_cli.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_detection.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_domains.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_guides.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_json_parse.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_litellm_provider.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_markdown_docs.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_openai_provider.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_provider_factory.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_gate.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_model.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_pass_loop.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_paths.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_severity.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_union.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_verifier.py +0 -0
- {codejury-1.2.0 → codejury-1.3.0}/tests/test_report.py +0 -0
- {codejury-1.2.0 → codejury-1.3.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.3.0
|
|
4
4
|
Summary: AI-assisted security review for code diffs and whole repositories.
|
|
5
5
|
Author: AISecLabs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -115,10 +115,10 @@ example a Claude base finder challenged by GPT and confirmed by Claude:
|
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
117
|
export CODEJURY_CHALLENGER_PROVIDER=openai
|
|
118
|
-
export CODEJURY_CHALLENGER_MODEL=...
|
|
118
|
+
export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
|
|
119
119
|
export CODEJURY_CHALLENGER_API_KEY=...
|
|
120
|
-
export CODEJURY_CHALLENGER_WIRE_API=responses
|
|
121
|
-
export CODEJURY_JUDGE_MODEL=...
|
|
120
|
+
export CODEJURY_CHALLENGER_WIRE_API=responses # the gpt-5 reasoning models speak Responses
|
|
121
|
+
export CODEJURY_JUDGE_MODEL=... # a Claude model, the confirmer, distinct from the challenger
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
The same `CODEJURY_FINDER_*` / `CODEJURY_CHALLENGER_*` / `CODEJURY_JUDGE_*` and the matching
|
|
@@ -73,10 +73,10 @@ example a Claude base finder challenged by GPT and confirmed by Claude:
|
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
75
|
export CODEJURY_CHALLENGER_PROVIDER=openai
|
|
76
|
-
export CODEJURY_CHALLENGER_MODEL=...
|
|
76
|
+
export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
|
|
77
77
|
export CODEJURY_CHALLENGER_API_KEY=...
|
|
78
|
-
export CODEJURY_CHALLENGER_WIRE_API=responses
|
|
79
|
-
export CODEJURY_JUDGE_MODEL=...
|
|
78
|
+
export CODEJURY_CHALLENGER_WIRE_API=responses # the gpt-5 reasoning models speak Responses
|
|
79
|
+
export CODEJURY_JUDGE_MODEL=... # a Claude model, the confirmer, distinct from the challenger
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
The same `CODEJURY_FINDER_*` / `CODEJURY_CHALLENGER_*` / `CODEJURY_JUDGE_*` and the matching
|
|
@@ -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")
|
|
@@ -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.
|
|
@@ -20,10 +20,15 @@ the target. A finding's `category` is one of these ids.
|
|
|
20
20
|
### Economic and Accounting
|
|
21
21
|
- `oracle-price-manipulation` spot price or balance as price, flash-loan assisted
|
|
22
22
|
- `accounting-precision` rounding, division before multiplication, ERC-4626 first-depositor inflation
|
|
23
|
+
- `weird-erc20` fee-on-transfer, rebasing, ERC777 callback, received below the requested amount
|
|
24
|
+
- `unsafe-math` unchecked-block overflow, narrowing downcast truncation
|
|
23
25
|
|
|
24
26
|
### Signatures
|
|
25
27
|
- `signature-replay` missing nonce, chainid, or domain separator, ecrecover malleability
|
|
26
28
|
|
|
29
|
+
### Randomness
|
|
30
|
+
- `bad-randomness` block.timestamp, blockhash, or prevrandao as a randomness source
|
|
31
|
+
|
|
27
32
|
Report only real, exploitable, high-confidence issues with a concrete exploit path and a
|
|
28
33
|
fund or control impact. Do not report gas-optimization or style notes, floating-pragma or
|
|
29
34
|
compiler advisories, dependency CVEs, or 0.8 checked-arithmetic overflow outside an
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: bad-randomness
|
|
3
|
+
title: Insecure Randomness
|
|
4
|
+
impact: HIGH
|
|
5
|
+
tags: [swc-120, randomness, fund-loss]
|
|
6
|
+
aliases: [weak-randomness, predictable-random]
|
|
7
|
+
triggers: ["block.timestamp", "blockhash", "block.difficulty", "block.prevrandao", "block.number", "keccak256(abi.encodePacked(block", "random", "% "]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Insecure Randomness
|
|
11
|
+
|
|
12
|
+
Deriving an outcome that controls value, a lottery winner, an NFT trait, a mint order, or
|
|
13
|
+
a selection, from on-chain data such as `block.timestamp`, `blockhash`, `block.number`, or
|
|
14
|
+
`block.prevrandao` is predictable. A caller computes the same value in the same block, and
|
|
15
|
+
a validator can influence or withhold it, so the result is gamed and the payout is stolen.
|
|
16
|
+
Use a verifiable randomness source such as a VRF, or a commit-reveal scheme where the seed
|
|
17
|
+
is fixed before it can be known.
|
|
18
|
+
|
|
19
|
+
### Vulnerable
|
|
20
|
+
```solidity
|
|
21
|
+
function drawWinner(address[] calldata players) external {
|
|
22
|
+
uint256 i = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))) % players.length;
|
|
23
|
+
_payout(players[i]); // any player predicts i in the same block
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Secure
|
|
28
|
+
```solidity
|
|
29
|
+
function drawWinner(uint256 requestId) external {
|
|
30
|
+
uint256 random = vrf.randomness(requestId); // unpredictable until the VRF fulfills
|
|
31
|
+
uint256 i = random % playerCount;
|
|
32
|
+
_payout(players[i]);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: unsafe-math
|
|
3
|
+
title: Unchecked Math and Unsafe Cast
|
|
4
|
+
impact: HIGH
|
|
5
|
+
tags: [swc-101, overflow, downcast, fund-loss]
|
|
6
|
+
aliases: [unchecked-math, unsafe-cast, downcast-truncation]
|
|
7
|
+
triggers: ["unchecked", "uint128", "uint96", "uint64", "uint32", "int128", "SafeCast", "toUint", "downcast", "overflow"]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Unchecked Math and Unsafe Cast
|
|
11
|
+
|
|
12
|
+
Solidity 0.8 reverts on overflow by default, but arithmetic inside an `unchecked` block,
|
|
13
|
+
or any code on a pre-0.8 pragma, wraps silently. Separately, a narrowing cast such as
|
|
14
|
+
`uint256` to `uint128` truncates the high bits with no revert, so a large value becomes a
|
|
15
|
+
small one. Either corrupts accounting, a balance, a share count, or a fee, and the
|
|
16
|
+
contract acts on the wrong number. Keep value math checked, and use a checked cast such as
|
|
17
|
+
OpenZeppelin SafeCast when narrowing a width.
|
|
18
|
+
|
|
19
|
+
### Vulnerable
|
|
20
|
+
```solidity
|
|
21
|
+
function record(uint256 amount) external {
|
|
22
|
+
totalShares += uint128(amount); // a value above 2**128-1 truncates, supply under-counts
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Secure
|
|
27
|
+
```solidity
|
|
28
|
+
function record(uint256 amount) external {
|
|
29
|
+
totalShares += SafeCast.toUint128(amount); // reverts instead of truncating
|
|
30
|
+
}
|
|
31
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: weird-erc20
|
|
3
|
+
title: Weird ERC20 Behavior
|
|
4
|
+
impact: HIGH
|
|
5
|
+
tags: [fee-on-transfer, rebasing, erc777, accounting, fund-loss]
|
|
6
|
+
aliases: [fee-on-transfer, deflationary-token, rebasing-token, erc777-hook]
|
|
7
|
+
triggers: ["transferFrom", "transfer(", "balanceOf", "amount", "ERC777", "tokensReceived", "tokensToSend", "rebas", "fee", "safeTransferFrom", "received"]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Weird ERC20 Behavior
|
|
11
|
+
|
|
12
|
+
A contract that integrates an arbitrary token often assumes the token moves exactly the
|
|
13
|
+
requested `amount`, returns true, and never hands back control. Many real tokens break
|
|
14
|
+
those assumptions. A fee-on-transfer or deflationary token delivers less than `amount`,
|
|
15
|
+
so crediting the caller the requested amount over-credits a deposit and inflates shares,
|
|
16
|
+
balances, or accounting against the pool. A rebasing token changes balances out from
|
|
17
|
+
under a stored amount, so a figure recorded at deposit no longer matches the real
|
|
18
|
+
balance. An ERC777 token runs a `tokensReceived` or `tokensToSend` hook inside a plain
|
|
19
|
+
`transfer` or `transferFrom`, handing control to a party the caller chooses, which is a
|
|
20
|
+
reentrancy vector covered in reentrancy. The boundary with unchecked-low-level-call is
|
|
21
|
+
that the return value here is honored, the bug is that the amount moved is not the amount
|
|
22
|
+
assumed. For value that must be exact, measure the real balance delta across the transfer
|
|
23
|
+
rather than trusting the requested amount, and treat a token address that is not one
|
|
24
|
+
fixed constant as possibly any of these.
|
|
25
|
+
|
|
26
|
+
### Vulnerable
|
|
27
|
+
```solidity
|
|
28
|
+
function deposit(uint256 amount) external {
|
|
29
|
+
token.transferFrom(msg.sender, address(this), amount);
|
|
30
|
+
shares[msg.sender] += amount; // credits the requested amount, a fee token delivered less
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Secure
|
|
35
|
+
```solidity
|
|
36
|
+
function deposit(uint256 amount) external {
|
|
37
|
+
uint256 balanceBefore = token.balanceOf(address(this));
|
|
38
|
+
token.transferFrom(msg.sender, address(this), amount);
|
|
39
|
+
uint256 received = token.balanceOf(address(this)) - balanceBefore;
|
|
40
|
+
shares[msg.sender] += received; // credit only what actually arrived
|
|
41
|
+
}
|
|
42
|
+
```
|
|
@@ -48,7 +48,7 @@ by contract: every `external` and `public` function, plus `fallback` and `receiv
|
|
|
48
48
|
each record the contract, the function, the access control on it, owner-only or
|
|
49
49
|
role-gated or open, and a review status.
|
|
50
50
|
|
|
51
|
-
Then record
|
|
51
|
+
Then record three cross-cutting artifacts in `inventory/`:
|
|
52
52
|
|
|
53
53
|
- **The role and ownership model**: every privileged role, owner, admin, governance, the
|
|
54
54
|
minter and upgrader, which functions each can call, and how a role is granted or
|
|
@@ -57,6 +57,13 @@ Then record two cross-cutting artifacts in `inventory/`:
|
|
|
57
57
|
balances, the share and accounting math, the price and oracle sources, and every path
|
|
58
58
|
that mints, burns, or moves value, since a value-leak has no obvious entrypoint and an
|
|
59
59
|
entrypoint-anchored read misses it.
|
|
60
|
+
- **The intent invariants**: the operator-seeded `inventory/_invariants.md`, the core
|
|
61
|
+
assets, who may legitimately move each, and the properties that must always hold,
|
|
62
|
+
conservation of value, single-use of a nonce or voucher, monotonic supply and balances,
|
|
63
|
+
ownership of a position, ordering across a multi-call flow, with the blast radius if one
|
|
64
|
+
breaks. A static read sees the modifiers but not the protocol's intended accounting, so
|
|
65
|
+
this names the intent a unit checks against. When the operator left it blank, it seeds
|
|
66
|
+
nothing and a unit reviews exactly as before.
|
|
60
67
|
|
|
61
68
|
---
|
|
62
69
|
|
|
@@ -30,13 +30,16 @@ happens inside each sub-review, never in this main context.
|
|
|
30
30
|
- `units/`, one unit per candidate contract carrying its deep-review mandate and `- Status: open`
|
|
31
31
|
- `inventory/_surface.md`, the coverage denominator
|
|
32
32
|
- `inventory/_auth_model.md`, for the role and ownership model
|
|
33
|
+
- `inventory/_invariants.md`, the operator-seeded intent invariants, optional
|
|
33
34
|
- `inventory/_severity.md`, the fund-loss grading rubric
|
|
34
35
|
|
|
35
36
|
Read `METHODOLOGY.md` once for the full process.
|
|
36
37
|
|
|
37
38
|
2. MAP. Make the worklist complete. Enumerate every external and public function, plus
|
|
38
39
|
`fallback` and `receive`, into `inventory/_surface.md`, and fill `inventory/_auth_model.md`
|
|
39
|
-
with the role and ownership model and the value map.
|
|
40
|
+
with the role and ownership model and the value map. If the operator seeded
|
|
41
|
+
`inventory/_invariants.md` with intent invariants, leave it for the units to check, and
|
|
42
|
+
if it is blank do not invent rows, an unseeded invariants file changes nothing. For anything the seeded units miss,
|
|
40
43
|
add a unit file by copying the mandate from a seeded one: contracts no glob flagged, and
|
|
41
44
|
sequence units for a multi-step or multi-contract flow whose invariant spans several
|
|
42
45
|
calls. Every entrypoint in the surface must be owned by some unit.
|
|
@@ -44,8 +47,8 @@ happens inside each sub-review, never in this main context.
|
|
|
44
47
|
3. FAN OUT. This step is mechanical, not a matter of judgment. For every unit in `units/`
|
|
45
48
|
with `- Status: open`, launch one sub-review per unit as a separate subagent, in
|
|
46
49
|
parallel. One per unit, no unit skipped. Give each only its unit file plus the shared
|
|
47
|
-
`_stack.md`, `inventory/_auth_model.md`, `inventory/
|
|
48
|
-
`_vulnerabilities.md`. Each sub-review reads its files, traces into inherited base
|
|
50
|
+
`_stack.md`, `inventory/_auth_model.md`, `inventory/_invariants.md`,
|
|
51
|
+
`inventory/_severity.md`, and `_vulnerabilities.md`. Each sub-review reads its files, traces into inherited base
|
|
49
52
|
contracts, libraries, and called contracts, hunts the high-impact classes, reentrancy,
|
|
50
53
|
access control, oracle manipulation, accounting, proxy and initializer flaws, signature
|
|
51
54
|
replay, unchecked calls, DoS, verifies each control on the code it reads, refutes its own
|
|
@@ -8,13 +8,15 @@ trace each one into the internal functions, libraries, base contracts it inherit
|
|
|
8
8
|
external contracts it calls, down to where value moves or state changes. The flaw often
|
|
9
9
|
lives in an inherited modifier, a library, or a called protocol, not the entrypoint. Read
|
|
10
10
|
the shared `_stack.md` and `inventory/_auth_model.md` for the role and ownership model,
|
|
11
|
-
`
|
|
12
|
-
|
|
11
|
+
`inventory/_invariants.md` for the operator-seeded intent invariants, `_vulnerabilities.md`
|
|
12
|
+
for the class definitions with vulnerable and secure examples, and `_false_positive_traps.md`
|
|
13
|
+
for the recurring ways a static read misjudges them.
|
|
13
14
|
|
|
14
15
|
Hunt the high-impact classes: reentrancy, missing or broken access control, oracle and
|
|
15
16
|
price manipulation, accounting and precision errors, proxy, delegatecall, and initializer
|
|
16
|
-
flaws, signature replay, unchecked low-level calls,
|
|
17
|
-
|
|
17
|
+
flaws, signature replay, unchecked low-level calls, weird ERC20 behavior such as
|
|
18
|
+
fee-on-transfer and rebasing tokens, and denial of service. Money is the asset, grade
|
|
19
|
+
every finding by funds moved, locked, or stolen.
|
|
18
20
|
|
|
19
21
|
For every control on the path, decide on the code you actually read, never on the presence
|
|
20
22
|
of a named guard:
|
|
@@ -64,6 +66,19 @@ of a named guard:
|
|
|
64
66
|
separator, the signer checked nonzero? A signature alone is replayable.
|
|
65
67
|
- **Trusted-source**: a value is not safe because a caller you treat as trusted set it, if
|
|
66
68
|
that caller is an arbitrary external account or contract.
|
|
69
|
+
- **Seeded invariant**: `inventory/_invariants.md` may name a property the operator asserts
|
|
70
|
+
must always hold, conservation of value, single-use of a nonce or voucher, monotonic
|
|
71
|
+
supply or balances, ownership of a position, ordering across a flow. Apply it only to the
|
|
72
|
+
invariants whose assets or functions this unit's code actually touches, and skip every
|
|
73
|
+
other row. For each one that applies, trace whether a reachable path can break the
|
|
74
|
+
property, an unconserved mint, a reused signature, a balance moved the wrong way, a
|
|
75
|
+
position mutated by a non-owner, a step run out of order, and treat a breakable invariant
|
|
76
|
+
as a finding, the same as any guard you read. Decide on the code you read, not on the row:
|
|
77
|
+
a seeded property is a hypothesis to test against this path, never a finding on its own.
|
|
78
|
+
When the file is blank, or no seeded row touches this unit's code, there is nothing to
|
|
79
|
+
check here and you report nothing for it. A confirmed break is graded by funds moved,
|
|
80
|
+
locked, or stolen per the rubric, the seeded blast radius is its floor, and a property the
|
|
81
|
+
code preserves is a cleared control you record, not a finding.
|
|
67
82
|
|
|
68
83
|
Refute in place: name the one controlling fact that would make the code safe, read that
|
|
69
84
|
exact code, including inherited modifiers and the called contract, and settle it. Confirmed
|
|
@@ -30,12 +30,17 @@ of these ids.
|
|
|
30
30
|
- `xml-external-entity` CWE-611
|
|
31
31
|
- `server-side-template-injection` CWE-1336
|
|
32
32
|
- `http-response-splitting` CWE-113
|
|
33
|
+
- `nosql-injection` CWE-943
|
|
33
34
|
|
|
34
35
|
### A04 Insecure Design / Business Logic
|
|
35
36
|
- `business-logic` CWE-840
|
|
36
37
|
- `replay-attack` CWE-294
|
|
37
38
|
- `race-condition` CWE-362
|
|
38
39
|
- `mass-assignment` CWE-915
|
|
40
|
+
- `unrestricted-file-upload` CWE-434
|
|
41
|
+
|
|
42
|
+
### A05 Security Misconfiguration
|
|
43
|
+
- `cors-misconfiguration` CWE-942
|
|
39
44
|
|
|
40
45
|
### A07 Identification and Authentication
|
|
41
46
|
- `improper-authentication` CWE-287
|
|
@@ -44,6 +49,7 @@ of these ids.
|
|
|
44
49
|
|
|
45
50
|
### A08 Software and Data Integrity
|
|
46
51
|
- `insecure-deserialization` CWE-502
|
|
52
|
+
- `prototype-pollution` CWE-1321
|
|
47
53
|
|
|
48
54
|
### A10 Server-Side Request Forgery
|
|
49
55
|
- `server-side-request-forgery` CWE-918
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: cors-misconfiguration
|
|
3
|
+
title: CORS Misconfiguration
|
|
4
|
+
impact: MEDIUM
|
|
5
|
+
tags: [cwe-942, owasp-a05]
|
|
6
|
+
triggers: ["Access-Control-Allow-Origin", "Access-Control-Allow-Credentials", "cors(", "origin", "req.headers.origin", "ACAO"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## CORS Misconfiguration
|
|
10
|
+
|
|
11
|
+
A server that reflects the request `Origin` back into `Access-Control-Allow-Origin` while
|
|
12
|
+
also sending `Access-Control-Allow-Credentials: true` lets any website read the
|
|
13
|
+
authenticated responses of a logged-in victim, so a malicious page steals the victim's
|
|
14
|
+
data or tokens cross-origin. Reflecting the origin, or trusting it by a substring or
|
|
15
|
+
suffix match, is the exploitable form. Validate the origin against an exact allowlist and
|
|
16
|
+
send credentials only to those origins.
|
|
17
|
+
|
|
18
|
+
### Vulnerable
|
|
19
|
+
```javascript
|
|
20
|
+
app.use((req, res, next) => {
|
|
21
|
+
res.setHeader("Access-Control-Allow-Origin", req.headers.origin) // reflects any origin
|
|
22
|
+
res.setHeader("Access-Control-Allow-Credentials", "true")
|
|
23
|
+
next()
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Secure
|
|
28
|
+
```javascript
|
|
29
|
+
const ALLOWED = new Set(["https://app.example.com"])
|
|
30
|
+
app.use((req, res, next) => {
|
|
31
|
+
if (ALLOWED.has(req.headers.origin)) {
|
|
32
|
+
res.setHeader("Access-Control-Allow-Origin", req.headers.origin)
|
|
33
|
+
res.setHeader("Access-Control-Allow-Credentials", "true")
|
|
34
|
+
}
|
|
35
|
+
next()
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Not a Finding
|
|
40
|
+
|
|
41
|
+
A wildcard `Access-Control-Allow-Origin: *` without `Allow-Credentials` on data that is
|
|
42
|
+
already public is not a finding, the browser blocks the credentialed case. An exact-match
|
|
43
|
+
allowlist is the expected control. Report it only when the origin is reflected, matched by
|
|
44
|
+
substring, suffix, or `startsWith`, or read from attacker-controlled config, together with
|
|
45
|
+
credentials or access to non-public data.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: nosql-injection
|
|
3
|
+
title: NoSQL Injection
|
|
4
|
+
impact: HIGH
|
|
5
|
+
tags: [cwe-943, owasp-a03]
|
|
6
|
+
triggers: ["find(", "findOne(", "$where", "$ne", "$gt", "req.body", "req.query", "collection.", "mongo"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## NoSQL Injection
|
|
10
|
+
|
|
11
|
+
Passing untrusted input straight into a NoSQL query object lets an attacker inject query
|
|
12
|
+
operators rather than values. A login that builds `{ user, pass }` from the request body
|
|
13
|
+
is bypassed when `pass` arrives as `{"$ne": null}`, and `$where`, `$gt`, or `$regex`
|
|
14
|
+
extract or enumerate data. The sql-injection class does not cover this, the payload is a
|
|
15
|
+
structured operator, not a string break. Coerce inputs to the expected scalar type or
|
|
16
|
+
validate against a schema before querying.
|
|
17
|
+
|
|
18
|
+
### Vulnerable
|
|
19
|
+
```javascript
|
|
20
|
+
app.post("/login", (req, res) => {
|
|
21
|
+
db.users.findOne({ user: req.body.user, pass: req.body.pass }) // pass can be {$ne: null}
|
|
22
|
+
.then(u => res.json({ ok: !!u }))
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Secure
|
|
27
|
+
```javascript
|
|
28
|
+
app.post("/login", (req, res) => {
|
|
29
|
+
const user = String(req.body.user)
|
|
30
|
+
const pass = String(req.body.pass) // operators cannot survive the cast to string
|
|
31
|
+
db.users.findOne({ user, pass }).then(u => res.json({ ok: !!u }))
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Not a Finding
|
|
36
|
+
|
|
37
|
+
A query whose untrusted fields are cast to a scalar or validated against a schema before
|
|
38
|
+
the call is the expected control. Report it only when a request value reaches the query
|
|
39
|
+
object as a raw object or array, so an operator like `$ne`, `$gt`, `$regex`, or `$where`
|
|
40
|
+
can be injected.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: prototype-pollution
|
|
3
|
+
title: Prototype Pollution
|
|
4
|
+
impact: HIGH
|
|
5
|
+
tags: [cwe-1321]
|
|
6
|
+
triggers: ["__proto__", "constructor", "prototype", "merge", "Object.assign", "deepMerge", "[key]", "extend("]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Prototype Pollution
|
|
10
|
+
|
|
11
|
+
A recursive merge, clone, or `set`-by-path that copies attacker-controlled keys into a
|
|
12
|
+
JavaScript object without rejecting `__proto__`, `constructor`, or `prototype` writes onto
|
|
13
|
+
`Object.prototype`. Every object then inherits the injected property, which lets an
|
|
14
|
+
attacker tamper with application logic, cause denial of service, or in some sinks reach
|
|
15
|
+
remote code execution. Reject those keys, use a null-prototype object or a `Map`, or
|
|
16
|
+
validate against a schema before merging.
|
|
17
|
+
|
|
18
|
+
### Vulnerable
|
|
19
|
+
```javascript
|
|
20
|
+
function merge(target, source) {
|
|
21
|
+
for (const key in source) {
|
|
22
|
+
if (typeof source[key] === "object") {
|
|
23
|
+
merge(target[key] = target[key] || {}, source[key]) // walks __proto__ from user JSON
|
|
24
|
+
} else {
|
|
25
|
+
target[key] = source[key]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
merge({}, JSON.parse(req.body))
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Secure
|
|
33
|
+
```javascript
|
|
34
|
+
const BLOCKED = new Set(["__proto__", "constructor", "prototype"])
|
|
35
|
+
function merge(target, source) {
|
|
36
|
+
for (const key in source) {
|
|
37
|
+
if (BLOCKED.has(key)) continue
|
|
38
|
+
if (typeof source[key] === "object") {
|
|
39
|
+
merge(target[key] = target[key] || {}, source[key])
|
|
40
|
+
} else {
|
|
41
|
+
target[key] = source[key]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
{codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/race-condition.md
RENAMED
|
@@ -14,8 +14,8 @@ A check and the action it guards run on shared state without a lock or atomic up
|
|
|
14
14
|
Vulnerable:
|
|
15
15
|
```python
|
|
16
16
|
acct = Account.objects.get(pk=pk)
|
|
17
|
-
if acct.balance >= amount:
|
|
18
|
-
acct.balance -= amount
|
|
17
|
+
if acct.balance >= amount: # concurrent requests both pass this check
|
|
18
|
+
acct.balance -= amount
|
|
19
19
|
acct.save()
|
|
20
20
|
```
|
|
21
21
|
Secure:
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: unrestricted-file-upload
|
|
3
|
+
title: Unrestricted File Upload
|
|
4
|
+
impact: HIGH
|
|
5
|
+
tags: [cwe-434, owasp-a04]
|
|
6
|
+
triggers: ["upload", "filename", ".save(", "multipart", "content-type", "secure_filename", "os.path.join", "MimeType"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Unrestricted File Upload
|
|
10
|
+
|
|
11
|
+
A handler that stores an uploaded file under an attacker-controlled name, extension, or
|
|
12
|
+
content type, into a web-served or executable directory, lets an attacker upload a
|
|
13
|
+
webshell such as a `.php` or `.jsp` file and execute it, or overwrite an existing file.
|
|
14
|
+
The boundary with path-traversal is the file type and execution, not the path: here the
|
|
15
|
+
danger is what the stored file is and where it can run. Force a safe generated name, an
|
|
16
|
+
allowlisted extension validated against the real content, and store outside the web root.
|
|
17
|
+
|
|
18
|
+
### Vulnerable
|
|
19
|
+
```python
|
|
20
|
+
@app.post("/upload")
|
|
21
|
+
def upload():
|
|
22
|
+
f = request.files["file"]
|
|
23
|
+
f.save(os.path.join(UPLOAD_DIR, f.filename)) # attacker sets name and extension
|
|
24
|
+
return "ok"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Secure
|
|
28
|
+
```python
|
|
29
|
+
ALLOWED = {"png", "jpg", "pdf"}
|
|
30
|
+
@app.post("/upload")
|
|
31
|
+
def upload():
|
|
32
|
+
f = request.files["file"]
|
|
33
|
+
ext = f.filename.rsplit(".", 1)[-1].lower()
|
|
34
|
+
if ext not in ALLOWED:
|
|
35
|
+
abort(400)
|
|
36
|
+
name = f"{uuid4()}.{ext}"
|
|
37
|
+
f.save(os.path.join(UPLOAD_DIR, name)) # generated name, allowlisted type, outside web root
|
|
38
|
+
return "ok"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Not a Finding
|
|
42
|
+
|
|
43
|
+
An upload validated against an extension and content-type allowlist, stored under a
|
|
44
|
+
generated name outside the web root, is the expected control. Report it only when the
|
|
45
|
+
name, extension, or type is attacker-controlled into a served or executable location, or
|
|
46
|
+
the validation is bypassable such as a check on the client-supplied content type alone.
|
|
@@ -64,7 +64,7 @@ Use the seeded entrypoint candidates as a starting subset, not the whole surface
|
|
|
64
64
|
Open the route modules and read the actual registrations. For each entrypoint
|
|
65
65
|
record the module, the route, the auth method, and a review status.
|
|
66
66
|
|
|
67
|
-
Then record
|
|
67
|
+
Then record three cross-cutting artifacts in `inventory/`:
|
|
68
68
|
|
|
69
69
|
- **The authorization model**: how this codebase enforces access control, by a
|
|
70
70
|
decorator, middleware, permission class, signature, or guard, the actors, tenants, and services,
|
|
@@ -73,6 +73,12 @@ Then record two cross-cutting artifacts in `inventory/`:
|
|
|
73
73
|
- **The sensitive-data map**: where tokens, secrets, PII, keys, and other tenants'
|
|
74
74
|
data live, since the data-exposure class has no attacker entrypoint and an
|
|
75
75
|
entrypoint-anchored read misses it.
|
|
76
|
+
- **The intent invariants**: the operator-seeded `inventory/_invariants.md`, the core
|
|
77
|
+
assets, who may legitimately move each, and the properties that must always hold,
|
|
78
|
+
conservation, single-use, monotonic, ownership, ordering, with the blast radius if
|
|
79
|
+
one breaks. A static read sees the controls but not the business intent behind them,
|
|
80
|
+
so this names the intent a unit checks against. When the operator left it blank, it
|
|
81
|
+
seeds nothing and a unit reviews exactly as before.
|
|
76
82
|
|
|
77
83
|
---
|
|
78
84
|
|
|
@@ -118,8 +124,8 @@ A unit looked at in passing by the orchestrator is the shallow whole-repo pass t
|
|
|
118
124
|
method exists to replace, and it is the single thing that drops recall.
|
|
119
125
|
|
|
120
126
|
Give each sub-review only its slice plus the shared artifacts: `_stack.md`, the
|
|
121
|
-
inventory's auth model, and `_vulnerabilities.md`.
|
|
122
|
-
mandate below:
|
|
127
|
+
inventory's auth model, the seeded `inventory/_invariants.md`, and `_vulnerabilities.md`.
|
|
128
|
+
Each sub-review follows the full mandate below:
|
|
123
129
|
|
|
124
130
|
1. **Traces** every entrypoint in its unit out of the view into the managers,
|
|
125
131
|
controllers, DAO, and libraries it calls, to the real sink. The flaw usually
|
|
@@ -37,13 +37,16 @@ review in passing here is the shallow whole-repo pass this method exists to repl
|
|
|
37
37
|
- `units/`, one unit per candidate entrypoint carrying its deep-review mandate and `- Status: open`
|
|
38
38
|
- `inventory/_surface.md`, the coverage denominator
|
|
39
39
|
- `inventory/_auth_model.md`
|
|
40
|
+
- `inventory/_invariants.md`, the operator-seeded intent invariants, optional
|
|
40
41
|
- `inventory/_severity.md`, the grading rubric
|
|
41
42
|
|
|
42
43
|
Read `METHODOLOGY.md` once for the full process.
|
|
43
44
|
|
|
44
45
|
2. MAP. Make the worklist complete. Enumerate every attacker-influenced entrypoint
|
|
45
46
|
into `inventory/_surface.md` and fill `inventory/_auth_model.md` with the access
|
|
46
|
-
model and trust boundaries.
|
|
47
|
+
model and trust boundaries. If the operator seeded `inventory/_invariants.md` with
|
|
48
|
+
intent invariants, leave it for the units to check, and if it is blank do not invent
|
|
49
|
+
rows, an unseeded invariants file changes nothing. For anything the seeded units miss, add a unit file by
|
|
47
50
|
copying the mandate from a seeded one: non-HTTP sources such as deserializers,
|
|
48
51
|
queues, file parsers, or webhooks, entrypoint modules no guide flagged, and sequence units
|
|
49
52
|
for a multi-step flow whose invariant spans several endpoints. Every entrypoint in
|
|
@@ -53,8 +56,8 @@ review in passing here is the shallow whole-repo pass this method exists to repl
|
|
|
53
56
|
`units/` with `- Status: open`, launch one sub-review per unit as a separate
|
|
54
57
|
subagent or task, in parallel. One per unit, no unit skipped, no two merged to save calls.
|
|
55
58
|
Give each only its unit file, which carries the mandate and the files to own, plus
|
|
56
|
-
the shared `_stack.md`, `inventory/_auth_model.md`, `inventory/
|
|
57
|
-
`_vulnerabilities.md`. Each
|
|
59
|
+
the shared `_stack.md`, `inventory/_auth_model.md`, `inventory/_invariants.md`,
|
|
60
|
+
`inventory/_severity.md`, and `_vulnerabilities.md`. Each
|
|
58
61
|
sub-review reads its files, traces into the managers, DAO, controllers, and
|
|
59
62
|
libraries they call, hunts the high-impact classes, verifies each control on the
|
|
60
63
|
code it actually reads, refutes its own candidates, grades every real finding by
|