codejury 1.2.0__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. {codejury-1.2.0 → codejury-1.4.0}/PKG-INFO +9 -7
  2. {codejury-1.2.0 → codejury-1.4.0}/README.md +8 -6
  3. {codejury-1.2.0 → codejury-1.4.0}/codejury/__init__.py +1 -1
  4. {codejury-1.2.0 → codejury-1.4.0}/codejury/cli.py +62 -34
  5. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/base.py +1 -1
  6. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/__init__.py +10 -3
  7. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/facts/slither.py +1 -1
  8. codejury-1.4.0/codejury/domains/evm/knowledge/guides/protocols/defi-primitives.md +41 -0
  9. codejury-1.4.0/codejury/domains/evm/knowledge/guides/protocols/erc-standards.md +39 -0
  10. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/index.md +6 -0
  11. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/access-control.md +10 -1
  12. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/accounting-precision.md +1 -0
  13. codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/bad-randomness.md +41 -0
  14. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/denial-of-service.md +2 -1
  15. codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/front-running.md +50 -0
  16. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/oracle-price-manipulation.md +1 -0
  17. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/proxy-delegatecall.md +1 -0
  18. codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/reentrancy.md +61 -0
  19. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/signature-replay.md +1 -0
  20. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/vulnerabilities/unchecked-low-level-call.md +8 -1
  21. codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/unsafe-math.md +32 -0
  22. codejury-1.4.0/codejury/domains/evm/knowledge/vulnerabilities/weird-erc20.md +43 -0
  23. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/methodology.md +8 -1
  24. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/slash-command.md +42 -5
  25. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/unit-review.md +24 -4
  26. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/poc.py +2 -2
  27. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/__init__.py +32 -4
  28. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/django.md +1 -1
  29. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/flask.md +6 -1
  30. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/go.md +6 -0
  31. codejury-1.4.0/codejury/domains/web/knowledge/guides/protocols/graphql.md +62 -0
  32. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/index.md +9 -0
  33. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/business-logic.md +1 -0
  34. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/code-injection.md +1 -0
  35. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/command-injection.md +11 -1
  36. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/cors-misconfiguration.md +46 -0
  37. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-request-forgery.md +1 -0
  38. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/cross-site-scripting.md +1 -0
  39. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/hardcoded-secrets.md +1 -0
  40. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/http-request-smuggling.md +49 -0
  41. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/http-response-splitting.md +1 -0
  42. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/improper-authentication.md +1 -0
  43. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/information-exposure.md +1 -0
  44. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-cryptography.md +1 -0
  45. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-deserialization.md +1 -0
  46. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/insecure-direct-object-reference.md +32 -0
  47. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-session-management.md +1 -0
  48. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/insecure-transport.md +1 -0
  49. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/jwt-validation.md +1 -0
  50. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/mass-assignment.md +1 -0
  51. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/missing-authorization.md +1 -0
  52. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/nosql-injection.md +41 -0
  53. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/open-redirect.md +1 -0
  54. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/path-traversal.md +41 -0
  55. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/prototype-pollution.md +46 -0
  56. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/race-condition.md +9 -3
  57. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/replay-attack.md +1 -0
  58. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/resource-exhaustion.md +45 -0
  59. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/security-misconfiguration.md +48 -0
  60. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-request-forgery.md +13 -1
  61. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/server-side-template-injection.md +1 -0
  62. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/sql-injection.md +1 -0
  63. codejury-1.4.0/codejury/domains/web/knowledge/vulnerabilities/unrestricted-file-upload.md +47 -0
  64. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/vulnerabilities/xml-external-entity.md +6 -1
  65. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/methodology.md +9 -3
  66. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/slash-command.md +42 -5
  67. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/unit-review.md +19 -0
  68. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/adversarial.py +2 -2
  69. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/audit.py +1 -1
  70. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/engine.py +2 -2
  71. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/prompts.py +2 -2
  72. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/vulnerabilities.py +0 -1
  73. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/engine.py +106 -66
  74. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/model.py +59 -0
  75. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/reviewer.py +1 -1
  76. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/scaffold.py +87 -18
  77. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/shapes.py +1 -1
  78. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/union.py +1 -1
  79. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/verifier.py +2 -2
  80. {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/PKG-INFO +9 -7
  81. {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/SOURCES.txt +14 -0
  82. {codejury-1.2.0 → codejury-1.4.0}/pyproject.toml +1 -1
  83. {codejury-1.2.0 → codejury-1.4.0}/tests/test_anthropic_provider.py +1 -1
  84. {codejury-1.2.0 → codejury-1.4.0}/tests/test_claude_agent_provider.py +1 -1
  85. {codejury-1.2.0 → codejury-1.4.0}/tests/test_cli.py +6 -3
  86. {codejury-1.2.0 → codejury-1.4.0}/tests/test_diff_adversarial.py +2 -2
  87. {codejury-1.2.0 → codejury-1.4.0}/tests/test_diff_audit.py +1 -1
  88. {codejury-1.2.0 → codejury-1.4.0}/tests/test_domains.py +84 -0
  89. {codejury-1.2.0 → codejury-1.4.0}/tests/test_evals.py +197 -5
  90. {codejury-1.2.0 → codejury-1.4.0}/tests/test_litellm_provider.py +1 -1
  91. {codejury-1.2.0 → codejury-1.4.0}/tests/test_openai_provider.py +1 -1
  92. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_agent.py +2 -2
  93. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_engine.py +80 -6
  94. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_scaffold.py +33 -0
  95. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_union.py +1 -1
  96. {codejury-1.2.0 → codejury-1.4.0}/tests/test_vulnerabilities.py +6 -3
  97. codejury-1.2.0/codejury/domains/evm/knowledge/vulnerabilities/reentrancy.md +0 -38
  98. codejury-1.2.0/codejury/domains/web/knowledge/vulnerabilities/insecure-direct-object-reference.md +0 -25
  99. codejury-1.2.0/codejury/domains/web/knowledge/vulnerabilities/path-traversal.md +0 -35
  100. {codejury-1.2.0 → codejury-1.4.0}/LICENSE +0 -0
  101. {codejury-1.2.0 → codejury-1.4.0}/codejury/__main__.py +0 -0
  102. {codejury-1.2.0 → codejury-1.4.0}/codejury/detection.py +0 -0
  103. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/__init__.py +0 -0
  104. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/detection.yaml +0 -0
  105. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/facts/__init__.py +0 -0
  106. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/facts/call_path.py +0 -0
  107. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/knowledge/guides/languages/solidity.md +0 -0
  108. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/false-positive-traps.md +0 -0
  109. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/evm/playbook/severity-rubric.md +0 -0
  110. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/registry.py +0 -0
  111. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/detection.yaml +0 -0
  112. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/go/echo.md +0 -0
  113. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/go/gin.md +0 -0
  114. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/express.md +0 -0
  115. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/javascript/nestjs.md +0 -0
  116. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/celery.md +0 -0
  117. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/frameworks/python/fastapi.md +0 -0
  118. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/javascript.md +0 -0
  119. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/python.md +0 -0
  120. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/languages/typescript.md +0 -0
  121. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/knowledge/guides/protocols/oauth.md +0 -0
  122. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/false-positive-traps.md +0 -0
  123. {codejury-1.2.0 → codejury-1.4.0}/codejury/domains/web/playbook/severity-rubric.md +0 -0
  124. {codejury-1.2.0 → codejury-1.4.0}/codejury/finding.py +0 -0
  125. {codejury-1.2.0 → codejury-1.4.0}/codejury/guides.py +0 -0
  126. {codejury-1.2.0 → codejury-1.4.0}/codejury/json_parse.py +0 -0
  127. {codejury-1.2.0 → codejury-1.4.0}/codejury/markdown_docs.py +0 -0
  128. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/__init__.py +0 -0
  129. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/anthropic.py +0 -0
  130. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/base.py +0 -0
  131. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/chat_format.py +0 -0
  132. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/claude_agent.py +0 -0
  133. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/factory.py +0 -0
  134. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/litellm.py +0 -0
  135. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/mock.py +0 -0
  136. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/openai.py +0 -0
  137. {codejury-1.2.0 → codejury-1.4.0}/codejury/providers/retry.py +0 -0
  138. {codejury-1.2.0 → codejury-1.4.0}/codejury/report.py +0 -0
  139. {codejury-1.2.0 → codejury-1.4.0}/codejury/resources.py +0 -0
  140. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/__init__.py +0 -0
  141. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/__init__.py +0 -0
  142. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/diff/filter.py +0 -0
  143. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/__init__.py +0 -0
  144. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/agent.py +0 -0
  145. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/gate.py +0 -0
  146. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/pass_loop.py +0 -0
  147. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/paths.py +0 -0
  148. {codejury-1.2.0 → codejury-1.4.0}/codejury/review/repo/severity.py +0 -0
  149. {codejury-1.2.0 → codejury-1.4.0}/codejury/severity.py +0 -0
  150. {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/dependency_links.txt +0 -0
  151. {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/entry_points.txt +0 -0
  152. {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/requires.txt +0 -0
  153. {codejury-1.2.0 → codejury-1.4.0}/codejury.egg-info/top_level.txt +0 -0
  154. {codejury-1.2.0 → codejury-1.4.0}/setup.cfg +0 -0
  155. {codejury-1.2.0 → codejury-1.4.0}/tests/test_chat_format.py +0 -0
  156. {codejury-1.2.0 → codejury-1.4.0}/tests/test_detection.py +0 -0
  157. {codejury-1.2.0 → codejury-1.4.0}/tests/test_guides.py +0 -0
  158. {codejury-1.2.0 → codejury-1.4.0}/tests/test_json_parse.py +0 -0
  159. {codejury-1.2.0 → codejury-1.4.0}/tests/test_markdown_docs.py +0 -0
  160. {codejury-1.2.0 → codejury-1.4.0}/tests/test_provider_factory.py +0 -0
  161. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_gate.py +0 -0
  162. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_model.py +0 -0
  163. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_pass_loop.py +0 -0
  164. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_paths.py +0 -0
  165. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_severity.py +0 -0
  166. {codejury-1.2.0 → codejury-1.4.0}/tests/test_repo_verifier.py +0 -0
  167. {codejury-1.2.0 → codejury-1.4.0}/tests/test_report.py +0 -0
  168. {codejury-1.2.0 → codejury-1.4.0}/tests/test_retry_provider.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codejury
3
- Version: 1.2.0
3
+ Version: 1.4.0
4
4
  Summary: AI-assisted security review for code diffs and whole repositories.
5
5
  Author: AISecLabs
6
6
  License-Expression: MIT
@@ -84,8 +84,10 @@ codejury install-slash-command # Claude Code
84
84
  codejury install-slash-command --agent codex # Codex
85
85
  ```
86
86
 
87
- `install-slash-command` copies `/codejury-review-repo` into the selected agent's command
88
- directory. Pass `--dir` to install it somewhere else.
87
+ `install-slash-command` copies `/codejury-review` into the selected agent's command
88
+ directory. The one command dispatches by argument: a directory runs the Repo Review
89
+ fan-out, a diff file or git range runs the coded Diff Review. Pass `--dir` to install it
90
+ somewhere else.
89
91
 
90
92
  ## Configure a Model Backend
91
93
 
@@ -115,10 +117,10 @@ example a Claude base finder challenged by GPT and confirmed by Claude:
115
117
 
116
118
  ```bash
117
119
  export CODEJURY_CHALLENGER_PROVIDER=openai
118
- export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
120
+ export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
119
121
  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
122
+ export CODEJURY_CHALLENGER_WIRE_API=responses # the gpt-5 reasoning models speak Responses
123
+ export CODEJURY_JUDGE_MODEL=... # a Claude model, the confirmer, distinct from the challenger
122
124
  ```
123
125
 
124
126
  The same `CODEJURY_FINDER_*` / `CODEJURY_CHALLENGER_*` / `CODEJURY_JUDGE_*` and the matching
@@ -232,7 +234,7 @@ _pocs.md PoC reconciliation, planned versus delivered
232
234
  Then run the interactive slash command in Claude Code or Codex:
233
235
 
234
236
  ```text
235
- /codejury-review-repo /path/to/repo
237
+ /codejury-review /path/to/repo
236
238
  ```
237
239
 
238
240
  The agent maps the attack surface, fills the authorization model, runs one focused
@@ -42,8 +42,10 @@ codejury install-slash-command # Claude Code
42
42
  codejury install-slash-command --agent codex # Codex
43
43
  ```
44
44
 
45
- `install-slash-command` copies `/codejury-review-repo` into the selected agent's command
46
- directory. Pass `--dir` to install it somewhere else.
45
+ `install-slash-command` copies `/codejury-review` into the selected agent's command
46
+ directory. The one command dispatches by argument: a directory runs the Repo Review
47
+ fan-out, a diff file or git range runs the coded Diff Review. Pass `--dir` to install it
48
+ somewhere else.
47
49
 
48
50
  ## Configure a Model Backend
49
51
 
@@ -73,10 +75,10 @@ example a Claude base finder challenged by GPT and confirmed by Claude:
73
75
 
74
76
  ```bash
75
77
  export CODEJURY_CHALLENGER_PROVIDER=openai
76
- export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
78
+ export CODEJURY_CHALLENGER_MODEL=... # a GPT model, the skeptic
77
79
  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
80
+ export CODEJURY_CHALLENGER_WIRE_API=responses # the gpt-5 reasoning models speak Responses
81
+ export CODEJURY_JUDGE_MODEL=... # a Claude model, the confirmer, distinct from the challenger
80
82
  ```
81
83
 
82
84
  The same `CODEJURY_FINDER_*` / `CODEJURY_CHALLENGER_*` / `CODEJURY_JUDGE_*` and the matching
@@ -190,7 +192,7 @@ _pocs.md PoC reconciliation, planned versus delivered
190
192
  Then run the interactive slash command in Claude Code or Codex:
191
193
 
192
194
  ```text
193
- /codejury-review-repo /path/to/repo
195
+ /codejury-review /path/to/repo
194
196
  ```
195
197
 
196
198
  The agent maps the attack surface, fills the authorization model, runs one focused
@@ -1,6 +1,6 @@
1
1
  """AI code security review tool.
2
2
 
3
- Two paths matched to their nature: a coded diff-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")
@@ -366,8 +366,10 @@ def main(argv: list[str] | None = None) -> int:
366
366
  _add_role_backend_args(roles, role)
367
367
 
368
368
  tuning = repo.add_argument_group("run tuning (advanced)", "only affect --run, sane defaults otherwise")
369
- tuning.add_argument("--max-passes", type=int, default=24, dest="max_passes",
370
- help="cap on diverse passes before stopping")
369
+ tuning.add_argument("--max-passes", type=int, default=None, dest="max_passes",
370
+ help="cap on diverse passes before stopping, default scales to the domain: "
371
+ "(min-lens-shots + 1) * number of lenses, so every lens meets its shot "
372
+ "floor with a cycle of headroom for convergence")
371
373
  tuning.add_argument("--converge-after", type=int, default=2, dest="converge_after",
372
374
  help="stop once this many consecutive passes add no new finding")
373
375
  tuning.add_argument("--min-lens-shots", type=int, default=2, dest="min_lens_shots",
@@ -382,12 +384,12 @@ def main(argv: list[str] | None = None) -> int:
382
384
  _add_domain_arg(repo)
383
385
 
384
386
  inst = sub.add_parser("install-slash-command",
385
- help="install the /codejury-review-repo slash command for an agent")
387
+ help="install the /codejury-review slash command for an agent")
386
388
  inst.add_argument("--agent", choices=("claude", "codex"), default="claude",
387
389
  help="which agent's command directory to install into")
388
390
  inst.add_argument("--dir", default=None, help="explicit target directory, overrides --agent")
389
391
  inst.add_argument("--force", action="store_true",
390
- help="overwrite an existing codejury-review-repo.md at the destination")
392
+ help="overwrite an existing codejury-review.md at the destination")
391
393
  inst.add_argument("--domain", default="web", metavar="DOMAIN",
392
394
  help="which domain's slash command to install, one of: " + ", ".join(available_domains()))
393
395
 
@@ -411,6 +413,49 @@ def _diff_provider(args, spec, kind: str):
411
413
  return _role_provider(args, spec)
412
414
 
413
415
 
416
+ def build_diff_providers(args):
417
+ """Resolve the diff seats into providers exactly as `review diff` does, so a non-CLI caller
418
+ such as the eval runs a case through the same wiring a user gets. Returns the base provider
419
+ and model the audit needs plus the per-role finder, challenger, and judge providers and
420
+ models, the role fields None in standard mode. The single source the CLI and the eval share,
421
+ so the probe cannot drift from the product on which model or seat reviews a diff."""
422
+ base = _base_spec(args)
423
+ if args.mode == "adversarial":
424
+ roles = {r: _role_spec(args, r, base) for r in ("finder", "challenger", "judge")}
425
+ kinds = {r: _seat_backend(s, args.executor) for r, s in roles.items()}
426
+ agent_roles = [r for r, k in kinds.items() if k == "agent"]
427
+ _warn_roles_under_agent(args, agent_roles)
428
+ if args.executor == "auto":
429
+ _note_subscription_fallback(agent_roles)
430
+ fp = _diff_provider(args, roles["finder"], kinds["finder"])
431
+ cp = _diff_provider(args, roles["challenger"], kinds["challenger"])
432
+ jp = _diff_provider(args, roles["judge"], kinds["judge"])
433
+ return (fp, roles["finder"]["model"], fp, roles["finder"]["model"],
434
+ cp, roles["challenger"]["model"], jp, roles["judge"]["model"])
435
+ base_kind = _seat_backend(base, args.executor)
436
+ if args.executor == "auto" and base_kind == "agent":
437
+ _note_subscription_fallback(("audit",))
438
+ return (_diff_provider(args, base, base_kind), base["model"], None, None, None, None, None, None)
439
+
440
+
441
+ def diff_args_from_env(mode: str, *, executor: str = "auto", rounds: int = 3):
442
+ """A diff args namespace from the environment defaults, the same values `review diff` reads
443
+ when no flag is passed, so `build_diff_providers` builds the user's real wiring. Lets the eval
444
+ drive the audit through the product path rather than a hardcoded provider."""
445
+ from types import SimpleNamespace
446
+ ns = dict(provider=DEFAULT_PROVIDER, model=DEFAULT_MODEL, api_key=DEFAULT_API_KEY,
447
+ api_base=DEFAULT_API_BASE, retries=DEFAULT_RETRIES, timeout=DEFAULT_TIMEOUT,
448
+ executor=executor, mode=mode, rounds=rounds)
449
+ for role in ROLES:
450
+ d = DEFAULT_ROLE_BACKENDS[role]
451
+ ns[f"{role}_provider"] = d["provider"]
452
+ ns[f"{role}_model"] = d["model"]
453
+ ns[f"{role}_api_key"] = d["api_key"]
454
+ ns[f"{role}_api_base"] = d["api_base"]
455
+ ns[f"{role}_wire_api"] = d["wire_api"]
456
+ return SimpleNamespace(**ns)
457
+
458
+
414
459
  def _dispatch(args, parser) -> int:
415
460
  scope = getattr(args, "scope", None)
416
461
  if args.command == "review" and scope == "diff":
@@ -424,29 +469,8 @@ def _dispatch(args, parser) -> int:
424
469
  else:
425
470
  diff = _read_diff(args)
426
471
  domain = resolve_domain(args.domain, _diff_paths(diff))
427
- base = _base_spec(args)
428
- if args.mode == "adversarial":
429
- # each seat resolves on its own key, so a keyless Claude finder and judge ride the
430
- # subscription while an OpenAI challenger uses its own key. The finder backs the base
431
- # default audit_diff needs, the per-role providers below override it field by field
432
- roles = {r: _role_spec(args, r, base) for r in ("finder", "challenger", "judge")}
433
- kinds = {r: _seat_backend(s, args.executor) for r, s in roles.items()}
434
- agent_roles = [r for r, k in kinds.items() if k == "agent"]
435
- _warn_roles_under_agent(args, agent_roles)
436
- if args.executor == "auto":
437
- _note_subscription_fallback(agent_roles)
438
- finder_provider = _diff_provider(args, roles["finder"], kinds["finder"])
439
- challenger_provider = _diff_provider(args, roles["challenger"], kinds["challenger"])
440
- judge_provider = _diff_provider(args, roles["judge"], kinds["judge"])
441
- finder_model = roles["finder"]["model"]
442
- challenger_model = roles["challenger"]["model"]
443
- judge_model = roles["judge"]["model"]
444
- provider, model = finder_provider, roles["finder"]["model"]
445
- else:
446
- base_kind = _seat_backend(base, args.executor)
447
- if args.executor == "auto" and base_kind == "agent":
448
- _note_subscription_fallback(("audit",))
449
- provider, model = _diff_provider(args, base, base_kind), base["model"]
472
+ (provider, model, finder_provider, finder_model,
473
+ challenger_provider, challenger_model, judge_provider, judge_model) = build_diff_providers(args)
450
474
  kept, _, degraded = audit_diff(
451
475
  diff, provider=provider, model=model,
452
476
  mode=args.mode, max_rounds=args.rounds, filter_findings=not args.no_filter,
@@ -457,7 +481,7 @@ def _dispatch(args, parser) -> int:
457
481
  print(render(args.fmt, kept))
458
482
  if degraded:
459
483
  # the adversarial judge was unusable and the result fell back to the
460
- # unjudged set, so this is a failed audit, not a clean pass, invariant 3
484
+ # unjudged set, so this is a failed audit, not a clean pass, invariant 4
461
485
  print("error: the adversarial audit degraded on an unusable judge reply, "
462
486
  "the result is incomplete and not a clean pass", file=sys.stderr)
463
487
  return 1 if degraded or gate(kept, args.fail_on) else 0
@@ -518,13 +542,17 @@ def _dispatch(args, parser) -> int:
518
542
  f"PoC reconciliation in {fr.workspace}/_pocs.md")
519
543
  if fr.verify and fr.verify.errors:
520
544
  print(f"WARNING: {fr.verify.errors} verification calls failed. Re-run to resume.", file=sys.stderr)
521
- return 1 # fail loud: an incomplete verification is not a clean finalize, invariant 3
545
+ return 1 # fail loud: an incomplete verification is not a clean finalize, invariant 4
522
546
  return 0
523
547
 
524
548
  if args.command == "review" and scope == "repo" and args.run:
525
549
  from codejury.review.repo.engine import run_repo_review
526
550
  from codejury.review.repo.verifier import ModelVerifier
527
551
  domain = resolve_domain(args.domain, _repo_file_names(args.directory))
552
+ # scale the pass cap to the domain, so the min-lens-shots floor is always meetable and the
553
+ # convergence early-stop can fire, with one lens cycle of headroom above the floor
554
+ if args.max_passes is None:
555
+ args.max_passes = (args.min_lens_shots + 1) * len(domain.lenses)
528
556
  _warn_secondary_env()
529
557
  base = _base_spec(args)
530
558
  finder = _role_spec(args, "finder", base)
@@ -600,7 +628,7 @@ def _dispatch(args, parser) -> int:
600
628
  file=sys.stderr)
601
629
  print(f"Findings written to {res.scaffold.workspace}/findings/ and {res.scaffold.workspace}/findings.json")
602
630
  # fail loud: a partial run or a run still finding issues at the cap must not exit clean,
603
- # invariant 3 and the stability red line, so a non-converged run is not reported as done
631
+ # invariant 4 and the stability red line, so a non-converged run is not reported as done
604
632
  return 1 if failures or not acc.converged else 0
605
633
 
606
634
  if args.command == "review" and scope == "repo":
@@ -633,7 +661,7 @@ def _dispatch(args, parser) -> int:
633
661
  print(
634
662
  "This command sets up the review, it does not find anything itself. Next, have an "
635
663
  f"interactive agent follow {res.workspace}/METHODOLOGY.md to run the review, or use the "
636
- "/codejury-review-repo command in Claude Code or Codex. The agent proposes findings in "
664
+ "/codejury-review command in Claude Code or Codex. The agent proposes findings in "
637
665
  f"{res.workspace}/candidates/, finalize confirms them into {res.workspace}/findings/."
638
666
  )
639
667
  return 0
@@ -645,14 +673,14 @@ def _dispatch(args, parser) -> int:
645
673
  "codex": Path.home() / ".codex" / "prompts",
646
674
  }
647
675
  target_dir = Path(args.dir) if args.dir else agent_dirs[args.agent]
648
- dst = target_dir / "codejury-review-repo.md"
676
+ dst = target_dir / "codejury-review.md"
649
677
  if dst.exists() and not args.force:
650
678
  print(f"{dst} already exists. Re-run with --force to overwrite it.", file=sys.stderr)
651
679
  return 1
652
680
  target_dir.mkdir(parents=True, exist_ok=True)
653
681
  dst.write_text(slash_command_file.read_text(encoding="utf-8"), encoding="utf-8")
654
682
  print(f"Installed slash command to {dst}")
655
- print("Run it in the agent with: /codejury-review-repo <repository>")
683
+ print("Run it in the agent with: /codejury-review <repository or diff>")
656
684
  return 0
657
685
 
658
686
  if args.command == "review":
@@ -97,7 +97,7 @@ class Domain:
97
97
  class BackendUnavailable(RuntimeError):
98
98
  """A tool-backed seam was asked to work but its external tool is not installed. Raised,
99
99
  never swallowed into an empty result, so a missing toolchain is a loud failure and not a
100
- silently clean review, invariant 3."""
100
+ silently clean review, invariant 4."""
101
101
 
102
102
 
103
103
  @dataclass(frozen=True, kw_only=True)
@@ -13,15 +13,22 @@ from pathlib import Path
13
13
  from codejury.domains.base import Domain
14
14
  from codejury.domains.evm.facts.slither import SlitherFacts
15
15
 
16
- # the repo-review pass lenses for contracts: each pass leads with one, the empty lens
17
- # reviews every class
16
+ # the repo-review pass lenses for contracts: each pass leads with one class, the empty lens
17
+ # reviews every class. A named lens is a reliable focused pass and the empty catch-all is not,
18
+ # so every shipped contract class gets its own lens rather than relying on the catch-all.
18
19
  EVM_LENSES = (
19
20
  "access-control",
20
21
  "reentrancy",
21
- "oracle-manipulation",
22
+ "oracle-price-manipulation",
22
23
  "accounting-precision",
23
24
  "signature-replay",
24
25
  "denial-of-service",
26
+ "bad-randomness",
27
+ "front-running",
28
+ "proxy-delegatecall",
29
+ "unchecked-low-level-call",
30
+ "unsafe-math",
31
+ "weird-erc20",
25
32
  "",
26
33
  )
27
34
 
@@ -4,7 +4,7 @@ Solidity compiler, availability is lazy-checked so importing the domain never ne
4
4
  heavy dependency, and slither itself is imported only inside extract.
5
5
 
6
6
  A backend that cannot run fails loud rather than returning empty facts that would read as a
7
- clean review, invariant 3. A missing toolchain raises BackendUnavailable, a compile error
7
+ clean review, invariant 4. A missing toolchain raises BackendUnavailable, a compile error
8
8
  in the target propagates as the native slither error.
9
9
  """
10
10
 
@@ -0,0 +1,41 @@
1
+ ---
2
+ id: defi-primitives
3
+ title: DeFi Primitives
4
+ kind: protocol
5
+ detect:
6
+ content: ["getReserves", "getAmountOut", "addLiquidity", "removeLiquidity", "borrow", "repay", "liquidate", "collateral", "healthFactor", "rewardPerToken", "getVotes", "propose", "quorum", "flashLoan"]
7
+ ---
8
+ # DeFi Primitive Review Notes
9
+
10
+ Protocol-level invariants for the common money legos, the AMM, the lending market, the
11
+ staking vault, and governance. Read the `languages/solidity` guide for the idioms. The
12
+ high-value bugs here are cross-function and cross-contract invariants, price manipulation,
13
+ and transaction ordering, so confirm each property against the real multi-step flow rather
14
+ than a single function.
15
+
16
+ ## AMM and Pricing
17
+ - A price read from spot reserves, `getReserves`, `slot0`, or `balanceOf`, moves in one
18
+ transaction under a flash loan. Use a manipulation-resistant source with a staleness and
19
+ a bounds check, see the oracle-price-manipulation class.
20
+ - A swap, deposit, or redeem that enforces no minimum output and no deadline lets a
21
+ sandwich or a stale transaction execute at an attacker-chosen price. Confirm a `minOut`
22
+ and a deadline bind the trade.
23
+
24
+ ## Lending
25
+ - Collateral value and the health factor are read at action time from a sound oracle, a
26
+ borrow cannot exceed the collateral after rounding, and a liquidation cannot be blocked
27
+ or front-run for free.
28
+ - Closing or liquidating a position updates debt and collateral accounting before any
29
+ external transfer or token callback, see the reentrancy class.
30
+
31
+ ## Staking and Rewards
32
+ - Reward accounting, the `rewardPerToken` accumulator and the per-user checkpoints, updates
33
+ before a stake, withdraw, or claim changes the balance it is computed from, or rewards are
34
+ over or under paid, see the accounting-precision class.
35
+
36
+ ## Governance
37
+ - Voting power is read from a snapshot taken before the proposal, not the live balance, or
38
+ a flash loan borrows votes for one block to pass a proposal. Confirm a checkpoint or
39
+ snapshot and a timelock on execution.
40
+ - A privileged governance or parameter action is gated to the executor and timelocked, see
41
+ the access-control class.
@@ -0,0 +1,39 @@
1
+ ---
2
+ id: erc-standards
3
+ title: ERC Token Standards
4
+ kind: protocol
5
+ detect:
6
+ content: ["ERC20", "ERC721", "ERC1155", "ERC4626", "IERC20", "transferFrom", "safeTransferFrom", "onERC721Received", "onERC1155Received", "convertToShares", "totalAssets", "decimals"]
7
+ ---
8
+ # ERC Token Standards Review Notes
9
+
10
+ These are token-standard invariants, independent of the surrounding protocol. The way each
11
+ shows up in code differs by stack, so read the `languages/solidity` guide for the concrete
12
+ idioms and confirm each invariant against the real flow. The high-value bugs are accounting
13
+ divergence, share-price manipulation, and callback reentrancy rather than syntax.
14
+
15
+ ## ERC20
16
+ - A `transfer` or `transferFrom` return value must be honored or wrapped with SafeERC20,
17
+ a token that returns false or nothing on failure makes the call look successful, see the
18
+ unchecked-low-level-call class.
19
+ - A token address that is not one fixed constant may be fee-on-transfer, deflationary,
20
+ rebasing, or ERC777. Measure the real balance delta across the transfer for any value
21
+ that must be exact, do not credit the requested amount, see the weird-erc20 class.
22
+ - Allowance has the approve then `transferFrom` race, and for a fee token the approval must
23
+ cover the amount plus the fee, not the amount alone.
24
+
25
+ ## ERC721 and ERC1155
26
+ - `safeTransferFrom` invokes `onERC721Received` or `onERC1155Received` on the recipient, a
27
+ hook that hands control to a party the caller chooses, a reentrancy vector. Write state
28
+ before the safe transfer, see the reentrancy class.
29
+ - `setApprovalForAll` grants blanket control of every token of an owner. Confirm it is
30
+ scoped to a trusted operator and revocable, and that a transfer checks owner or approval.
31
+
32
+ ## ERC4626 Vaults
33
+ - Conversion rounds against the user, shares down on deposit and assets down on withdraw,
34
+ or value leaks to the other side, see the accounting-precision class.
35
+ - First-depositor share-price inflation: an empty vault lets the first depositor donate
36
+ assets to inflate the share price and steal later deposits. Confirm a seed deposit, a
37
+ dead-shares mint, or a virtual-offset defense.
38
+ - `totalAssets` must reflect real holdings, not a spot read an attacker can move in one
39
+ transaction, see the oracle-price-manipulation class.
@@ -19,11 +19,17 @@ the target. A finding's `category` is one of these ids.
19
19
 
20
20
  ### Economic and Accounting
21
21
  - `oracle-price-manipulation` spot price or balance as price, flash-loan assisted
22
+ - `front-running` no minOut or deadline, sandwich, profitable action front-run
22
23
  - `accounting-precision` rounding, division before multiplication, ERC-4626 first-depositor inflation
24
+ - `weird-erc20` fee-on-transfer, rebasing, ERC777 callback, received below the requested amount
25
+ - `unsafe-math` unchecked-block overflow, narrowing downcast truncation
23
26
 
24
27
  ### Signatures
25
28
  - `signature-replay` missing nonce, chainid, or domain separator, ecrecover malleability
26
29
 
30
+ ### Randomness
31
+ - `bad-randomness` block.timestamp, blockhash, or prevrandao as a randomness source
32
+
27
33
  Report only real, exploitable, high-confidence issues with a concrete exploit path and a
28
34
  fund or control impact. Do not report gas-optimization or style notes, floating-pragma or
29
35
  compiler advisories, dependency CVEs, or 0.8 checked-arithmetic overflow outside an
@@ -1,10 +1,11 @@
1
1
  ---
2
2
  id: access-control
3
3
  title: Missing or Broken Access Control
4
+ lens: access-control
4
5
  impact: CRITICAL
5
6
  tags: [swc-105, swc-115, access-control, fund-loss]
6
7
  aliases: [missing-access-control, broken-access-control]
7
- triggers: ["onlyOwner", "function mint", "function burn", "function withdraw", "selfdestruct", "tx.origin", "require(msg.sender", "_mint", "setOwner", "transferOwnership", "external", "public"]
8
+ triggers: ["onlyOwner", "function mint", "function burn", "function withdraw", "selfdestruct", "tx.origin", "require(msg.sender", "_mint", "setOwner", "transferOwnership", "function approve", "blacklist", "isBlacklisted", "whenNotPaused", "external", "public"]
8
9
  ---
9
10
 
10
11
  ## Missing or Broken Access Control
@@ -16,6 +17,14 @@ The cause is a missing modifier, a modifier on the wrong function, an authorizat
16
17
  but not the right one. Gate every state-changing privileged function to the exact role,
17
18
  and use `msg.sender`.
18
19
 
20
+ A security gate that some entrypoints enforce and a sibling omits is a broken invariant,
21
+ report the gap even when the omitted path does not itself move value. A token with a
22
+ blacklist, pause, or sanctions check that runs on `transfer` and `transferFrom` but not on
23
+ `approve`, `permit`, or `increaseAllowance` lets a barred account still take part in the
24
+ flow, so the control is not the invariant it claims. Enumerate the sibling entrypoints a
25
+ stated invariant should cover and name the one missing the check, do not clear it because a
26
+ later step looks gated.
27
+
19
28
  ### Vulnerable
20
29
  ```solidity
21
30
  function mint(address to, uint256 amount) external { // no access control
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  id: accounting-precision
3
3
  title: Accounting and Precision Error
4
+ lens: accounting-precision
4
5
  impact: HIGH
5
6
  tags: [accounting, rounding, precision, erc4626, fund-loss]
6
7
  aliases: [accounting, precision]
@@ -0,0 +1,41 @@
1
+ ---
2
+ id: bad-randomness
3
+ title: Insecure Randomness
4
+ lens: bad-randomness
5
+ impact: HIGH
6
+ tags: [swc-120, randomness, fund-loss]
7
+ aliases: [weak-randomness, predictable-random]
8
+ triggers: ["block.timestamp", "blockhash", "block.difficulty", "block.prevrandao", "block.number", "keccak256(abi.encodePacked(block", "random", "% "]
9
+ ---
10
+
11
+ ## Insecure Randomness
12
+
13
+ Deriving an outcome that controls value, a lottery winner, an NFT trait, a mint order, or
14
+ a selection, from on-chain data such as `block.timestamp`, `blockhash`, `block.number`, or
15
+ `block.prevrandao` is predictable. A caller computes the same value in the same block, and
16
+ a validator can influence or withhold it, so the result is gamed and the payout is stolen.
17
+ Use a verifiable randomness source such as a VRF, or a commit-reveal scheme where the seed
18
+ is fixed before it can be known.
19
+
20
+ The timestamp-dependence variant is the same root cause applied to time, not selection: a
21
+ proposer can nudge `block.timestamp` by a few seconds, so logic that gates an auction close,
22
+ a vesting unlock, a deadline, or a reward rate on an exact `block.timestamp` comparison is
23
+ manipulable at the margin. Use a tolerance band, a block-number gate, or an oracle time
24
+ rather than trusting `block.timestamp` to the second.
25
+
26
+ ### Vulnerable
27
+ ```solidity
28
+ function drawWinner(address[] calldata players) external {
29
+ uint256 i = uint256(keccak256(abi.encodePacked(block.timestamp, block.prevrandao))) % players.length;
30
+ _payout(players[i]); // any player predicts i in the same block
31
+ }
32
+ ```
33
+
34
+ ### Secure
35
+ ```solidity
36
+ function drawWinner(uint256 requestId) external {
37
+ uint256 random = vrf.randomness(requestId); // unpredictable until the VRF fulfills
38
+ uint256 i = random % playerCount;
39
+ _payout(players[i]);
40
+ }
41
+ ```
@@ -1,8 +1,9 @@
1
1
  ---
2
2
  id: denial-of-service
3
3
  title: Denial of Service
4
+ lens: denial-of-service
4
5
  impact: HIGH
5
- tags: [swc-113, swc-128, dos, griefing, gas]
6
+ tags: [swc-113, swc-128, dos, griefing, gas, fund-loss]
6
7
  aliases: [dos]
7
8
  triggers: ["for (", "while (", ".length", "push(", "transfer(", "revert", "external call", "unbounded", "for(uint", "selfdestruct"]
8
9
  ---
@@ -0,0 +1,50 @@
1
+ ---
2
+ id: front-running
3
+ title: Front-Running and Slippage
4
+ lens: front-running
5
+ impact: HIGH
6
+ tags: [swc-114, front-running, mev, sandwich, slippage, fund-loss]
7
+ aliases: [mev, sandwich, slippage, frontrunning, front-run]
8
+ triggers: ["swap", "minOut", "amountOutMin", "minAmountOut", "amountOutMinimum", "deadline", "getAmountOut", "addLiquidity", "removeLiquidity", "swapExactTokensForTokens", "0, path", "claim", "harvest"]
9
+ ---
10
+
11
+ ## Front-Running and Slippage
12
+
13
+ A state-changing action is ordered against the victim in the mempool. A swap, deposit,
14
+ or redeem that enforces no minimum output and no deadline executes at an attacker-chosen
15
+ price, or a profitable action is observed and front-run. The classic shape is a sandwich,
16
+ the attacker buys ahead of the victim, lets the victim's trade move the price, then sells
17
+ into it. Bind every trade with a caller-supplied `minOut` and a `deadline`, and treat any
18
+ value the transaction derives from a movable on-chain source as untrusted, see the
19
+ oracle-price-manipulation class.
20
+
21
+ ### Vulnerable
22
+ ```solidity
23
+ function zapIn(uint256 amountIn) external {
24
+ // minOut hardcoded to 0 and block.timestamp as the deadline, so a sandwich sets the price
25
+ uint256[] memory out = router.swapExactTokensForTokens(
26
+ amountIn, 0, path, address(this), block.timestamp
27
+ );
28
+ deposit(out[out.length - 1]);
29
+ }
30
+ ```
31
+
32
+ ### Secure
33
+ ```solidity
34
+ function zapIn(uint256 amountIn, uint256 minOut, uint256 deadline) external {
35
+ uint256[] memory out = router.swapExactTokensForTokens(
36
+ amountIn, minOut, path, address(this), deadline
37
+ );
38
+ deposit(out[out.length - 1]);
39
+ }
40
+ ```
41
+
42
+ ### Not a Finding
43
+
44
+ A trade that passes the caller's `minOut` and `deadline` straight through to the router
45
+ is the expected control and is not reportable. Report it only when the slippage bound is
46
+ hardcoded to 0, the deadline is replaced with `block.timestamp` so it can never expire,
47
+ `minOut` is computed from a spot price read in the same call, or a privileged action's
48
+ ordering grants a concrete and quantifiable fund advantage. A pure view function, an
49
+ action with no economic ordering advantage, or generic mempool-visibility commentary is
50
+ not a finding.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  id: oracle-price-manipulation
3
3
  title: Oracle and Price Manipulation
4
+ lens: oracle-price-manipulation
4
5
  impact: CRITICAL
5
6
  tags: [oracle, price-manipulation, flash-loan, fund-loss]
6
7
  aliases: [oracle, oracle-manipulation, oracle-validation, price-manipulation]
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  id: proxy-delegatecall
3
3
  title: Proxy, Delegatecall, and Initializer Flaws
4
+ lens: proxy-delegatecall
4
5
  impact: CRITICAL
5
6
  tags: [swc-112, delegatecall, proxy, initializer, upgrade, fund-loss]
6
7
  aliases: [delegatecall, unprotected-upgrade]
@@ -0,0 +1,61 @@
1
+ ---
2
+ id: reentrancy
3
+ title: Reentrancy
4
+ lens: reentrancy
5
+ impact: CRITICAL
6
+ tags: [swc-107, reentrancy, fund-loss]
7
+ aliases: [read-only-reentrancy]
8
+ triggers: [".call{value", ".call(", "transfer(", "send(", "external call", "balances[", "withdraw", "nonReentrant", "safeTransfer", "onERC721Received", "tokensToSend", "tokensReceived", "ERC777", "buyout", "before state", "get_virtual_price", "getReserves", "getRate", "sharePrice", "view returns", "balanceOf(address(this))"]
9
+ ---
10
+
11
+ ## Reentrancy
12
+
13
+ An external call hands control to the callee before the contract finishes updating its
14
+ own state, so the callee can call back in and act on the stale pre-update state. The
15
+ classic form drains a balance by re-entering a withdraw before the balance is zeroed.
16
+ Cross-function reentrancy re-enters a different function that shares the same state, and
17
+ read-only reentrancy reads a view mid-update from another protocol. The read-only form
18
+ needs no state write in the reentered call: a price or share view such as a Curve
19
+ `get_virtual_price`, a Balancer `getRate`, or a `getReserves` or `balanceOf(address(this))`
20
+ spot read returns a stale value while a withdraw or exit has sent value but not yet synced
21
+ its reserves, and a consumer that prices collateral off that view is fooled. Write state
22
+ before the external call, or guard with `nonReentrant`, and remember a guard on the mutating
23
+ function does not stop the cross-contract read-only form, the view must be consistent at the
24
+ moment of the external call.
25
+
26
+ A token transfer is an external call too. An ERC777 token runs a `tokensToSend` hook on
27
+ the sender and a `tokensReceived` hook on the recipient, and other tokens add their own
28
+ transfer callback, so a plain `transfer` or `safeTransfer` of an ERC20 can hand control to
29
+ an attacker exactly like a raw `call`. Treat any token whose address is not a fixed trusted
30
+ constant as possibly hook bearing and check every token-move path for reentrancy, not only
31
+ the `call{value}` paths. Clearing a path because the token is assumed to be a normal ERC20
32
+ with no hook is an assumed off-file control unless the token set is pinned to a known
33
+ allowlist, so keep the finding, see the recall red line.
34
+
35
+ Ordering effects before interactions in the one function under review is not enough on its
36
+ own. A transfer hook can reenter a different function that reads or writes the same position,
37
+ loan, or reserve while this flow is only half finished, the cross-function form, so trace
38
+ where control can go during every transfer and check the whole flow's invariants, not just
39
+ the local ones. In a lending, auction, or buyout flow that pays the current holder before it
40
+ finalizes the position's terms, that payout is the callback window, so audit the order of
41
+ the payout against the state writes.
42
+
43
+ ### Vulnerable
44
+ ```solidity
45
+ function withdraw() external {
46
+ uint256 bal = balances[msg.sender];
47
+ (bool ok, ) = msg.sender.call{value: bal}(""); // call before the state update
48
+ require(ok);
49
+ balances[msg.sender] = 0; // too late, attacker reentered
50
+ }
51
+ ```
52
+
53
+ ### Secure
54
+ ```solidity
55
+ function withdraw() external nonReentrant {
56
+ uint256 bal = balances[msg.sender];
57
+ balances[msg.sender] = 0; // effects before interaction
58
+ (bool ok, ) = msg.sender.call{value: bal}("");
59
+ require(ok);
60
+ }
61
+ ```