codejury 0.10.4__tar.gz → 0.10.5__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 (196) hide show
  1. {codejury-0.10.4 → codejury-0.10.5}/PKG-INFO +1 -1
  2. codejury-0.10.5/codejury/analysis/__init__.py +8 -0
  3. {codejury-0.10.4 → codejury-0.10.5}/codejury/analysis/taint.py +27 -10
  4. {codejury-0.10.4 → codejury-0.10.5}/codejury/cli.py +5 -1
  5. codejury-0.10.5/codejury/infrastructure/__init__.py +4 -0
  6. {codejury-0.10.4 → codejury-0.10.5}/codejury/infrastructure/cache.py +17 -3
  7. codejury-0.10.5/codejury/orchestrators/__init__.py +6 -0
  8. {codejury-0.10.4 → codejury-0.10.5}/codejury/orchestrators/challenge.py +9 -2
  9. {codejury-0.10.4 → codejury-0.10.5}/codejury/reporting.py +1 -2
  10. {codejury-0.10.4 → codejury-0.10.5}/codejury/tasks/base.py +11 -2
  11. {codejury-0.10.4 → codejury-0.10.5}/codejury.egg-info/PKG-INFO +1 -1
  12. {codejury-0.10.4 → codejury-0.10.5}/codejury.egg-info/SOURCES.txt +1 -0
  13. {codejury-0.10.4 → codejury-0.10.5}/pyproject.toml +1 -1
  14. codejury-0.10.5/tests/test_openai_format.py +30 -0
  15. {codejury-0.10.4 → codejury-0.10.5}/tests/test_review_fixes.py +26 -0
  16. {codejury-0.10.4 → codejury-0.10.5}/tests/test_taint.py +12 -0
  17. codejury-0.10.4/codejury/analysis/__init__.py +0 -7
  18. codejury-0.10.4/codejury/infrastructure/__init__.py +0 -4
  19. codejury-0.10.4/codejury/orchestrators/__init__.py +0 -6
  20. {codejury-0.10.4 → codejury-0.10.5}/LICENSE +0 -0
  21. {codejury-0.10.4 → codejury-0.10.5}/README.md +0 -0
  22. {codejury-0.10.4 → codejury-0.10.5}/codejury/__init__.py +0 -0
  23. {codejury-0.10.4 → codejury-0.10.5}/codejury/agents/__init__.py +0 -0
  24. {codejury-0.10.4 → codejury-0.10.5}/codejury/agents/base.py +0 -0
  25. {codejury-0.10.4 → codejury-0.10.5}/codejury/agents/debate.py +0 -0
  26. {codejury-0.10.4 → codejury-0.10.5}/codejury/agents/mock.py +0 -0
  27. {codejury-0.10.4 → codejury-0.10.5}/codejury/agents/parsing.py +0 -0
  28. {codejury-0.10.4 → codejury-0.10.5}/codejury/agents/refuter.py +0 -0
  29. {codejury-0.10.4 → codejury-0.10.5}/codejury/agents/verifier.py +0 -0
  30. {codejury-0.10.4 → codejury-0.10.5}/codejury/analysis/provenance.py +0 -0
  31. {codejury-0.10.4 → codejury-0.10.5}/codejury/assembly.py +0 -0
  32. {codejury-0.10.4 → codejury-0.10.5}/codejury/baseline.py +0 -0
  33. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/authentication.yaml +0 -0
  34. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/authorization.yaml +0 -0
  35. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/business_logic.yaml +0 -0
  36. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/crypto.yaml +0 -0
  37. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/data_protection.yaml +0 -0
  38. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/dependency_config.yaml +0 -0
  39. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/error_logging.yaml +0 -0
  40. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/excessive_agency.yaml +0 -0
  41. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/input_validation.yaml +0 -0
  42. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/insecure_output_handling.yaml +0 -0
  43. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/model_supply_chain.yaml +0 -0
  44. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/output_encoding.yaml +0 -0
  45. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/prompt_injection.yaml +0 -0
  46. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/secrets.yaml +0 -0
  47. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/capabilities/session.yaml +0 -0
  48. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ag_allowlist_safe.yaml +0 -0
  49. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ag_arbitrary_tool_vuln.yaml +0 -0
  50. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ag_destructive_no_confirm_vuln.yaml +0 -0
  51. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ag_fixed_enum_safe.yaml +0 -0
  52. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ag_human_approval_safe.yaml +0 -0
  53. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ag_model_confirmed_vuln.yaml +0 -0
  54. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/authn_bcrypt_password.yaml +0 -0
  55. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/authn_jwt_noverify_vuln.yaml +0 -0
  56. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/authn_jwt_verified_safe.yaml +0 -0
  57. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/authn_sha256_checksum_safe.yaml +0 -0
  58. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/authn_sha256_password.yaml +0 -0
  59. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/authn_weak_hash_indirect_vuln.yaml +0 -0
  60. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/authz_idor_vuln.yaml +0 -0
  61. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/authz_owner_safe.yaml +0 -0
  62. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/business_logic_price_tamper_vuln.yaml +0 -0
  63. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/business_logic_server_checked_safe.yaml +0 -0
  64. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/cmdi_fixed_argv_safe.yaml +0 -0
  65. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/cmdi_ossystem_vuln.yaml +0 -0
  66. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/cmdi_subprocess_safe.yaml +0 -0
  67. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/crypto_aesgcm_safe.yaml +0 -0
  68. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/crypto_ecb_vuln.yaml +0 -0
  69. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/data_protection_plaintext_pii_vuln.yaml +0 -0
  70. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/data_protection_tokenized_safe.yaml +0 -0
  71. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/dependency_config_tls_verify_off_vuln.yaml +0 -0
  72. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/dependency_config_tls_verify_on_safe.yaml +0 -0
  73. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/deserialize_json_safe.yaml +0 -0
  74. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/deserialize_pickle_vuln.yaml +0 -0
  75. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/error_logging_redacted_safe.yaml +0 -0
  76. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/error_logging_secret_leak_vuln.yaml +0 -0
  77. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ioh_escaped_output_safe.yaml +0 -0
  78. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ioh_exec_output_vuln.yaml +0 -0
  79. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ioh_innerhtml_output_vuln.yaml +0 -0
  80. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ioh_json_response_safe.yaml +0 -0
  81. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ioh_output_to_sql_vuln.yaml +0 -0
  82. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ioh_schema_validated_safe.yaml +0 -0
  83. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/literal_eval_safe.yaml +0 -0
  84. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/path_basename_safe.yaml +0 -0
  85. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/path_contained_safe.yaml +0 -0
  86. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/path_traversal_vuln.yaml +0 -0
  87. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/pi_delimited_data_safe.yaml +0 -0
  88. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/pi_format_role_vuln.yaml +0 -0
  89. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/pi_indirect_rag_vuln.yaml +0 -0
  90. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/pi_system_concat_vuln.yaml +0 -0
  91. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/pi_user_content_concat_safe.yaml +0 -0
  92. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/pi_user_role_safe.yaml +0 -0
  93. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sc_pinned_safe.yaml +0 -0
  94. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sc_safetensors_safe.yaml +0 -0
  95. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sc_torch_load_vuln.yaml +0 -0
  96. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sc_trust_remote_code_vuln.yaml +0 -0
  97. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/secrets_env_safe.yaml +0 -0
  98. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/secrets_hardcoded_vuln.yaml +0 -0
  99. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/session_fixation_vuln.yaml +0 -0
  100. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/session_secure_cookie_safe.yaml +0 -0
  101. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sql_constant_concat_safe.yaml +0 -0
  102. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sqli_format_vuln.yaml +0 -0
  103. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sqli_fstring_query.yaml +0 -0
  104. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sqli_indirect_var_vuln.yaml +0 -0
  105. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/sqli_parameterized_query.yaml +0 -0
  106. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ssrf_allowlist_safe.yaml +0 -0
  107. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ssrf_constant_url_safe.yaml +0 -0
  108. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ssrf_substring_allowlist_bypass_vuln.yaml +0 -0
  109. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/ssrf_user_url_vuln.yaml +0 -0
  110. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/xfile_idor_no_check_vuln.yaml +0 -0
  111. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/xfile_idor_owner_checked_safe.yaml +0 -0
  112. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/xfile_path_sanitized_safe.yaml +0 -0
  113. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/xfile_path_tainted_vuln.yaml +0 -0
  114. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/xss_innerhtml_constant_safe.yaml +0 -0
  115. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/xss_innerhtml_vuln.yaml +0 -0
  116. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/golden/xss_textcontent_safe.yaml +0 -0
  117. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/suppressions.yaml +0 -0
  118. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/taint.yaml +0 -0
  119. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/tasks/audit_diff_debate.yaml +0 -0
  120. {codejury-0.10.4 → codejury-0.10.5}/codejury/data/tasks/quick_scan_single.yaml +0 -0
  121. {codejury-0.10.4 → codejury-0.10.5}/codejury/domain/__init__.py +0 -0
  122. {codejury-0.10.4 → codejury-0.10.5}/codejury/domain/artifact.py +0 -0
  123. {codejury-0.10.4 → codejury-0.10.5}/codejury/domain/capability.py +0 -0
  124. {codejury-0.10.4 → codejury-0.10.5}/codejury/domain/context.py +0 -0
  125. {codejury-0.10.4 → codejury-0.10.5}/codejury/domain/observation.py +0 -0
  126. {codejury-0.10.4 → codejury-0.10.5}/codejury/domain/result.py +0 -0
  127. {codejury-0.10.4 → codejury-0.10.5}/codejury/evaluation.py +0 -0
  128. {codejury-0.10.4 → codejury-0.10.5}/codejury/infrastructure/json_parse.py +0 -0
  129. {codejury-0.10.4 → codejury-0.10.5}/codejury/integrations/__init__.py +0 -0
  130. {codejury-0.10.4 → codejury-0.10.5}/codejury/integrations/github.py +0 -0
  131. {codejury-0.10.4 → codejury-0.10.5}/codejury/orchestrators/adaptive.py +0 -0
  132. {codejury-0.10.4 → codejury-0.10.5}/codejury/orchestrators/base.py +0 -0
  133. {codejury-0.10.4 → codejury-0.10.5}/codejury/orchestrators/debate.py +0 -0
  134. {codejury-0.10.4 → codejury-0.10.5}/codejury/orchestrators/pipeline.py +0 -0
  135. {codejury-0.10.4 → codejury-0.10.5}/codejury/orchestrators/reflexion.py +0 -0
  136. {codejury-0.10.4 → codejury-0.10.5}/codejury/orchestrators/single.py +0 -0
  137. {codejury-0.10.4 → codejury-0.10.5}/codejury/orchestrators/taint_gate.py +0 -0
  138. {codejury-0.10.4 → codejury-0.10.5}/codejury/providers/__init__.py +0 -0
  139. {codejury-0.10.4 → codejury-0.10.5}/codejury/providers/anthropic.py +0 -0
  140. {codejury-0.10.4 → codejury-0.10.5}/codejury/providers/base.py +0 -0
  141. {codejury-0.10.4 → codejury-0.10.5}/codejury/providers/litellm.py +0 -0
  142. {codejury-0.10.4 → codejury-0.10.5}/codejury/providers/mock.py +0 -0
  143. {codejury-0.10.4 → codejury-0.10.5}/codejury/providers/openai.py +0 -0
  144. {codejury-0.10.4 → codejury-0.10.5}/codejury/providers/openai_format.py +0 -0
  145. {codejury-0.10.4 → codejury-0.10.5}/codejury/providers/retry.py +0 -0
  146. {codejury-0.10.4 → codejury-0.10.5}/codejury/resources.py +0 -0
  147. {codejury-0.10.4 → codejury-0.10.5}/codejury/sources/__init__.py +0 -0
  148. {codejury-0.10.4 → codejury-0.10.5}/codejury/sources/base.py +0 -0
  149. {codejury-0.10.4 → codejury-0.10.5}/codejury/sources/callers.py +0 -0
  150. {codejury-0.10.4 → codejury-0.10.5}/codejury/sources/chunker.py +0 -0
  151. {codejury-0.10.4 → codejury-0.10.5}/codejury/sources/diff.py +0 -0
  152. {codejury-0.10.4 → codejury-0.10.5}/codejury/sources/function.py +0 -0
  153. {codejury-0.10.4 → codejury-0.10.5}/codejury/sources/mock.py +0 -0
  154. {codejury-0.10.4 → codejury-0.10.5}/codejury/sources/repo.py +0 -0
  155. {codejury-0.10.4 → codejury-0.10.5}/codejury/suppression.py +0 -0
  156. {codejury-0.10.4 → codejury-0.10.5}/codejury/tasks/__init__.py +0 -0
  157. {codejury-0.10.4 → codejury-0.10.5}/codejury/tasks/registry.py +0 -0
  158. {codejury-0.10.4 → codejury-0.10.5}/codejury.egg-info/dependency_links.txt +0 -0
  159. {codejury-0.10.4 → codejury-0.10.5}/codejury.egg-info/entry_points.txt +0 -0
  160. {codejury-0.10.4 → codejury-0.10.5}/codejury.egg-info/requires.txt +0 -0
  161. {codejury-0.10.4 → codejury-0.10.5}/codejury.egg-info/top_level.txt +0 -0
  162. {codejury-0.10.4 → codejury-0.10.5}/setup.cfg +0 -0
  163. {codejury-0.10.4 → codejury-0.10.5}/tests/test_adaptive.py +0 -0
  164. {codejury-0.10.4 → codejury-0.10.5}/tests/test_anthropic_provider.py +0 -0
  165. {codejury-0.10.4 → codejury-0.10.5}/tests/test_assembly.py +0 -0
  166. {codejury-0.10.4 → codejury-0.10.5}/tests/test_audit_pipeline.py +0 -0
  167. {codejury-0.10.4 → codejury-0.10.5}/tests/test_baseline.py +0 -0
  168. {codejury-0.10.4 → codejury-0.10.5}/tests/test_cache.py +0 -0
  169. {codejury-0.10.4 → codejury-0.10.5}/tests/test_callers.py +0 -0
  170. {codejury-0.10.4 → codejury-0.10.5}/tests/test_capability.py +0 -0
  171. {codejury-0.10.4 → codejury-0.10.5}/tests/test_challenge.py +0 -0
  172. {codejury-0.10.4 → codejury-0.10.5}/tests/test_cli_audit.py +0 -0
  173. {codejury-0.10.4 → codejury-0.10.5}/tests/test_context.py +0 -0
  174. {codejury-0.10.4 → codejury-0.10.5}/tests/test_debate_agents.py +0 -0
  175. {codejury-0.10.4 → codejury-0.10.5}/tests/test_debate_orchestrator.py +0 -0
  176. {codejury-0.10.4 → codejury-0.10.5}/tests/test_diff_source.py +0 -0
  177. {codejury-0.10.4 → codejury-0.10.5}/tests/test_evaluation.py +0 -0
  178. {codejury-0.10.4 → codejury-0.10.5}/tests/test_function_source.py +0 -0
  179. {codejury-0.10.4 → codejury-0.10.5}/tests/test_integrations.py +0 -0
  180. {codejury-0.10.4 → codejury-0.10.5}/tests/test_json_parse.py +0 -0
  181. {codejury-0.10.4 → codejury-0.10.5}/tests/test_litellm_provider.py +0 -0
  182. {codejury-0.10.4 → codejury-0.10.5}/tests/test_openai_provider.py +0 -0
  183. {codejury-0.10.4 → codejury-0.10.5}/tests/test_orchestrator.py +0 -0
  184. {codejury-0.10.4 → codejury-0.10.5}/tests/test_parsing.py +0 -0
  185. {codejury-0.10.4 → codejury-0.10.5}/tests/test_pipeline_orchestrator.py +0 -0
  186. {codejury-0.10.4 → codejury-0.10.5}/tests/test_provenance.py +0 -0
  187. {codejury-0.10.4 → codejury-0.10.5}/tests/test_reflexion_orchestrator.py +0 -0
  188. {codejury-0.10.4 → codejury-0.10.5}/tests/test_repo_source.py +0 -0
  189. {codejury-0.10.4 → codejury-0.10.5}/tests/test_reporting.py +0 -0
  190. {codejury-0.10.4 → codejury-0.10.5}/tests/test_retry_provider.py +0 -0
  191. {codejury-0.10.4 → codejury-0.10.5}/tests/test_sarif.py +0 -0
  192. {codejury-0.10.4 → codejury-0.10.5}/tests/test_suppression.py +0 -0
  193. {codejury-0.10.4 → codejury-0.10.5}/tests/test_taint_crossfile.py +0 -0
  194. {codejury-0.10.4 → codejury-0.10.5}/tests/test_taint_gate.py +0 -0
  195. {codejury-0.10.4 → codejury-0.10.5}/tests/test_tasks.py +0 -0
  196. {codejury-0.10.4 → codejury-0.10.5}/tests/test_verifier.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codejury
3
- Version: 0.10.4
3
+ Version: 0.10.5
4
4
  Summary: General-purpose Application Security AI audit framework: five-layer architecture, capabilities as first-class data
5
5
  Author: AISecLabs
6
6
  License-Expression: MIT
@@ -0,0 +1,8 @@
1
+ """Static analysis for provenance (P1).
2
+
3
+ The code-graph / data-flow engine that gives the verifier provenance: whether a
4
+ value reaching a sink is attacker-controlled, sanitized, or a trusted constant.
5
+ It targets the taint precision floor that single-file LLM review cannot reach,
6
+ see ROADMAP P1. Python AST based, currently intra-procedural plus a one-hop
7
+ cross-file caller resolution, not yet a full code graph.
8
+ """
@@ -130,7 +130,15 @@ def taint_of(
130
130
  if func_path and _access_in(func_path, vocab.trusted):
131
131
  return Taint.TRUSTED
132
132
  if _callee_in(node, vocab.propagators) or _callee_in(node, vocab.safe_sinks):
133
- return _combine([recurse(a) for a in node.args])
133
+ parts = [recurse(a) for a in node.args]
134
+ # a method-form propagator carries the receiver's taint too, e.g.
135
+ # request.args.get("id").strip(). Recurse the receiver when it is a
136
+ # value (Name/Call/Subscript), not a module path like os.path.join.
137
+ if isinstance(node.func, ast.Attribute) and isinstance(
138
+ node.func.value, (ast.Name, ast.Call, ast.Subscript)
139
+ ):
140
+ parts.append(recurse(node.func.value))
141
+ return _combine(parts)
134
142
  return Taint.UNKNOWN # unknown call; a cross-file hop may resolve it later
135
143
  if isinstance(node, (ast.Attribute, ast.Subscript)):
136
144
  path = access_path(node)
@@ -242,17 +250,26 @@ def _call_sites(name: str, files: dict[str, str]) -> list[tuple[ast.AST, ast.Cal
242
250
  tree = _parse(source)
243
251
  if tree is None:
244
252
  continue
245
- funcs = [n for n in ast.walk(tree) if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))]
253
+ scopes = _scope_index(tree)
246
254
  for call in find_calls(tree, name):
247
- sites.append((_enclosing_scope(funcs, call) or tree, call))
255
+ sites.append((scopes.get(id(call), tree), call))
248
256
  return sites
249
257
 
250
258
 
251
- def _enclosing_scope(funcs: list[ast.AST], call: ast.Call) -> ast.AST | None:
252
- containing = [f for f in funcs if any(node is call for node in ast.walk(f))]
253
- if not containing:
254
- return None # module-level call site
255
- return min(containing, key=lambda f: sum(1 for _ in ast.walk(f))) # innermost
259
+ @functools.lru_cache(maxsize=256)
260
+ def _scope_index(tree: ast.Module) -> dict[int, ast.AST]:
261
+ """Map id(node) to its innermost enclosing function (or the module tree) in one
262
+ pass, so a call's scope is an O(1) lookup instead of an O(functions) re-walk."""
263
+ mapping: dict[int, ast.AST] = {}
264
+
265
+ def walk(node: ast.AST, scope: ast.AST) -> None:
266
+ inner = node if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) else scope
267
+ for child in ast.iter_child_nodes(node):
268
+ mapping[id(child)] = inner
269
+ walk(child, inner)
270
+
271
+ walk(tree, tree)
272
+ return mapping
256
273
 
257
274
 
258
275
  def worst_sink_taint(content: str, files: dict[str, str], vocab: TaintVocab) -> Taint | None:
@@ -272,7 +289,7 @@ def worst_sink_taint(content: str, files: dict[str, str], vocab: TaintVocab) ->
272
289
  tree = _parse(content)
273
290
  if tree is None:
274
291
  return None
275
- funcs = [n for n in ast.walk(tree) if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))]
292
+ scopes = _scope_index(tree)
276
293
  taints: list[Taint] = []
277
294
  for call in [n for n in ast.walk(tree) if isinstance(n, ast.Call)]:
278
295
  if is_safe_sink(call, vocab):
@@ -280,7 +297,7 @@ def worst_sink_taint(content: str, files: dict[str, str], vocab: TaintVocab) ->
280
297
  continue
281
298
  if _callee_in(call, vocab.sanitizers) or _callee_in(call, vocab.propagators):
282
299
  continue # not a sink itself; counted via the enclosing sink's argument
283
- scope = _enclosing_scope(funcs, call) or tree
300
+ scope = scopes.get(id(call), tree)
284
301
  for arg in (*call.args, *(kw.value for kw in call.keywords)):
285
302
  taints.append(taint_in_repo(scope, arg, vocab, files))
286
303
  return _combine(taints) if taints else Taint.UNKNOWN
@@ -290,6 +290,7 @@ def main(argv: list[str] | None = None) -> int:
290
290
  run_p.add_argument("--capabilities", default=CAPABILITIES_DIR, help="capability YAML directory")
291
291
  run_p.add_argument("--format", choices=_FORMATS, default="text", dest="fmt")
292
292
  run_p.add_argument("--no-suppress", action="store_true", help="disable the known-noise suppression filter")
293
+ run_p.add_argument("--no-cache", action="store_true", help="bypass the verdict cache (always re-query the model)")
293
294
  run_p.add_argument("--fail-on", choices=_FAIL_ON, default=None, dest="fail_on", help="exit 1 if a finding at/above this severity is found")
294
295
 
295
296
  eval_p = sub.add_parser("eval", help="score golden cases and report precision/recall")
@@ -366,7 +367,10 @@ def _dispatch(args, parser) -> int:
366
367
  print(f"unknown task {args.task!r}; available: {', '.join(sorted(tasks)) or '(none)'}")
367
368
  return 1
368
369
  results = run_task(
369
- tasks[args.task], DiffSource(_read_diff(args.diff)), load_capabilities(args.capabilities)
370
+ tasks[args.task],
371
+ DiffSource(_read_diff(args.diff)),
372
+ load_capabilities(args.capabilities),
373
+ cache=None if args.no_cache else VerdictCache(),
370
374
  )
371
375
  results = _maybe_suppress(results, not args.no_suppress)
372
376
  print(_render_results(args.fmt, results))
@@ -0,0 +1,4 @@
1
+ """codejury.infrastructure: Layer 1 cross-cutting utilities, json parsing and the verdict cache.
2
+
3
+ Lowest layer: depends on nothing else in codejury, used by the layers above.
4
+ """
@@ -22,6 +22,7 @@ from __future__ import annotations
22
22
 
23
23
  import hashlib
24
24
  import json
25
+ import os
25
26
  from pathlib import Path
26
27
 
27
28
  from codejury.domain.artifact import CodeArtifact
@@ -30,6 +31,10 @@ from codejury.domain.result import AnalysisResult
30
31
 
31
32
  DEFAULT_CACHE_DIR = Path.home() / ".cache" / "codejury" / "verdicts"
32
33
 
34
+ # Bump when the cached payload shape changes, so entries written by an older
35
+ # codejury are not read back under a changed schema. It is folded into the key.
36
+ _SCHEMA = "1"
37
+
33
38
 
34
39
  def _normalize(code: str) -> str:
35
40
  lines = code.replace("\r\n", "\n").replace("\r", "\n").split("\n")
@@ -40,6 +45,7 @@ def verdict_key(
40
45
  artifact: CodeArtifact, capabilities: list[Capability], *, orchestration: str
41
46
  ) -> str:
42
47
  payload = {
48
+ "schema": _SCHEMA,
43
49
  "kind": artifact.kind,
44
50
  "path": artifact.path,
45
51
  "code": _normalize(artifact.content),
@@ -65,12 +71,20 @@ class VerdictCache:
65
71
  path = self._dir / f"{key}.json"
66
72
  if not path.exists():
67
73
  return None
68
- with open(path, encoding="utf-8") as f:
69
- return AnalysisResult.from_dict(json.load(f))
74
+ try:
75
+ with open(path, encoding="utf-8") as f:
76
+ return AnalysisResult.from_dict(json.load(f))
77
+ except (ValueError, OSError): # corrupt or half-written entry: treat as a miss
78
+ return None
70
79
 
71
80
  def put(self, key: str, result: AnalysisResult) -> None:
72
81
  if result.error: # never cache a partial/failed run
73
82
  return
74
83
  self._dir.mkdir(parents=True, exist_ok=True)
75
- with open(self._dir / f"{key}.json", "w", encoding="utf-8") as f:
84
+ path = self._dir / f"{key}.json"
85
+ # write to a temp file then atomically rename, so a concurrent reader never
86
+ # sees a half-written entry
87
+ tmp = path.with_suffix(f".{os.getpid()}.tmp")
88
+ with open(tmp, "w", encoding="utf-8") as f:
76
89
  json.dump(result.to_dict(), f, ensure_ascii=False)
90
+ os.replace(tmp, path)
@@ -0,0 +1,6 @@
1
+ """codejury.orchestrators: strategies for running agents over a context.
2
+
3
+ single, pipeline, debate, reflexion, challenge, taint, and adaptive. The strategy
4
+ is the orchestration axis; a task picks one. Each takes the same agents and
5
+ context and returns an AnalysisResult, so they are interchangeable.
6
+ """
@@ -38,7 +38,10 @@ class ChallengeOrchestrator(Orchestrator):
38
38
  if missing:
39
39
  return AnalysisResult(error=f"challenge requires agents: {', '.join(missing)}")
40
40
 
41
- verdicts = agents["verifier"].run(context)
41
+ try:
42
+ verdicts = agents["verifier"].run(context)
43
+ except Exception as exc:
44
+ return AnalysisResult(error=f"agent 'verifier' failed: {exc}")
42
45
  flagged = [
43
46
  v
44
47
  for v in verdicts
@@ -49,7 +52,11 @@ class ChallengeOrchestrator(Orchestrator):
49
52
  if not flagged:
50
53
  return AnalysisResult(observations=verdicts)
51
54
 
52
- refutations = agents["refuter"].run(dataclasses.replace(context, history=flagged))
55
+ try:
56
+ refutations = agents["refuter"].run(dataclasses.replace(context, history=flagged))
57
+ except Exception as exc:
58
+ # a refuter failure must not lose the verdicts already produced
59
+ return AnalysisResult(observations=verdicts, error=f"agent 'refuter' failed: {exc}")
53
60
  reasons = {c.target: c.reason for c in refutations if isinstance(c, Concession)}
54
61
  # the refuter targets a capability, not an individual verdict; if a capability
55
62
  # has more than one flagged verdict the refutation is ambiguous, so leave them
@@ -11,14 +11,13 @@ from __future__ import annotations
11
11
  import json
12
12
 
13
13
  from codejury import __version__ as _tool_version
14
- from codejury.domain.observation import Observation, is_problem, observation_from_dict
14
+ from codejury.domain.observation import _PROBLEM_STATUSES, Observation, is_problem, observation_from_dict
15
15
  from codejury.domain.result import AnalysisResult
16
16
 
17
17
  Results = list[tuple[str, AnalysisResult]]
18
18
 
19
19
  _SEVERITY_ORDER = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3, "INFO": 4}
20
20
  _CLEARED = ("SECURE", "NOT_PRESENT")
21
- _PROBLEM_STATUSES = ("VULNERABLE", "PARTIAL")
22
21
 
23
22
  # Finding severity -> SARIF result level; VULNERABLE verdict is error, PARTIAL warning.
24
23
  _SARIF_LEVEL = {"CRITICAL": "error", "HIGH": "error", "MEDIUM": "warning", "LOW": "note", "INFO": "note"}
@@ -15,10 +15,12 @@ from codejury.assembly import (
15
15
  DEFAULT_MODEL,
16
16
  build_orchestration,
17
17
  make_provider,
18
+ orchestration_descriptor,
18
19
  run_over_source,
19
20
  )
20
21
  from codejury.domain.capability import Capability
21
22
  from codejury.domain.result import AnalysisResult
23
+ from codejury.infrastructure.cache import VerdictCache
22
24
  from codejury.sources.base import Source
23
25
 
24
26
 
@@ -57,7 +59,7 @@ class Task:
57
59
 
58
60
 
59
61
  def run_task(
60
- task: Task, source: Source, capabilities: list[Capability]
62
+ task: Task, source: Source, capabilities: list[Capability], *, cache: VerdictCache | None = None
61
63
  ) -> list[tuple[str, AnalysisResult]]:
62
64
  # api_base may come from the task as a non-secret URL; the key only from the env.
63
65
  provider = make_provider(
@@ -69,4 +71,11 @@ def run_task(
69
71
  agents, orchestrator = build_orchestration(
70
72
  task.orchestrator, provider=provider, model=task.model, max_tokens=task.max_tokens
71
73
  )
72
- return run_over_source(source, task.select(capabilities), agents, orchestrator)
74
+ return run_over_source(
75
+ source,
76
+ task.select(capabilities),
77
+ agents,
78
+ orchestrator,
79
+ cache=cache,
80
+ orchestration=orchestration_descriptor(provider, task.orchestrator, task.model, task.max_tokens),
81
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codejury
3
- Version: 0.10.4
3
+ Version: 0.10.5
4
4
  Summary: General-purpose Application Security AI audit framework: five-layer architecture, capabilities as first-class data
5
5
  Author: AISecLabs
6
6
  License-Expression: MIT
@@ -171,6 +171,7 @@ tests/test_function_source.py
171
171
  tests/test_integrations.py
172
172
  tests/test_json_parse.py
173
173
  tests/test_litellm_provider.py
174
+ tests/test_openai_format.py
174
175
  tests/test_openai_provider.py
175
176
  tests/test_orchestrator.py
176
177
  tests/test_parsing.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codejury"
3
- version = "0.10.4"
3
+ version = "0.10.5"
4
4
  description = "General-purpose Application Security AI audit framework: five-layer architecture, capabilities as first-class data"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -0,0 +1,30 @@
1
+ from types import SimpleNamespace
2
+
3
+ from codejury.providers.openai_format import choice_text
4
+
5
+
6
+ def test_extracts_plain_string_content():
7
+ resp = SimpleNamespace(choices=[SimpleNamespace(message=SimpleNamespace(content="hello"))])
8
+ assert choice_text(resp) == "hello"
9
+
10
+
11
+ def test_extracts_content_block_list():
12
+ resp = SimpleNamespace(
13
+ choices=[SimpleNamespace(message={"content": [{"text": "a"}, {"text": "b"}, "c"]})]
14
+ )
15
+ assert choice_text(resp) == "abc"
16
+
17
+
18
+ def test_no_choices_returns_empty():
19
+ assert choice_text(SimpleNamespace(choices=[])) == ""
20
+ assert choice_text(SimpleNamespace(choices=None)) == ""
21
+
22
+
23
+ def test_none_content_returns_empty():
24
+ resp = SimpleNamespace(choices=[SimpleNamespace(message=SimpleNamespace(content=None))])
25
+ assert choice_text(resp) == ""
26
+
27
+
28
+ def test_dict_message_without_content():
29
+ resp = SimpleNamespace(choices=[SimpleNamespace(message={})])
30
+ assert choice_text(resp) == ""
@@ -98,3 +98,29 @@ def test_suppression_rejects_empty_match_any(tmp_path):
98
98
  f.write_text("- {id: noop, path_ext: ['.py']}\n")
99
99
  with pytest.raises(ValueError):
100
100
  load_suppressions(f)
101
+
102
+
103
+ def test_challenge_surfaces_verifier_error_not_traceback():
104
+ # a provider failure in challenge must become AnalysisResult.error, not raise
105
+ from codejury.domain.artifact import CodeArtifact
106
+ from codejury.domain.capability import Capability
107
+ from codejury.domain.context import AnalysisContext
108
+ from codejury.orchestrators.challenge import ChallengeOrchestrator
109
+
110
+ class _Boom:
111
+ def run(self, ctx):
112
+ raise RuntimeError("provider down")
113
+
114
+ ctx = AnalysisContext(
115
+ artifact=CodeArtifact(kind="file", path="a.py", content="x"),
116
+ capabilities=[Capability(id="input_validation", name="iv")],
117
+ )
118
+ result = ChallengeOrchestrator().run({"verifier": _Boom(), "refuter": _Boom()}, ctx)
119
+ assert result.error and "verifier" in result.error
120
+
121
+
122
+ def test_cache_get_tolerates_corrupt_entry(tmp_path):
123
+ from codejury.infrastructure.cache import VerdictCache
124
+ (tmp_path / "k.json").write_text("{ not valid json")
125
+ assert VerdictCache(tmp_path).get("k") is None # treated as a miss, not a crash
126
+
@@ -121,3 +121,15 @@ def test_worst_sink_taint_safe_sink_only_is_sanitized():
121
121
  # a safe parser consuming external data is provably safe
122
122
  code = "def f(request):\n return ast.literal_eval(request.data)\n"
123
123
  assert worst_sink_taint(code, {"m.py": code}, VOCAB) is Taint.SANITIZED
124
+
125
+
126
+ def test_method_form_propagator_keeps_receiver_taint():
127
+ # request.args.get("id").strip() must stay EXTERNAL, not collapse to CONSTANT
128
+ src = "def f(request):\n return open(request.args.get('id').strip())\n"
129
+ assert _taint(src, "f", "open") is Taint.EXTERNAL
130
+
131
+
132
+ def test_module_function_propagator_not_polluted_by_receiver():
133
+ # os.path.join's receiver is a module path, not a value; taint comes from the args
134
+ src = "def f(request):\n return open(os.path.join(BASE, request.args['p']))\n"
135
+ assert _taint(src, "f", "open") is Taint.EXTERNAL # not UNKNOWN from the os.path receiver
@@ -1,7 +0,0 @@
1
- """Static analysis for provenance (P1).
2
-
3
- The code-graph / data-flow engine that gives the verifier provenance: whether a
4
- value reaching a sink is attacker-controlled, sanitized, or a trusted constant.
5
- This is the real fix for the taint precision floor that single-file LLM review
6
- cannot reach (see ROADMAP P1). Python / AST based to start.
7
- """
@@ -1,4 +0,0 @@
1
- """codejury.infrastructure: Layer 1 cross-cutting utilities (json parsing, retry, ...).
2
-
3
- Lowest layer: depends on nothing else in codejury, used by the layers above.
4
- """
@@ -1,6 +0,0 @@
1
- """codejury.orchestrators: strategies for running agents over a context.
2
-
3
- single / debate / pipeline / reflexion. The strategy is the "any orchestration"
4
- axis; a task picks one. Each takes the same agents and context and returns an
5
- AnalysisResult, so they are interchangeable.
6
- """
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes