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.
Files changed (160) hide show
  1. {codejury-1.2.0 → codejury-1.3.0}/PKG-INFO +4 -4
  2. {codejury-1.2.0 → codejury-1.3.0}/README.md +3 -3
  3. {codejury-1.2.0 → codejury-1.3.0}/codejury/__init__.py +1 -1
  4. {codejury-1.2.0 → codejury-1.3.0}/codejury/cli.py +1 -1
  5. codejury-1.3.0/codejury/domains/evm/knowledge/guides/protocols/defi-primitives.md +41 -0
  6. codejury-1.3.0/codejury/domains/evm/knowledge/guides/protocols/erc-standards.md +39 -0
  7. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/index.md +5 -0
  8. codejury-1.3.0/codejury/domains/evm/knowledge/vulnerabilities/bad-randomness.md +34 -0
  9. codejury-1.3.0/codejury/domains/evm/knowledge/vulnerabilities/unsafe-math.md +31 -0
  10. codejury-1.3.0/codejury/domains/evm/knowledge/vulnerabilities/weird-erc20.md +42 -0
  11. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/methodology.md +8 -1
  12. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/slash-command.md +6 -3
  13. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/unit-review.md +19 -4
  14. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/index.md +6 -0
  15. codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/cors-misconfiguration.md +45 -0
  16. codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/nosql-injection.md +40 -0
  17. codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/prototype-pollution.md +45 -0
  18. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/race-condition.md +2 -2
  19. codejury-1.3.0/codejury/domains/web/knowledge/vulnerabilities/unrestricted-file-upload.md +46 -0
  20. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/methodology.md +9 -3
  21. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/slash-command.md +6 -3
  22. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/unit-review.md +13 -0
  23. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/adversarial.py +1 -1
  24. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/audit.py +1 -1
  25. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/engine.py +1 -1
  26. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/prompts.py +2 -2
  27. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/vulnerabilities.py +0 -1
  28. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/engine.py +47 -10
  29. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/scaffold.py +36 -1
  30. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/shapes.py +1 -1
  31. {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/PKG-INFO +4 -4
  32. {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/SOURCES.txt +9 -0
  33. {codejury-1.2.0 → codejury-1.3.0}/pyproject.toml +1 -1
  34. {codejury-1.2.0 → codejury-1.3.0}/tests/test_diff_adversarial.py +1 -1
  35. {codejury-1.2.0 → codejury-1.3.0}/tests/test_diff_audit.py +1 -1
  36. {codejury-1.2.0 → codejury-1.3.0}/tests/test_evals.py +26 -0
  37. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_agent.py +2 -2
  38. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_engine.py +28 -0
  39. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_scaffold.py +14 -0
  40. {codejury-1.2.0 → codejury-1.3.0}/tests/test_vulnerabilities.py +2 -1
  41. {codejury-1.2.0 → codejury-1.3.0}/LICENSE +0 -0
  42. {codejury-1.2.0 → codejury-1.3.0}/codejury/__main__.py +0 -0
  43. {codejury-1.2.0 → codejury-1.3.0}/codejury/detection.py +0 -0
  44. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/__init__.py +0 -0
  45. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/base.py +0 -0
  46. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/__init__.py +0 -0
  47. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/detection.yaml +0 -0
  48. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/facts/__init__.py +0 -0
  49. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/facts/call_path.py +0 -0
  50. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/facts/slither.py +0 -0
  51. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/guides/languages/solidity.md +0 -0
  52. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/access-control.md +0 -0
  53. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/accounting-precision.md +0 -0
  54. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/denial-of-service.md +0 -0
  55. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/oracle-price-manipulation.md +0 -0
  56. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/proxy-delegatecall.md +0 -0
  57. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/reentrancy.md +0 -0
  58. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/signature-replay.md +0 -0
  59. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/knowledge/vulnerabilities/unchecked-low-level-call.md +0 -0
  60. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/false-positive-traps.md +0 -0
  61. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/playbook/severity-rubric.md +0 -0
  62. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/evm/poc.py +0 -0
  63. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/registry.py +0 -0
  64. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/__init__.py +0 -0
  65. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/detection.yaml +0 -0
  66. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/go/echo.md +0 -0
  67. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/go/gin.md +0 -0
  68. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/express.md +0 -0
  69. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/nestjs.md +0 -0
  70. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/python/celery.md +0 -0
  71. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/python/django.md +0 -0
  72. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/python/fastapi.md +0 -0
  73. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/frameworks/python/flask.md +0 -0
  74. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/languages/go.md +0 -0
  75. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/languages/javascript.md +0 -0
  76. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/languages/python.md +0 -0
  77. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/languages/typescript.md +0 -0
  78. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/guides/protocols/oauth.md +0 -0
  79. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/business-logic.md +0 -0
  80. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/code-injection.md +0 -0
  81. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/command-injection.md +0 -0
  82. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-request-forgery.md +0 -0
  83. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-scripting.md +0 -0
  84. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/hardcoded-secrets.md +0 -0
  85. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/http-response-splitting.md +0 -0
  86. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/improper-authentication.md +0 -0
  87. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/information-exposure.md +0 -0
  88. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-cryptography.md +0 -0
  89. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-deserialization.md +0 -0
  90. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-direct-object-reference.md +0 -0
  91. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-session-management.md +0 -0
  92. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-transport.md +0 -0
  93. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/jwt-validation.md +0 -0
  94. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/mass-assignment.md +0 -0
  95. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/missing-authorization.md +0 -0
  96. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/open-redirect.md +0 -0
  97. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/path-traversal.md +0 -0
  98. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/replay-attack.md +0 -0
  99. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-request-forgery.md +0 -0
  100. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-template-injection.md +0 -0
  101. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/sql-injection.md +0 -0
  102. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/knowledge/vulnerabilities/xml-external-entity.md +0 -0
  103. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/false-positive-traps.md +0 -0
  104. {codejury-1.2.0 → codejury-1.3.0}/codejury/domains/web/playbook/severity-rubric.md +0 -0
  105. {codejury-1.2.0 → codejury-1.3.0}/codejury/finding.py +0 -0
  106. {codejury-1.2.0 → codejury-1.3.0}/codejury/guides.py +0 -0
  107. {codejury-1.2.0 → codejury-1.3.0}/codejury/json_parse.py +0 -0
  108. {codejury-1.2.0 → codejury-1.3.0}/codejury/markdown_docs.py +0 -0
  109. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/__init__.py +0 -0
  110. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/anthropic.py +0 -0
  111. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/base.py +0 -0
  112. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/chat_format.py +0 -0
  113. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/claude_agent.py +0 -0
  114. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/factory.py +0 -0
  115. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/litellm.py +0 -0
  116. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/mock.py +0 -0
  117. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/openai.py +0 -0
  118. {codejury-1.2.0 → codejury-1.3.0}/codejury/providers/retry.py +0 -0
  119. {codejury-1.2.0 → codejury-1.3.0}/codejury/report.py +0 -0
  120. {codejury-1.2.0 → codejury-1.3.0}/codejury/resources.py +0 -0
  121. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/__init__.py +0 -0
  122. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/__init__.py +0 -0
  123. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/diff/filter.py +0 -0
  124. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/__init__.py +0 -0
  125. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/agent.py +0 -0
  126. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/gate.py +0 -0
  127. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/model.py +0 -0
  128. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/pass_loop.py +0 -0
  129. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/paths.py +0 -0
  130. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/reviewer.py +0 -0
  131. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/severity.py +0 -0
  132. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/union.py +0 -0
  133. {codejury-1.2.0 → codejury-1.3.0}/codejury/review/repo/verifier.py +0 -0
  134. {codejury-1.2.0 → codejury-1.3.0}/codejury/severity.py +0 -0
  135. {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/dependency_links.txt +0 -0
  136. {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/entry_points.txt +0 -0
  137. {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/requires.txt +0 -0
  138. {codejury-1.2.0 → codejury-1.3.0}/codejury.egg-info/top_level.txt +0 -0
  139. {codejury-1.2.0 → codejury-1.3.0}/setup.cfg +0 -0
  140. {codejury-1.2.0 → codejury-1.3.0}/tests/test_anthropic_provider.py +0 -0
  141. {codejury-1.2.0 → codejury-1.3.0}/tests/test_chat_format.py +0 -0
  142. {codejury-1.2.0 → codejury-1.3.0}/tests/test_claude_agent_provider.py +0 -0
  143. {codejury-1.2.0 → codejury-1.3.0}/tests/test_cli.py +0 -0
  144. {codejury-1.2.0 → codejury-1.3.0}/tests/test_detection.py +0 -0
  145. {codejury-1.2.0 → codejury-1.3.0}/tests/test_domains.py +0 -0
  146. {codejury-1.2.0 → codejury-1.3.0}/tests/test_guides.py +0 -0
  147. {codejury-1.2.0 → codejury-1.3.0}/tests/test_json_parse.py +0 -0
  148. {codejury-1.2.0 → codejury-1.3.0}/tests/test_litellm_provider.py +0 -0
  149. {codejury-1.2.0 → codejury-1.3.0}/tests/test_markdown_docs.py +0 -0
  150. {codejury-1.2.0 → codejury-1.3.0}/tests/test_openai_provider.py +0 -0
  151. {codejury-1.2.0 → codejury-1.3.0}/tests/test_provider_factory.py +0 -0
  152. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_gate.py +0 -0
  153. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_model.py +0 -0
  154. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_pass_loop.py +0 -0
  155. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_paths.py +0 -0
  156. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_severity.py +0 -0
  157. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_union.py +0 -0
  158. {codejury-1.2.0 → codejury-1.3.0}/tests/test_repo_verifier.py +0 -0
  159. {codejury-1.2.0 → codejury-1.3.0}/tests/test_report.py +0 -0
  160. {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.2.0
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=... # a GPT model, the skeptic
118
+ export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
119
119
  export CODEJURY_CHALLENGER_API_KEY=...
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
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=... # a GPT model, the skeptic
76
+ export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
77
77
  export CODEJURY_CHALLENGER_API_KEY=...
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
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-audit engine, a standard single
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-audit flags for `review 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 two cross-cutting artifacts in `inventory/`:
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. For anything the seeded units miss,
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/_severity.md`, and
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
- `_vulnerabilities.md` for the class definitions with vulnerable and secure examples, and
12
- `_false_positive_traps.md` for the recurring ways a static read misjudges them.
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, and denial of service. Money is the
17
- asset, grade every finding by funds moved, locked, or stolen.
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
+ ```
@@ -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 # concurrent requests both pass
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 two cross-cutting artifacts in `inventory/`:
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`. Each sub-review follows the full
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. For anything the seeded units miss, add a unit file by
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/_severity.md`, and
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