codejury 0.10.6__tar.gz → 0.11.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 (277) hide show
  1. {codejury-0.10.6 → codejury-0.11.0}/PKG-INFO +2 -2
  2. {codejury-0.10.6 → codejury-0.11.0}/codejury/__init__.py +4 -3
  3. {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/debate.py +10 -11
  4. {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/mock.py +4 -4
  5. codejury-0.11.0/codejury/agents/skill_runner.py +121 -0
  6. codejury-0.11.0/codejury/analysis/attack_path.py +110 -0
  7. codejury-0.11.0/codejury/analysis/repo_model.py +162 -0
  8. {codejury-0.10.6 → codejury-0.11.0}/codejury/assembly.py +41 -38
  9. {codejury-0.10.6 → codejury-0.11.0}/codejury/cli.py +176 -42
  10. codejury-0.11.0/codejury/data/entrypoints.yaml +16 -0
  11. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_allowlist_safe.yaml +1 -1
  12. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_arbitrary_tool_vuln.yaml +1 -1
  13. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_destructive_no_confirm_vuln.yaml +1 -1
  14. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_fixed_enum_safe.yaml +1 -1
  15. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_human_approval_safe.yaml +1 -1
  16. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_model_confirmed_vuln.yaml +1 -1
  17. codejury-0.11.0/codejury/data/golden/api_design_authz_inconsistent_vuln.yaml +23 -0
  18. codejury-0.11.0/codejury/data/golden/api_design_authz_uniform_safe.yaml +22 -0
  19. codejury-0.11.0/codejury/data/golden/api_design_cors_allowlist_safe.yaml +23 -0
  20. codejury-0.11.0/codejury/data/golden/api_design_cors_reflect_origin_vuln.yaml +20 -0
  21. codejury-0.11.0/codejury/data/golden/api_design_cors_wildcard_credentials_vuln.yaml +23 -0
  22. codejury-0.11.0/codejury/data/golden/api_design_mass_assignment_allowlist_safe.yaml +22 -0
  23. codejury-0.11.0/codejury/data/golden/api_design_mass_assignment_vuln.yaml +21 -0
  24. codejury-0.11.0/codejury/data/golden/api_design_single_missing_auth_not_ours_safe.yaml +18 -0
  25. codejury-0.11.0/codejury/data/golden/architecture_clean_surface_safe.yaml +19 -0
  26. codejury-0.11.0/codejury/data/golden/architecture_debug_route_exposed_vuln.yaml +21 -0
  27. codejury-0.11.0/codejury/data/golden/architecture_internal_route_exposed_vuln.yaml +18 -0
  28. codejury-0.11.0/codejury/data/golden/architecture_legit_admin_route_safe.yaml +19 -0
  29. codejury-0.11.0/codejury/data/golden/architecture_minimal_surface_safe.yaml +18 -0
  30. codejury-0.11.0/codejury/data/golden/architecture_test_echo_route_vuln.yaml +17 -0
  31. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_bcrypt_password.yaml +1 -1
  32. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_jwt_noverify_vuln.yaml +1 -1
  33. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_jwt_verified_safe.yaml +1 -1
  34. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_sha256_checksum_safe.yaml +1 -1
  35. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_sha256_password.yaml +1 -1
  36. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_weak_hash_indirect_vuln.yaml +1 -1
  37. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authz_idor_vuln.yaml +1 -1
  38. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authz_owner_safe.yaml +1 -1
  39. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/business_logic_atomic_safe.yaml +1 -1
  40. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/business_logic_price_tamper_vuln.yaml +1 -1
  41. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/business_logic_server_checked_safe.yaml +1 -1
  42. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/business_logic_toctou_vuln.yaml +1 -1
  43. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/cmdi_fixed_argv_safe.yaml +1 -1
  44. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/cmdi_ossystem_vuln.yaml +1 -1
  45. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/cmdi_subprocess_safe.yaml +1 -1
  46. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/crypto_aesgcm_nonce_safe.yaml +1 -1
  47. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/crypto_aesgcm_safe.yaml +1 -1
  48. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/crypto_ecb_vuln.yaml +1 -1
  49. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/crypto_static_iv_vuln.yaml +1 -1
  50. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/data_protection_cleartext_http_vuln.yaml +1 -1
  51. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/data_protection_encrypted_at_rest_safe.yaml +1 -1
  52. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/data_protection_plaintext_pii_vuln.yaml +1 -1
  53. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/data_protection_tokenized_safe.yaml +1 -1
  54. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/dependency_config_debug_off_safe.yaml +1 -1
  55. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/dependency_config_debug_true_vuln.yaml +1 -1
  56. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/dependency_config_tls_verify_off_vuln.yaml +1 -1
  57. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/dependency_config_tls_verify_on_safe.yaml +1 -1
  58. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/deserialize_json_safe.yaml +1 -1
  59. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/deserialize_pickle_vuln.yaml +1 -1
  60. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/error_logging_redacted_safe.yaml +1 -1
  61. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/error_logging_reraise_safe.yaml +1 -1
  62. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/error_logging_secret_leak_vuln.yaml +1 -1
  63. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/error_logging_swallow_vuln.yaml +1 -1
  64. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_escaped_output_safe.yaml +1 -1
  65. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_exec_output_vuln.yaml +1 -1
  66. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_innerhtml_output_vuln.yaml +1 -1
  67. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_json_response_safe.yaml +1 -1
  68. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_output_to_sql_vuln.yaml +1 -1
  69. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_schema_validated_safe.yaml +1 -1
  70. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/literal_eval_safe.yaml +1 -1
  71. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/output_encoding_header_injection_vuln.yaml +1 -1
  72. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/path_basename_safe.yaml +1 -1
  73. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/path_contained_safe.yaml +1 -1
  74. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/path_traversal_vuln.yaml +1 -1
  75. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_delimited_data_safe.yaml +1 -1
  76. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_format_role_vuln.yaml +1 -1
  77. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_indirect_rag_vuln.yaml +1 -1
  78. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_system_concat_vuln.yaml +1 -1
  79. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_user_content_concat_safe.yaml +1 -1
  80. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_user_role_safe.yaml +1 -1
  81. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sc_pinned_safe.yaml +1 -1
  82. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sc_safetensors_safe.yaml +1 -1
  83. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sc_torch_load_vuln.yaml +1 -1
  84. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sc_trust_remote_code_vuln.yaml +1 -1
  85. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/secrets_env_safe.yaml +1 -1
  86. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/secrets_from_config_safe.yaml +1 -1
  87. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/secrets_hardcoded_token_vuln.yaml +1 -1
  88. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/secrets_hardcoded_vuln.yaml +1 -1
  89. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/session_cookie_flags_safe.yaml +1 -1
  90. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/session_fixation_vuln.yaml +1 -1
  91. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/session_insecure_cookie_vuln.yaml +1 -1
  92. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/session_secure_cookie_safe.yaml +1 -1
  93. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sql_constant_concat_safe.yaml +1 -1
  94. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sqli_format_vuln.yaml +1 -1
  95. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sqli_fstring_query.yaml +1 -1
  96. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sqli_indirect_var_vuln.yaml +1 -1
  97. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sqli_parameterized_query.yaml +1 -1
  98. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ssrf_allowlist_safe.yaml +1 -1
  99. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ssrf_constant_url_safe.yaml +1 -1
  100. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ssrf_substring_allowlist_bypass_vuln.yaml +1 -1
  101. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ssrf_user_url_vuln.yaml +1 -1
  102. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xfile_idor_no_check_vuln.yaml +1 -1
  103. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xfile_idor_owner_checked_safe.yaml +1 -1
  104. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xfile_path_sanitized_safe.yaml +1 -1
  105. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xfile_path_tainted_vuln.yaml +1 -1
  106. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xss_innerhtml_constant_safe.yaml +1 -1
  107. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xss_innerhtml_vuln.yaml +1 -1
  108. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xss_textcontent_safe.yaml +1 -1
  109. codejury-0.11.0/codejury/data/poc/cmdi.yaml +14 -0
  110. codejury-0.11.0/codejury/data/poc/path.yaml +12 -0
  111. codejury-0.11.0/codejury/data/poc/sqli.yaml +14 -0
  112. codejury-0.11.0/codejury/data/poc/ssrf.yaml +15 -0
  113. codejury-0.11.0/codejury/data/skills/api_design/SKILL.md +116 -0
  114. codejury-0.11.0/codejury/data/skills/api_design/skill.yaml +8 -0
  115. codejury-0.11.0/codejury/data/skills/architecture/SKILL.md +52 -0
  116. codejury-0.11.0/codejury/data/skills/architecture/skill.yaml +8 -0
  117. codejury-0.11.0/codejury/data/skills/authn/SKILL.md +59 -0
  118. codejury-0.11.0/codejury/data/skills/authn/skill.yaml +11 -0
  119. codejury-0.11.0/codejury/data/skills/authz/SKILL.md +46 -0
  120. codejury-0.11.0/codejury/data/skills/authz/skill.yaml +11 -0
  121. codejury-0.11.0/codejury/data/skills/business_logic/SKILL.md +50 -0
  122. codejury-0.11.0/codejury/data/skills/business_logic/skill.yaml +11 -0
  123. codejury-0.11.0/codejury/data/skills/crypto/SKILL.md +55 -0
  124. codejury-0.11.0/codejury/data/skills/crypto/skill.yaml +12 -0
  125. codejury-0.11.0/codejury/data/skills/data_protection/SKILL.md +50 -0
  126. codejury-0.11.0/codejury/data/skills/data_protection/skill.yaml +11 -0
  127. codejury-0.11.0/codejury/data/skills/dependency_config/SKILL.md +55 -0
  128. codejury-0.11.0/codejury/data/skills/dependency_config/skill.yaml +11 -0
  129. codejury-0.11.0/codejury/data/skills/error_logging/SKILL.md +45 -0
  130. codejury-0.11.0/codejury/data/skills/error_logging/skill.yaml +11 -0
  131. codejury-0.11.0/codejury/data/skills/excessive_agency/SKILL.md +58 -0
  132. codejury-0.11.0/codejury/data/skills/excessive_agency/skill.yaml +11 -0
  133. codejury-0.11.0/codejury/data/skills/input_validation/SKILL.md +128 -0
  134. codejury-0.11.0/codejury/data/skills/input_validation/skill.yaml +13 -0
  135. codejury-0.11.0/codejury/data/skills/insecure_output_handling/SKILL.md +62 -0
  136. codejury-0.11.0/codejury/data/skills/insecure_output_handling/skill.yaml +11 -0
  137. codejury-0.11.0/codejury/data/skills/model_supply_chain/SKILL.md +57 -0
  138. codejury-0.11.0/codejury/data/skills/model_supply_chain/skill.yaml +11 -0
  139. codejury-0.11.0/codejury/data/skills/output_encoding/SKILL.md +45 -0
  140. codejury-0.11.0/codejury/data/skills/output_encoding/skill.yaml +11 -0
  141. codejury-0.11.0/codejury/data/skills/prompt_injection/SKILL.md +62 -0
  142. codejury-0.11.0/codejury/data/skills/prompt_injection/skill.yaml +10 -0
  143. codejury-0.11.0/codejury/data/skills/secrets/SKILL.md +48 -0
  144. codejury-0.11.0/codejury/data/skills/secrets/skill.yaml +10 -0
  145. codejury-0.11.0/codejury/data/skills/session/SKILL.md +47 -0
  146. codejury-0.11.0/codejury/data/skills/session/skill.yaml +11 -0
  147. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/tasks/audit_diff_debate.yaml +1 -1
  148. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/tasks/quick_scan_single.yaml +1 -1
  149. {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/artifact.py +1 -1
  150. {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/context.py +5 -5
  151. {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/observation.py +25 -0
  152. codejury-0.11.0/codejury/domain/skill.py +117 -0
  153. {codejury-0.10.6 → codejury-0.11.0}/codejury/evaluation.py +15 -15
  154. {codejury-0.10.6 → codejury-0.11.0}/codejury/infrastructure/cache.py +23 -12
  155. {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/base.py +2 -2
  156. codejury-0.11.0/codejury/orchestrators/skill_pipeline.py +32 -0
  157. {codejury-0.10.6 → codejury-0.11.0}/codejury/reporting.py +46 -13
  158. {codejury-0.10.6 → codejury-0.11.0}/codejury/resources.py +4 -2
  159. codejury-0.11.0/codejury/sandbox.py +363 -0
  160. codejury-0.11.0/codejury/selection.py +145 -0
  161. codejury-0.11.0/codejury/sources/api_surface.py +101 -0
  162. {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/repo.py +5 -0
  163. {codejury-0.10.6 → codejury-0.11.0}/codejury/tasks/base.py +19 -18
  164. {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/PKG-INFO +2 -2
  165. {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/SOURCES.txt +70 -23
  166. {codejury-0.10.6 → codejury-0.11.0}/pyproject.toml +3 -3
  167. {codejury-0.10.6 → codejury-0.11.0}/tests/test_adaptive.py +7 -5
  168. codejury-0.11.0/tests/test_api_surface.py +106 -0
  169. {codejury-0.10.6 → codejury-0.11.0}/tests/test_assembly.py +15 -13
  170. codejury-0.11.0/tests/test_attack_path.py +141 -0
  171. {codejury-0.10.6 → codejury-0.11.0}/tests/test_cache.py +23 -20
  172. {codejury-0.10.6 → codejury-0.11.0}/tests/test_challenge.py +5 -5
  173. {codejury-0.10.6 → codejury-0.11.0}/tests/test_cli_audit.py +19 -19
  174. {codejury-0.10.6 → codejury-0.11.0}/tests/test_context.py +2 -2
  175. {codejury-0.10.6 → codejury-0.11.0}/tests/test_debate_agents.py +4 -4
  176. {codejury-0.10.6 → codejury-0.11.0}/tests/test_debate_orchestrator.py +2 -2
  177. {codejury-0.10.6 → codejury-0.11.0}/tests/test_evaluation.py +25 -21
  178. codejury-0.11.0/tests/test_full_review.py +111 -0
  179. {codejury-0.10.6 → codejury-0.11.0}/tests/test_orchestrator.py +2 -2
  180. {codejury-0.10.6 → codejury-0.11.0}/tests/test_reflexion_orchestrator.py +2 -2
  181. codejury-0.11.0/tests/test_repo_model.py +152 -0
  182. {codejury-0.10.6 → codejury-0.11.0}/tests/test_review_fixes.py +4 -16
  183. codejury-0.11.0/tests/test_sandbox.py +195 -0
  184. codejury-0.11.0/tests/test_selection.py +109 -0
  185. codejury-0.11.0/tests/test_skill.py +140 -0
  186. codejury-0.11.0/tests/test_skill_runner.py +113 -0
  187. {codejury-0.10.6 → codejury-0.11.0}/tests/test_taint_gate.py +12 -11
  188. {codejury-0.10.6 → codejury-0.11.0}/tests/test_tasks.py +17 -15
  189. codejury-0.10.6/codejury/agents/verifier.py +0 -143
  190. codejury-0.10.6/codejury/data/capabilities/authentication.yaml +0 -67
  191. codejury-0.10.6/codejury/data/capabilities/authorization.yaml +0 -55
  192. codejury-0.10.6/codejury/data/capabilities/business_logic.yaml +0 -58
  193. codejury-0.10.6/codejury/data/capabilities/crypto.yaml +0 -78
  194. codejury-0.10.6/codejury/data/capabilities/data_protection.yaml +0 -57
  195. codejury-0.10.6/codejury/data/capabilities/dependency_config.yaml +0 -79
  196. codejury-0.10.6/codejury/data/capabilities/error_logging.yaml +0 -49
  197. codejury-0.10.6/codejury/data/capabilities/excessive_agency.yaml +0 -64
  198. codejury-0.10.6/codejury/data/capabilities/input_validation.yaml +0 -171
  199. codejury-0.10.6/codejury/data/capabilities/insecure_output_handling.yaml +0 -65
  200. codejury-0.10.6/codejury/data/capabilities/model_supply_chain.yaml +0 -59
  201. codejury-0.10.6/codejury/data/capabilities/output_encoding.yaml +0 -56
  202. codejury-0.10.6/codejury/data/capabilities/prompt_injection.yaml +0 -67
  203. codejury-0.10.6/codejury/data/capabilities/secrets.yaml +0 -72
  204. codejury-0.10.6/codejury/data/capabilities/session.yaml +0 -60
  205. codejury-0.10.6/codejury/domain/capability.py +0 -151
  206. codejury-0.10.6/codejury/orchestrators/pipeline.py +0 -32
  207. codejury-0.10.6/tests/test_audit_pipeline.py +0 -63
  208. codejury-0.10.6/tests/test_capability.py +0 -81
  209. codejury-0.10.6/tests/test_pipeline_orchestrator.py +0 -59
  210. codejury-0.10.6/tests/test_verifier.py +0 -63
  211. {codejury-0.10.6 → codejury-0.11.0}/LICENSE +0 -0
  212. {codejury-0.10.6 → codejury-0.11.0}/README.md +0 -0
  213. {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/__init__.py +0 -0
  214. {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/base.py +0 -0
  215. {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/parsing.py +0 -0
  216. {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/refuter.py +0 -0
  217. {codejury-0.10.6 → codejury-0.11.0}/codejury/analysis/__init__.py +0 -0
  218. {codejury-0.10.6 → codejury-0.11.0}/codejury/analysis/provenance.py +0 -0
  219. {codejury-0.10.6 → codejury-0.11.0}/codejury/analysis/taint.py +0 -0
  220. {codejury-0.10.6 → codejury-0.11.0}/codejury/baseline.py +0 -0
  221. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/suppressions.yaml +0 -0
  222. {codejury-0.10.6 → codejury-0.11.0}/codejury/data/taint.yaml +0 -0
  223. {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/__init__.py +0 -0
  224. {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/result.py +0 -0
  225. {codejury-0.10.6 → codejury-0.11.0}/codejury/infrastructure/__init__.py +0 -0
  226. {codejury-0.10.6 → codejury-0.11.0}/codejury/infrastructure/json_parse.py +0 -0
  227. {codejury-0.10.6 → codejury-0.11.0}/codejury/integrations/__init__.py +0 -0
  228. {codejury-0.10.6 → codejury-0.11.0}/codejury/integrations/github.py +0 -0
  229. {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/__init__.py +0 -0
  230. {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/adaptive.py +0 -0
  231. {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/challenge.py +0 -0
  232. {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/debate.py +0 -0
  233. {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/reflexion.py +0 -0
  234. {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/single.py +0 -0
  235. {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/taint_gate.py +0 -0
  236. {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/__init__.py +0 -0
  237. {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/anthropic.py +0 -0
  238. {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/base.py +0 -0
  239. {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/litellm.py +0 -0
  240. {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/mock.py +0 -0
  241. {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/openai.py +0 -0
  242. {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/openai_format.py +0 -0
  243. {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/retry.py +0 -0
  244. {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/__init__.py +0 -0
  245. {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/base.py +0 -0
  246. {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/callers.py +0 -0
  247. {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/chunker.py +0 -0
  248. {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/diff.py +0 -0
  249. {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/function.py +0 -0
  250. {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/mock.py +0 -0
  251. {codejury-0.10.6 → codejury-0.11.0}/codejury/suppression.py +0 -0
  252. {codejury-0.10.6 → codejury-0.11.0}/codejury/tasks/__init__.py +0 -0
  253. {codejury-0.10.6 → codejury-0.11.0}/codejury/tasks/registry.py +0 -0
  254. {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/dependency_links.txt +0 -0
  255. {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/entry_points.txt +0 -0
  256. {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/requires.txt +0 -0
  257. {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/top_level.txt +0 -0
  258. {codejury-0.10.6 → codejury-0.11.0}/setup.cfg +0 -0
  259. {codejury-0.10.6 → codejury-0.11.0}/tests/test_anthropic_provider.py +0 -0
  260. {codejury-0.10.6 → codejury-0.11.0}/tests/test_baseline.py +0 -0
  261. {codejury-0.10.6 → codejury-0.11.0}/tests/test_callers.py +0 -0
  262. {codejury-0.10.6 → codejury-0.11.0}/tests/test_diff_source.py +0 -0
  263. {codejury-0.10.6 → codejury-0.11.0}/tests/test_function_source.py +0 -0
  264. {codejury-0.10.6 → codejury-0.11.0}/tests/test_integrations.py +0 -0
  265. {codejury-0.10.6 → codejury-0.11.0}/tests/test_json_parse.py +0 -0
  266. {codejury-0.10.6 → codejury-0.11.0}/tests/test_litellm_provider.py +0 -0
  267. {codejury-0.10.6 → codejury-0.11.0}/tests/test_openai_format.py +0 -0
  268. {codejury-0.10.6 → codejury-0.11.0}/tests/test_openai_provider.py +0 -0
  269. {codejury-0.10.6 → codejury-0.11.0}/tests/test_parsing.py +0 -0
  270. {codejury-0.10.6 → codejury-0.11.0}/tests/test_provenance.py +0 -0
  271. {codejury-0.10.6 → codejury-0.11.0}/tests/test_repo_source.py +0 -0
  272. {codejury-0.10.6 → codejury-0.11.0}/tests/test_reporting.py +0 -0
  273. {codejury-0.10.6 → codejury-0.11.0}/tests/test_retry_provider.py +0 -0
  274. {codejury-0.10.6 → codejury-0.11.0}/tests/test_sarif.py +0 -0
  275. {codejury-0.10.6 → codejury-0.11.0}/tests/test_suppression.py +0 -0
  276. {codejury-0.10.6 → codejury-0.11.0}/tests/test_taint.py +0 -0
  277. {codejury-0.10.6 → codejury-0.11.0}/tests/test_taint_crossfile.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codejury
3
- Version: 0.10.6
4
- Summary: General-purpose Application Security AI audit framework: five-layer architecture, capabilities as first-class data
3
+ Version: 0.11.0
4
+ Summary: General-purpose Application Security AI audit framework: skills as first-class data, whole-repo full-review, attack paths with PoC verification
5
5
  Author: AISecLabs
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/aiseclabs/codejury
@@ -1,8 +1,9 @@
1
1
  """codejury: a general-purpose Application Security AI audit framework.
2
2
 
3
- Five layers: Task, then Capability plus Orchestrator plus Source plus Agent, then Provider, then Infrastructure.
4
- Domain knowledge lives in YAML capability files as a first-class citizen,
5
- aligned with OWASP ASVS.
3
+ Layers: Task, then Skill plus Selector plus Orchestrator plus Source plus Agent,
4
+ then Provider, then Infrastructure. Domain knowledge lives in skill directories
5
+ (a manifest plus a prose playbook) as a first-class citizen, aligned with OWASP
6
+ ASVS and the OWASP LLM Top 10.
6
7
  """
7
8
 
8
9
  from importlib.metadata import PackageNotFoundError, version
@@ -1,6 +1,6 @@
1
1
  """Finder / Challenger / Judge agents for the debate orchestrator.
2
2
 
3
- Each reads the artifact, the capability hints, and the accumulated history
3
+ Each reads the artifact, the skill playbooks, and the accumulated history
4
4
  (prior findings and rebuttals) from the context, calls the provider once, and
5
5
  maps its JSON reply onto observations:
6
6
 
@@ -17,9 +17,9 @@ from __future__ import annotations
17
17
  from codejury.agents.base import Agent
18
18
  from codejury.agents.parsing import one_of, str_list, to_evidence, to_float
19
19
  from codejury.domain.artifact import CodeArtifact
20
- from codejury.domain.capability import Capability
21
20
  from codejury.domain.context import AnalysisContext
22
21
  from codejury.domain.observation import Concession, Finding, Observation, Severity
22
+ from codejury.domain.skill import Skill
23
23
  from codejury.infrastructure.json_parse import extract_json_object
24
24
  from codejury.providers.base import Message, Provider
25
25
 
@@ -77,7 +77,7 @@ class FinderAgent(_DebateAgent):
77
77
  )
78
78
 
79
79
  def run(self, ctx: AnalysisContext) -> list[Observation]:
80
- parts = ["Review the code for security vulnerabilities.", _hints(ctx.capabilities), _DEEP_LENS, _code(ctx.artifact)]
80
+ parts = ["Review the code for security vulnerabilities.", _hints(ctx), _DEEP_LENS, _code(ctx.artifact)]
81
81
  if ctx.round_num > 1 and ctx.history:
82
82
  parts.append(_render_history(ctx.history))
83
83
  parts.append("Concede findings the rebuttals refute, keep the valid ones, and add any you missed.")
@@ -140,14 +140,13 @@ def _code(artifact: CodeArtifact) -> str:
140
140
  return f"Code under review ({artifact.path}):\n```\n{artifact.content}\n```"
141
141
 
142
142
 
143
- def _hints(capabilities: list[Capability]) -> str:
144
- lines = []
145
- for cap in capabilities:
146
- for sub_name, sub in cap.sub_capabilities.items():
147
- for ap in sub.anti_patterns:
148
- tag = f"{ap.cwe} {ap.severity}" if ap.cwe else ap.severity
149
- lines.append(f"- {cap.id}.{sub_name} [{tag}]: {ap.description}")
150
- return "Look especially for:\n" + "\n".join(lines) if lines else ""
143
+ def _hints(ctx: AnalysisContext) -> str:
144
+ """What to look for: the skill playbooks in scope."""
145
+ blocks = []
146
+ for s in ctx.skills:
147
+ standard = f" ({s.standard})" if s.standard else ""
148
+ blocks.append(f"## {s.id}: {s.name}{standard}\n{s.instructions}")
149
+ return "Apply these security skills:\n\n" + "\n\n".join(blocks) if blocks else ""
151
150
 
152
151
 
153
152
  def _render_history(history: list[Observation]) -> str:
@@ -1,8 +1,8 @@
1
1
  """MockAgent: a minimal Agent for the dry-run and tests.
2
2
 
3
3
  It really calls the provider (so the dry-run exercises the agent -> provider
4
- path), then emits one Verdict per in-scope capability, parking the model's reply
5
- in ``reasoning``. It does no real parsing or judgement; ``VerifierAgent`` does.
4
+ path), then emits one Verdict per in-scope skill, parking the model's reply in
5
+ ``reasoning``. It does no real parsing or judgement; ``SkillRunner`` does.
6
6
  """
7
7
 
8
8
  from __future__ import annotations
@@ -28,10 +28,10 @@ class MockAgent(Agent):
28
28
  )
29
29
  return [
30
30
  Verdict(
31
- capability=cap.id,
31
+ capability=skill.id,
32
32
  produced_by=self._role,
33
33
  status="UNKNOWN",
34
34
  reasoning=result.text,
35
35
  )
36
- for cap in ctx.capabilities
36
+ for skill in ctx.skills
37
37
  ]
@@ -0,0 +1,121 @@
1
+ """SkillRunner: execute a skill's playbook against a piece of code.
2
+
3
+ The agent that rules code against a skill. It hands the model the skill's
4
+ SKILL.md playbook verbatim and asks it to rule on each dimension the playbook
5
+ names. One provider call per skill.
6
+
7
+ Parsing is defensive: a missing or malformed reply yields no verdicts rather
8
+ than raising, and unknown status values fall back to UNKNOWN. Invariant 3 is
9
+ enforced here: a problem verdict (VULNERABLE / PARTIAL) with no code location is
10
+ not reportable, so it is dropped.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import get_args
16
+
17
+ from codejury.agents.base import Agent
18
+ from codejury.agents.parsing import one_of, to_evidence, to_float
19
+ from codejury.domain.context import AnalysisContext
20
+ from codejury.domain.observation import Observation, Verdict, VerdictStatus
21
+ from codejury.domain.skill import Skill
22
+ from codejury.infrastructure.json_parse import extract_json_object
23
+ from codejury.providers.base import Message, Provider
24
+
25
+ _VALID_STATUS = set(get_args(VerdictStatus))
26
+ _PROBLEM = {"VULNERABLE", "PARTIAL"} # statuses that require a code location
27
+
28
+ _SYSTEM = (
29
+ "You are a security reviewer executing one specific check skill. Follow the "
30
+ "skill's playbook exactly, ruling on what is fine as well as what is wrong. "
31
+ "Respond with a single JSON object and nothing else."
32
+ )
33
+
34
+ _JSON_SHAPE = (
35
+ '{"verdicts": [{"dimension": "...", '
36
+ '"status": "SECURE|VULNERABLE|PARTIAL|NOT_PRESENT|UNKNOWN", "reasoning": "...", '
37
+ '"evidence": [{"file": "path", "line": 0, "code": "..."}], '
38
+ '"cwe": "CWE-...", "confidence": 0.0}]}'
39
+ )
40
+
41
+
42
+ class SkillRunner(Agent):
43
+ def __init__(self, *, provider: Provider, model: str, max_tokens: int = 2048) -> None:
44
+ self._provider = provider
45
+ self._model = model
46
+ self._max_tokens = max_tokens
47
+
48
+ def run(self, ctx: AnalysisContext) -> list[Observation]:
49
+ out: list[Observation] = []
50
+ for skill in ctx.skills:
51
+ prompt = _build_prompt(ctx.artifact.path, ctx.artifact.content, skill, ctx.artifact.context)
52
+ result = self._provider.complete(
53
+ system=_SYSTEM,
54
+ messages=[Message(role="user", content=prompt)],
55
+ model=self._model,
56
+ max_tokens=self._max_tokens,
57
+ )
58
+ out.extend(_parse_verdicts(result.text, skill))
59
+ return out
60
+
61
+
62
+ def _render_skill(skill: Skill) -> str:
63
+ header = f"Skill: {skill.id} ({skill.name})"
64
+ if skill.standard:
65
+ header += f"\nStandard: {skill.standard}"
66
+ return f"{header}\n\n{skill.instructions}"
67
+
68
+
69
+ def _build_prompt(path: str, content: str, skill: Skill, context: str = "") -> str:
70
+ context_block = (
71
+ f"Related code (call sites / usages elsewhere, for tracing where values come from, "
72
+ f"NOT under review):\n```\n{context}\n```\n\n"
73
+ if context
74
+ else ""
75
+ )
76
+ return (
77
+ "Apply the security check skill below to the code.\n\n"
78
+ f"{_render_skill(skill)}\n\n"
79
+ f"Code under review ({path}):\n```\n{content}\n```\n\n"
80
+ f"{context_block}"
81
+ "Output one verdict per dimension the skill covers, even when SECURE or "
82
+ "NOT_PRESENT, so the report says what was checked and what passed. Cite an "
83
+ "evidence file and line for every VULNERABLE or PARTIAL verdict.\n"
84
+ "For input-driven issues (injection, path traversal, SSRF), mark VULNERABLE only when "
85
+ "untrusted/external input could plausibly reach the sink in the code shown. A constant, "
86
+ "a stored data field, a value from trusted config, or a path or argument the operator "
87
+ "supplies (e.g. a CLI argument) is not attacker-controlled; do not flag it.\n\n"
88
+ "Respond with a single JSON object exactly like:\n" + _JSON_SHAPE
89
+ )
90
+
91
+
92
+ def _has_location(evidence: list) -> bool:
93
+ return any(e.line is not None for e in evidence)
94
+
95
+
96
+ def _parse_verdicts(text: str, skill: Skill) -> list[Verdict]:
97
+ obj = extract_json_object(text)
98
+ if not obj:
99
+ return []
100
+ out: list[Verdict] = []
101
+ for v in obj.get("verdicts", []):
102
+ if not isinstance(v, dict):
103
+ continue
104
+ status = one_of(v.get("status"), _VALID_STATUS, "UNKNOWN")
105
+ evidence = to_evidence(v.get("evidence"))
106
+ if status in _PROBLEM and not _has_location(evidence):
107
+ continue # invariant 3: a problem with no code location is not reportable
108
+ dimension = str(v.get("dimension", "")).strip()
109
+ cwe = str(v.get("cwe", "")).strip() or skill.cwe
110
+ out.append(
111
+ Verdict(
112
+ capability=f"{skill.id}.{dimension}" if dimension else skill.id,
113
+ produced_by="skill",
114
+ status=status,
115
+ reasoning=str(v.get("reasoning", "")),
116
+ cwe=cwe,
117
+ evidence=evidence,
118
+ confidence=to_float(v.get("confidence"), 0.5),
119
+ )
120
+ )
121
+ return out
@@ -0,0 +1,110 @@
1
+ """Suspected attack-path synthesis (P6-03).
2
+
3
+ Turns a flagged problem into a source -> sink path, anchoring each hop to a real
4
+ code location: the external-input expression (source) and the dangerous operation
5
+ the finding already cites (sink). When the source is in the cross-file caller
6
+ (``artifact.context``), the path crosses files.
7
+
8
+ These paths are **suspected**, not proven: they are stitched from the taint
9
+ vocabulary and the finding's evidence, not from a full data-flow graph, so they
10
+ carry ``attack_path_proven=False``. A path is only ever emitted when a real
11
+ external-input anchor exists; nothing is narrated without a code location (P6-05
12
+ upgrades suspected to proven via the deeper graph).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import ast
18
+ import dataclasses
19
+
20
+ from codejury.analysis.provenance import access_path
21
+ from codejury.analysis.taint import TaintVocab, load_vocab
22
+ from codejury.domain.artifact import CodeArtifact
23
+ from codejury.domain.observation import PathStep, is_problem
24
+ from codejury.domain.result import AnalysisResult
25
+
26
+ _CONTEXT_FILE = "<context>"
27
+
28
+
29
+ def _matches(path: str, sources: tuple[str, ...]) -> bool:
30
+ return any(path == s or path.startswith(s + ".") for s in sources)
31
+
32
+
33
+ def _find_source(code: str, sources: tuple[str, ...]) -> tuple[int, str] | None:
34
+ """The earliest external-input expression in ``code`` as (line, access path)."""
35
+ try:
36
+ tree = ast.parse(code)
37
+ except SyntaxError:
38
+ return None
39
+ best: tuple[int, str] | None = None
40
+ for node in ast.walk(tree):
41
+ if isinstance(node, (ast.Attribute, ast.Subscript)):
42
+ path = access_path(node)
43
+ elif isinstance(node, ast.Call):
44
+ path = access_path(node.func)
45
+ else:
46
+ continue
47
+ line = getattr(node, "lineno", None)
48
+ if path and line and _matches(path, sources):
49
+ if best is None or line < best[0]:
50
+ best = (line, path)
51
+ return best
52
+
53
+
54
+ def suspected_path(
55
+ *,
56
+ content: str,
57
+ context: str = "",
58
+ sink_file: str,
59
+ sink_line: int | None,
60
+ sink_note: str = "",
61
+ vocab: TaintVocab | None = None,
62
+ ) -> list[PathStep]:
63
+ """A suspected source -> sink path, or [] when no real source/sink anchor exists.
64
+
65
+ The source is the first external-input expression in the code under review,
66
+ or, failing that, in the cross-file caller context. The sink is the cited
67
+ location. Every returned step has a real line."""
68
+ if sink_line is None:
69
+ return []
70
+ vocab = vocab or load_vocab()
71
+ local = _find_source(content, vocab.sources)
72
+ if local:
73
+ source = PathStep(file=sink_file, line=local[0], role="source", note=local[1])
74
+ else:
75
+ upstream = _find_source(context, vocab.sources) if context else None
76
+ if not upstream:
77
+ return [] # no external-input anchor: do not fabricate a path
78
+ source = PathStep(file=_CONTEXT_FILE, line=upstream[0], role="source", note=upstream[1])
79
+ return [source, PathStep(file=sink_file, line=sink_line, role="sink", note=sink_note)]
80
+
81
+
82
+ def attach_suspected_paths(
83
+ result: AnalysisResult, artifact: CodeArtifact, *, vocab: TaintVocab | None = None
84
+ ) -> AnalysisResult:
85
+ """Add a suspected attack path to each flagged problem that cites a location
86
+ and does not already carry one. Returns the result unchanged when nothing
87
+ applies, so it is cheap to call on every run."""
88
+ if result.error:
89
+ return result
90
+ vocab = vocab or load_vocab()
91
+ enriched = []
92
+ changed = False
93
+ for o in result.observations:
94
+ evidence = getattr(o, "evidence", None)
95
+ if is_problem(o) and evidence and not getattr(o, "attack_path", None):
96
+ sink = next((e for e in evidence if e.line is not None), None)
97
+ if sink is not None:
98
+ steps = suspected_path(
99
+ content=artifact.content,
100
+ context=artifact.context,
101
+ sink_file=sink.file or artifact.path,
102
+ sink_line=sink.line,
103
+ sink_note=o.capability,
104
+ vocab=vocab,
105
+ )
106
+ if steps:
107
+ o = dataclasses.replace(o, attack_path=steps, attack_path_proven=False)
108
+ changed = True
109
+ enriched.append(o)
110
+ return AnalysisResult(observations=enriched, error=result.error) if changed else result
@@ -0,0 +1,162 @@
1
+ """RepoModel (P6-01): a deterministic, AST-built map of a repository.
2
+
3
+ The first stage of a whole-repo audit. Before any model call, this records what
4
+ the repo is: its files and its entrypoints, the functions where external input
5
+ arrives (HTTP routes, CLI commands). Later stages review the API surface (P6-02)
6
+ and trace attack paths (P6-03) on top of it.
7
+
8
+ Entrypoint signatures live in data, ``data/entrypoints.yaml``, so a new framework
9
+ is added without touching this analyzer. Detection is pure AST, no model call, so
10
+ the model is deterministic and cacheable.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import ast
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+
19
+ import yaml
20
+
21
+ from codejury.resources import ENTRYPOINTS_FILE
22
+
23
+
24
+ @dataclass(frozen=True, kw_only=True)
25
+ class Entrypoint:
26
+ file: str
27
+ line: int
28
+ function: str
29
+ kind: str # "http" or "cli"
30
+ framework: str
31
+ route: str = ""
32
+ method: str = ""
33
+
34
+
35
+ @dataclass(frozen=True, kw_only=True)
36
+ class RepoModel:
37
+ root: str
38
+ files: tuple[str, ...]
39
+ entrypoints: tuple[Entrypoint, ...]
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class _Signatures:
44
+ decorators: tuple[dict, ...]
45
+ calls: tuple[dict, ...]
46
+
47
+
48
+ def load_entrypoint_signatures(path: str | Path = ENTRYPOINTS_FILE) -> _Signatures:
49
+ with open(path, encoding="utf-8") as f:
50
+ data = yaml.safe_load(f) or {}
51
+ return _Signatures(decorators=tuple(data.get("decorators", [])), calls=tuple(data.get("calls", [])))
52
+
53
+
54
+ def build_repo_model_from_dir(root: str | Path, *, signatures: _Signatures | None = None) -> RepoModel:
55
+ """Build a RepoModel by reading a directory. Reuses RepoSource's safe walk."""
56
+ from codejury.sources.repo import RepoSource
57
+
58
+ files = RepoSource(root, extensions=(".py",)).read_files()
59
+ return build_repo_model(root, files, signatures=signatures)
60
+
61
+
62
+ def build_repo_model(root: str | Path, files: dict[str, str], *, signatures: _Signatures | None = None) -> RepoModel:
63
+ """Build a RepoModel from {path: content}. Files that do not parse are skipped."""
64
+ sigs = signatures or load_entrypoint_signatures()
65
+ entrypoints: list[Entrypoint] = []
66
+ for path, content in sorted(files.items()):
67
+ try:
68
+ tree = ast.parse(content)
69
+ except SyntaxError:
70
+ continue
71
+ entrypoints.extend(_entrypoints_in(path, tree, sigs))
72
+ return RepoModel(root=str(root), files=tuple(sorted(files)), entrypoints=tuple(entrypoints))
73
+
74
+
75
+ def _entrypoints_in(path: str, tree: ast.Module, sigs: _Signatures) -> list[Entrypoint]:
76
+ out: list[Entrypoint] = []
77
+ for node in ast.walk(tree):
78
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
79
+ for dec in node.decorator_list:
80
+ ep = _decorator_entrypoint(path, node, dec, sigs.decorators)
81
+ if ep is not None:
82
+ out.append(ep)
83
+ elif isinstance(node, ast.Call):
84
+ ep = _call_entrypoint(path, node, sigs.calls)
85
+ if ep is not None:
86
+ out.append(ep)
87
+ return out
88
+
89
+
90
+ def _decorator_name(dec: ast.AST) -> str | None:
91
+ func = dec.func if isinstance(dec, ast.Call) else dec
92
+ if isinstance(func, ast.Attribute):
93
+ return func.attr
94
+ if isinstance(func, ast.Name):
95
+ return func.id
96
+ return None
97
+
98
+
99
+ def _decorator_entrypoint(path, func, dec, deco_sigs) -> Entrypoint | None:
100
+ name = _decorator_name(dec)
101
+ if name is None:
102
+ return None
103
+ sig = next((s for s in deco_sigs if name in s["names"]), None)
104
+ if sig is None:
105
+ return None
106
+ call = dec if isinstance(dec, ast.Call) else None
107
+ return Entrypoint(
108
+ file=path,
109
+ line=func.lineno,
110
+ function=func.name,
111
+ kind=sig["kind"],
112
+ framework=sig["framework"],
113
+ route=_first_str_arg(call) if call else "",
114
+ method=_method(name, call, sig.get("method", "")),
115
+ )
116
+
117
+
118
+ def _call_entrypoint(path, call, call_sigs) -> Entrypoint | None:
119
+ name = call.func.attr if isinstance(call.func, ast.Attribute) else (
120
+ call.func.id if isinstance(call.func, ast.Name) else None
121
+ )
122
+ sig = next((s for s in call_sigs if name in s["names"]), None)
123
+ if sig is None:
124
+ return None
125
+ return Entrypoint(
126
+ file=path,
127
+ line=call.lineno,
128
+ function=_view_name(call),
129
+ kind=sig["kind"],
130
+ framework=sig["framework"],
131
+ route=_first_str_arg(call),
132
+ )
133
+
134
+
135
+ def _first_str_arg(call: ast.Call) -> str:
136
+ if call.args and isinstance(call.args[0], ast.Constant) and isinstance(call.args[0].value, str):
137
+ return call.args[0].value
138
+ return ""
139
+
140
+
141
+ def _method(decorator_name: str, call: ast.Call | None, rule: str) -> str:
142
+ if rule == "name":
143
+ return decorator_name.upper()
144
+ if rule == "kwarg:methods" and call is not None:
145
+ for kw in call.keywords:
146
+ if kw.arg == "methods" and isinstance(kw.value, (ast.List, ast.Tuple)):
147
+ methods = [e.value for e in kw.value.elts if isinstance(e, ast.Constant) and isinstance(e.value, str)]
148
+ if methods:
149
+ return ",".join(methods)
150
+ return "GET" # the framework default when methods is omitted
151
+ return ""
152
+
153
+
154
+ def _view_name(call: ast.Call) -> str:
155
+ # Django path("route", view): the view is the second positional argument
156
+ if len(call.args) >= 2:
157
+ view = call.args[1]
158
+ if isinstance(view, ast.Name):
159
+ return view.id
160
+ if isinstance(view, ast.Attribute):
161
+ return view.attr
162
+ return ""
@@ -11,26 +11,27 @@ import os
11
11
  from codejury.agents.base import Agent
12
12
  from codejury.agents.debate import ChallengerAgent, FinderAgent, JudgeAgent
13
13
  from codejury.agents.refuter import RefuterAgent
14
- from codejury.agents.verifier import VerifierAgent
14
+ from codejury.agents.skill_runner import SkillRunner
15
+ from codejury.analysis.attack_path import attach_suspected_paths
16
+ from codejury.analysis.taint import load_vocab
15
17
  from codejury.domain.artifact import CodeArtifact
16
- from codejury.domain.capability import Capability
17
18
  from codejury.domain.context import AnalysisContext
18
19
  from codejury.domain.result import AnalysisResult
19
20
  from codejury.infrastructure.cache import VerdictCache, verdict_key
21
+ from codejury.selection import Selector, SkillRouter
20
22
  from codejury.orchestrators.base import Orchestrator
21
23
  from codejury.orchestrators.adaptive import AdaptiveOrchestrator
22
24
  from codejury.orchestrators.challenge import ChallengeOrchestrator
23
25
  from codejury.orchestrators.debate import DebateOrchestrator
24
- from codejury.orchestrators.pipeline import PipelineOrchestrator
25
26
  from codejury.orchestrators.reflexion import ReflexionOrchestrator
26
27
  from codejury.orchestrators.single import SingleOrchestrator
28
+ from codejury.orchestrators.skill_pipeline import SkillPipelineOrchestrator
27
29
  from codejury.orchestrators.taint_gate import TaintGateOrchestrator
28
30
  from codejury.providers.anthropic import AnthropicProvider
29
31
  from codejury.providers.base import Provider
30
32
  from codejury.providers.litellm import LiteLLMProvider
31
33
  from codejury.providers.openai import OpenAIProvider
32
34
  from codejury.providers.retry import RetryProvider
33
- from codejury.sources.base import Source
34
35
 
35
36
  STRATEGIES = ("single", "pipeline", "debate", "reflexion", "challenge", "taint", "adaptive")
36
37
  PROVIDERS = ("anthropic", "openai", "litellm")
@@ -53,9 +54,18 @@ def make_provider(
53
54
  return provider
54
55
 
55
56
 
56
- def build_orchestration(
57
+ def build_skill_orchestration(
57
58
  strategy: str, *, provider: Provider, model: str, max_tokens: int
58
59
  ) -> tuple[dict[str, Agent], Orchestrator]:
60
+ """Skill-based orchestration. SkillRunner takes the verifier role; the debate
61
+ agents are skill-aware (FinderAgent reads the skill playbooks), so every
62
+ strategy build_orchestration offers is available here too.
63
+
64
+ The taint and challenge gates key on ``capability.split('.')[0]``, which a
65
+ skill verdict (``input_validation.<dimension>``) still satisfies."""
66
+ def runner() -> SkillRunner:
67
+ return SkillRunner(provider=provider, model=model, max_tokens=max_tokens)
68
+
59
69
  if strategy == "debate":
60
70
  roles = (FinderAgent, ChallengerAgent, JudgeAgent)
61
71
  agents = {cls.role: cls(provider=provider, model=model, max_tokens=max_tokens) for cls in roles}
@@ -63,7 +73,7 @@ def build_orchestration(
63
73
  if strategy == "adaptive":
64
74
  roles = (FinderAgent, ChallengerAgent, JudgeAgent)
65
75
  agents = {cls.role: cls(provider=provider, model=model, max_tokens=max_tokens) for cls in roles}
66
- agents["verifier"] = VerifierAgent(provider=provider, model=model, max_tokens=max_tokens)
76
+ agents["verifier"] = runner()
67
77
  return agents, AdaptiveOrchestrator()
68
78
  if strategy == "reflexion":
69
79
  agents = {
@@ -72,17 +82,12 @@ def build_orchestration(
72
82
  }
73
83
  return agents, ReflexionOrchestrator()
74
84
  if strategy == "challenge":
75
- agents = {
76
- "verifier": VerifierAgent(provider=provider, model=model, max_tokens=max_tokens),
77
- "refuter": RefuterAgent(provider=provider, model=model),
78
- }
79
- return agents, ChallengeOrchestrator()
80
- verifier = {"verifier": VerifierAgent(provider=provider, model=model, max_tokens=max_tokens)}
85
+ return {"verifier": runner(), "refuter": RefuterAgent(provider=provider, model=model)}, ChallengeOrchestrator()
81
86
  if strategy == "pipeline":
82
- return verifier, PipelineOrchestrator()
87
+ return {"verifier": runner()}, SkillPipelineOrchestrator()
83
88
  if strategy == "taint":
84
- return verifier, TaintGateOrchestrator()
85
- return verifier, SingleOrchestrator()
89
+ return {"verifier": runner()}, TaintGateOrchestrator()
90
+ return {"verifier": runner()}, SingleOrchestrator()
86
91
 
87
92
 
88
93
  def provider_tag(provider: Provider) -> str:
@@ -96,46 +101,44 @@ def orchestration_descriptor(provider: Provider, strategy: str, model: str, max_
96
101
  return f"{provider_tag(provider)}|{strategy}|{model}|{max_tokens}"
97
102
 
98
103
 
99
- def run_over_artifacts(
104
+ def run_over_artifacts_with_skills(
100
105
  artifacts: list[CodeArtifact],
101
- capabilities: list[Capability],
106
+ selector: Selector,
102
107
  agents: dict[str, Agent],
103
108
  orchestrator: Orchestrator,
104
109
  *,
110
+ router: SkillRouter | None = None,
105
111
  cache: VerdictCache | None = None,
106
112
  orchestration: str = "",
113
+ verify: bool = False,
107
114
  ) -> list[tuple[str, AnalysisResult]]:
108
- """Run the orchestration over each artifact, returning (path, result) per artifact.
115
+ """Run the orchestration over each artifact on its selected skills.
116
+
117
+ The selector picks, per artifact, which skills apply (the deterministic
118
+ applies_to filter, then the optional router). verdict_key duck-types on each
119
+ skill's id and fingerprint, so the determinism cache works unchanged. When
120
+ ``verify`` is set, high-severity findings are PoC-checked in the sandbox and
121
+ upgraded to proven; the verify flag is folded into the cache key."""
122
+ from codejury.sandbox import load_poc_templates, verify_result
109
123
 
110
- When ``cache`` is given, an unchanged artifact returns its recorded result
111
- instead of re-running the orchestrator (determinism, invariant 2).
112
- """
113
124
  results = []
125
+ vocab = load_vocab() # once: the attack-path synthesizer reuses it per artifact
126
+ templates = load_poc_templates() if verify else []
127
+ orchestration = orchestration + ("|verify" if verify else "")
114
128
  for artifact in artifacts:
129
+ skills = selector.select(artifact, router=router)
115
130
  if cache is not None:
116
- key = verdict_key(artifact, capabilities, orchestration=orchestration)
131
+ key = verdict_key(artifact, skills, orchestration=orchestration)
117
132
  hit = cache.get(key)
118
133
  if hit is not None:
119
134
  results.append((artifact.path, hit))
120
135
  continue
121
- ctx = AnalysisContext(artifact=artifact, capabilities=capabilities)
136
+ ctx = AnalysisContext(artifact=artifact, skills=skills)
122
137
  result = orchestrator.run(agents, ctx)
138
+ result = attach_suspected_paths(result, artifact, vocab=vocab)
139
+ if verify:
140
+ result = verify_result(result, artifact.content, templates)
123
141
  if cache is not None:
124
142
  cache.put(key, result)
125
143
  results.append((artifact.path, result))
126
144
  return results
127
-
128
-
129
- def run_over_source(
130
- source: Source,
131
- capabilities: list[Capability],
132
- agents: dict[str, Agent],
133
- orchestrator: Orchestrator,
134
- *,
135
- cache: VerdictCache | None = None,
136
- orchestration: str = "",
137
- ) -> list[tuple[str, AnalysisResult]]:
138
- return run_over_artifacts(
139
- source.list_artifacts(), capabilities, agents, orchestrator,
140
- cache=cache, orchestration=orchestration,
141
- )