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.
- {codejury-0.10.6 → codejury-0.11.0}/PKG-INFO +2 -2
- {codejury-0.10.6 → codejury-0.11.0}/codejury/__init__.py +4 -3
- {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/debate.py +10 -11
- {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/mock.py +4 -4
- codejury-0.11.0/codejury/agents/skill_runner.py +121 -0
- codejury-0.11.0/codejury/analysis/attack_path.py +110 -0
- codejury-0.11.0/codejury/analysis/repo_model.py +162 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/assembly.py +41 -38
- {codejury-0.10.6 → codejury-0.11.0}/codejury/cli.py +176 -42
- codejury-0.11.0/codejury/data/entrypoints.yaml +16 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_allowlist_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_arbitrary_tool_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_destructive_no_confirm_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_fixed_enum_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_human_approval_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ag_model_confirmed_vuln.yaml +1 -1
- codejury-0.11.0/codejury/data/golden/api_design_authz_inconsistent_vuln.yaml +23 -0
- codejury-0.11.0/codejury/data/golden/api_design_authz_uniform_safe.yaml +22 -0
- codejury-0.11.0/codejury/data/golden/api_design_cors_allowlist_safe.yaml +23 -0
- codejury-0.11.0/codejury/data/golden/api_design_cors_reflect_origin_vuln.yaml +20 -0
- codejury-0.11.0/codejury/data/golden/api_design_cors_wildcard_credentials_vuln.yaml +23 -0
- codejury-0.11.0/codejury/data/golden/api_design_mass_assignment_allowlist_safe.yaml +22 -0
- codejury-0.11.0/codejury/data/golden/api_design_mass_assignment_vuln.yaml +21 -0
- codejury-0.11.0/codejury/data/golden/api_design_single_missing_auth_not_ours_safe.yaml +18 -0
- codejury-0.11.0/codejury/data/golden/architecture_clean_surface_safe.yaml +19 -0
- codejury-0.11.0/codejury/data/golden/architecture_debug_route_exposed_vuln.yaml +21 -0
- codejury-0.11.0/codejury/data/golden/architecture_internal_route_exposed_vuln.yaml +18 -0
- codejury-0.11.0/codejury/data/golden/architecture_legit_admin_route_safe.yaml +19 -0
- codejury-0.11.0/codejury/data/golden/architecture_minimal_surface_safe.yaml +18 -0
- codejury-0.11.0/codejury/data/golden/architecture_test_echo_route_vuln.yaml +17 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_bcrypt_password.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_jwt_noverify_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_jwt_verified_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_sha256_checksum_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_sha256_password.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authn_weak_hash_indirect_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authz_idor_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/authz_owner_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/business_logic_atomic_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/business_logic_price_tamper_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/business_logic_server_checked_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/business_logic_toctou_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/cmdi_fixed_argv_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/cmdi_ossystem_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/cmdi_subprocess_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/crypto_aesgcm_nonce_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/crypto_aesgcm_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/crypto_ecb_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/crypto_static_iv_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/data_protection_cleartext_http_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/data_protection_encrypted_at_rest_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/data_protection_plaintext_pii_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/data_protection_tokenized_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/dependency_config_debug_off_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/dependency_config_debug_true_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/dependency_config_tls_verify_off_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/dependency_config_tls_verify_on_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/deserialize_json_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/deserialize_pickle_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/error_logging_redacted_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/error_logging_reraise_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/error_logging_secret_leak_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/error_logging_swallow_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_escaped_output_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_exec_output_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_innerhtml_output_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_json_response_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_output_to_sql_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ioh_schema_validated_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/literal_eval_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/output_encoding_header_injection_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/path_basename_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/path_contained_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/path_traversal_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_delimited_data_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_format_role_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_indirect_rag_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_system_concat_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_user_content_concat_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/pi_user_role_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sc_pinned_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sc_safetensors_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sc_torch_load_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sc_trust_remote_code_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/secrets_env_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/secrets_from_config_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/secrets_hardcoded_token_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/secrets_hardcoded_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/session_cookie_flags_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/session_fixation_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/session_insecure_cookie_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/session_secure_cookie_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sql_constant_concat_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sqli_format_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sqli_fstring_query.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sqli_indirect_var_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/sqli_parameterized_query.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ssrf_allowlist_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ssrf_constant_url_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ssrf_substring_allowlist_bypass_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/ssrf_user_url_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xfile_idor_no_check_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xfile_idor_owner_checked_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xfile_path_sanitized_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xfile_path_tainted_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xss_innerhtml_constant_safe.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xss_innerhtml_vuln.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/golden/xss_textcontent_safe.yaml +1 -1
- codejury-0.11.0/codejury/data/poc/cmdi.yaml +14 -0
- codejury-0.11.0/codejury/data/poc/path.yaml +12 -0
- codejury-0.11.0/codejury/data/poc/sqli.yaml +14 -0
- codejury-0.11.0/codejury/data/poc/ssrf.yaml +15 -0
- codejury-0.11.0/codejury/data/skills/api_design/SKILL.md +116 -0
- codejury-0.11.0/codejury/data/skills/api_design/skill.yaml +8 -0
- codejury-0.11.0/codejury/data/skills/architecture/SKILL.md +52 -0
- codejury-0.11.0/codejury/data/skills/architecture/skill.yaml +8 -0
- codejury-0.11.0/codejury/data/skills/authn/SKILL.md +59 -0
- codejury-0.11.0/codejury/data/skills/authn/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/authz/SKILL.md +46 -0
- codejury-0.11.0/codejury/data/skills/authz/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/business_logic/SKILL.md +50 -0
- codejury-0.11.0/codejury/data/skills/business_logic/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/crypto/SKILL.md +55 -0
- codejury-0.11.0/codejury/data/skills/crypto/skill.yaml +12 -0
- codejury-0.11.0/codejury/data/skills/data_protection/SKILL.md +50 -0
- codejury-0.11.0/codejury/data/skills/data_protection/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/dependency_config/SKILL.md +55 -0
- codejury-0.11.0/codejury/data/skills/dependency_config/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/error_logging/SKILL.md +45 -0
- codejury-0.11.0/codejury/data/skills/error_logging/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/excessive_agency/SKILL.md +58 -0
- codejury-0.11.0/codejury/data/skills/excessive_agency/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/input_validation/SKILL.md +128 -0
- codejury-0.11.0/codejury/data/skills/input_validation/skill.yaml +13 -0
- codejury-0.11.0/codejury/data/skills/insecure_output_handling/SKILL.md +62 -0
- codejury-0.11.0/codejury/data/skills/insecure_output_handling/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/model_supply_chain/SKILL.md +57 -0
- codejury-0.11.0/codejury/data/skills/model_supply_chain/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/output_encoding/SKILL.md +45 -0
- codejury-0.11.0/codejury/data/skills/output_encoding/skill.yaml +11 -0
- codejury-0.11.0/codejury/data/skills/prompt_injection/SKILL.md +62 -0
- codejury-0.11.0/codejury/data/skills/prompt_injection/skill.yaml +10 -0
- codejury-0.11.0/codejury/data/skills/secrets/SKILL.md +48 -0
- codejury-0.11.0/codejury/data/skills/secrets/skill.yaml +10 -0
- codejury-0.11.0/codejury/data/skills/session/SKILL.md +47 -0
- codejury-0.11.0/codejury/data/skills/session/skill.yaml +11 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/tasks/audit_diff_debate.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/tasks/quick_scan_single.yaml +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/artifact.py +1 -1
- {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/context.py +5 -5
- {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/observation.py +25 -0
- codejury-0.11.0/codejury/domain/skill.py +117 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/evaluation.py +15 -15
- {codejury-0.10.6 → codejury-0.11.0}/codejury/infrastructure/cache.py +23 -12
- {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/base.py +2 -2
- codejury-0.11.0/codejury/orchestrators/skill_pipeline.py +32 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/reporting.py +46 -13
- {codejury-0.10.6 → codejury-0.11.0}/codejury/resources.py +4 -2
- codejury-0.11.0/codejury/sandbox.py +363 -0
- codejury-0.11.0/codejury/selection.py +145 -0
- codejury-0.11.0/codejury/sources/api_surface.py +101 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/repo.py +5 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/tasks/base.py +19 -18
- {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/PKG-INFO +2 -2
- {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/SOURCES.txt +70 -23
- {codejury-0.10.6 → codejury-0.11.0}/pyproject.toml +3 -3
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_adaptive.py +7 -5
- codejury-0.11.0/tests/test_api_surface.py +106 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_assembly.py +15 -13
- codejury-0.11.0/tests/test_attack_path.py +141 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_cache.py +23 -20
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_challenge.py +5 -5
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_cli_audit.py +19 -19
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_context.py +2 -2
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_debate_agents.py +4 -4
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_debate_orchestrator.py +2 -2
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_evaluation.py +25 -21
- codejury-0.11.0/tests/test_full_review.py +111 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_orchestrator.py +2 -2
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_reflexion_orchestrator.py +2 -2
- codejury-0.11.0/tests/test_repo_model.py +152 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_review_fixes.py +4 -16
- codejury-0.11.0/tests/test_sandbox.py +195 -0
- codejury-0.11.0/tests/test_selection.py +109 -0
- codejury-0.11.0/tests/test_skill.py +140 -0
- codejury-0.11.0/tests/test_skill_runner.py +113 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_taint_gate.py +12 -11
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_tasks.py +17 -15
- codejury-0.10.6/codejury/agents/verifier.py +0 -143
- codejury-0.10.6/codejury/data/capabilities/authentication.yaml +0 -67
- codejury-0.10.6/codejury/data/capabilities/authorization.yaml +0 -55
- codejury-0.10.6/codejury/data/capabilities/business_logic.yaml +0 -58
- codejury-0.10.6/codejury/data/capabilities/crypto.yaml +0 -78
- codejury-0.10.6/codejury/data/capabilities/data_protection.yaml +0 -57
- codejury-0.10.6/codejury/data/capabilities/dependency_config.yaml +0 -79
- codejury-0.10.6/codejury/data/capabilities/error_logging.yaml +0 -49
- codejury-0.10.6/codejury/data/capabilities/excessive_agency.yaml +0 -64
- codejury-0.10.6/codejury/data/capabilities/input_validation.yaml +0 -171
- codejury-0.10.6/codejury/data/capabilities/insecure_output_handling.yaml +0 -65
- codejury-0.10.6/codejury/data/capabilities/model_supply_chain.yaml +0 -59
- codejury-0.10.6/codejury/data/capabilities/output_encoding.yaml +0 -56
- codejury-0.10.6/codejury/data/capabilities/prompt_injection.yaml +0 -67
- codejury-0.10.6/codejury/data/capabilities/secrets.yaml +0 -72
- codejury-0.10.6/codejury/data/capabilities/session.yaml +0 -60
- codejury-0.10.6/codejury/domain/capability.py +0 -151
- codejury-0.10.6/codejury/orchestrators/pipeline.py +0 -32
- codejury-0.10.6/tests/test_audit_pipeline.py +0 -63
- codejury-0.10.6/tests/test_capability.py +0 -81
- codejury-0.10.6/tests/test_pipeline_orchestrator.py +0 -59
- codejury-0.10.6/tests/test_verifier.py +0 -63
- {codejury-0.10.6 → codejury-0.11.0}/LICENSE +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/README.md +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/base.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/parsing.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/agents/refuter.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/analysis/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/analysis/provenance.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/analysis/taint.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/baseline.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/suppressions.yaml +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/data/taint.yaml +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/domain/result.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/infrastructure/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/infrastructure/json_parse.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/integrations/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/integrations/github.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/adaptive.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/challenge.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/debate.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/reflexion.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/single.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/orchestrators/taint_gate.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/anthropic.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/base.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/litellm.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/mock.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/openai.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/openai_format.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/providers/retry.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/base.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/callers.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/chunker.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/diff.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/function.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/sources/mock.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/suppression.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/tasks/__init__.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury/tasks/registry.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/setup.cfg +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_baseline.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_callers.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_diff_source.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_function_source.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_integrations.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_json_parse.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_litellm_provider.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_openai_format.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_openai_provider.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_parsing.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_provenance.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_repo_source.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_reporting.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_retry_provider.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_sarif.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_suppression.py +0 -0
- {codejury-0.10.6 → codejury-0.11.0}/tests/test_taint.py +0 -0
- {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.
|
|
4
|
-
Summary: General-purpose Application Security AI audit framework:
|
|
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
|
-
|
|
4
|
-
Domain knowledge lives in
|
|
5
|
-
aligned with OWASP
|
|
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
|
|
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
|
|
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(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
5
|
-
|
|
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=
|
|
31
|
+
capability=skill.id,
|
|
32
32
|
produced_by=self._role,
|
|
33
33
|
status="UNKNOWN",
|
|
34
34
|
reasoning=result.text,
|
|
35
35
|
)
|
|
36
|
-
for
|
|
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.
|
|
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
|
|
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"] =
|
|
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
|
-
|
|
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,
|
|
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
|
|
104
|
+
def run_over_artifacts_with_skills(
|
|
100
105
|
artifacts: list[CodeArtifact],
|
|
101
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
)
|