codejury 0.10.4__tar.gz → 0.10.6__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.4 → codejury-0.10.6}/PKG-INFO +1 -1
- codejury-0.10.6/codejury/analysis/__init__.py +8 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/analysis/taint.py +27 -10
- {codejury-0.10.4 → codejury-0.10.6}/codejury/cli.py +5 -1
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ag_arbitrary_tool_vuln.yaml +1 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/authn_sha256_password.yaml +1 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/authz_idor_vuln.yaml +1 -0
- codejury-0.10.6/codejury/data/golden/business_logic_atomic_safe.yaml +15 -0
- codejury-0.10.6/codejury/data/golden/business_logic_toctou_vuln.yaml +14 -0
- codejury-0.10.6/codejury/data/golden/crypto_aesgcm_nonce_safe.yaml +13 -0
- codejury-0.10.6/codejury/data/golden/crypto_static_iv_vuln.yaml +14 -0
- codejury-0.10.6/codejury/data/golden/data_protection_cleartext_http_vuln.yaml +11 -0
- codejury-0.10.6/codejury/data/golden/data_protection_encrypted_at_rest_safe.yaml +11 -0
- codejury-0.10.6/codejury/data/golden/dependency_config_debug_off_safe.yaml +12 -0
- codejury-0.10.6/codejury/data/golden/dependency_config_debug_true_vuln.yaml +11 -0
- codejury-0.10.6/codejury/data/golden/error_logging_reraise_safe.yaml +15 -0
- codejury-0.10.6/codejury/data/golden/error_logging_swallow_vuln.yaml +14 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ioh_exec_output_vuln.yaml +1 -0
- codejury-0.10.6/codejury/data/golden/output_encoding_header_injection_vuln.yaml +11 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/pi_system_concat_vuln.yaml +1 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sc_trust_remote_code_vuln.yaml +1 -0
- codejury-0.10.6/codejury/data/golden/secrets_from_config_safe.yaml +12 -0
- codejury-0.10.6/codejury/data/golden/secrets_hardcoded_token_vuln.yaml +13 -0
- codejury-0.10.6/codejury/data/golden/session_cookie_flags_safe.yaml +10 -0
- codejury-0.10.6/codejury/data/golden/session_insecure_cookie_vuln.yaml +11 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/xss_innerhtml_vuln.yaml +1 -0
- codejury-0.10.6/codejury/infrastructure/__init__.py +4 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/infrastructure/cache.py +17 -3
- codejury-0.10.6/codejury/orchestrators/__init__.py +6 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/orchestrators/challenge.py +9 -2
- {codejury-0.10.4 → codejury-0.10.6}/codejury/reporting.py +1 -2
- {codejury-0.10.4 → codejury-0.10.6}/codejury/tasks/base.py +11 -2
- {codejury-0.10.4 → codejury-0.10.6}/codejury.egg-info/PKG-INFO +1 -1
- {codejury-0.10.4 → codejury-0.10.6}/codejury.egg-info/SOURCES.txt +16 -0
- {codejury-0.10.4 → codejury-0.10.6}/pyproject.toml +1 -1
- codejury-0.10.6/tests/test_openai_format.py +30 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_review_fixes.py +26 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_taint.py +12 -0
- codejury-0.10.4/codejury/analysis/__init__.py +0 -7
- codejury-0.10.4/codejury/infrastructure/__init__.py +0 -4
- codejury-0.10.4/codejury/orchestrators/__init__.py +0 -6
- {codejury-0.10.4 → codejury-0.10.6}/LICENSE +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/README.md +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/__init__.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/agents/__init__.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/agents/base.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/agents/debate.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/agents/mock.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/agents/parsing.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/agents/refuter.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/agents/verifier.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/analysis/provenance.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/assembly.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/baseline.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/authentication.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/authorization.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/business_logic.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/crypto.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/data_protection.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/dependency_config.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/error_logging.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/excessive_agency.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/input_validation.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/insecure_output_handling.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/model_supply_chain.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/output_encoding.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/prompt_injection.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/secrets.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/capabilities/session.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ag_allowlist_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ag_destructive_no_confirm_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ag_fixed_enum_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ag_human_approval_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ag_model_confirmed_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/authn_bcrypt_password.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/authn_jwt_noverify_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/authn_jwt_verified_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/authn_sha256_checksum_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/authn_weak_hash_indirect_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/authz_owner_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/business_logic_price_tamper_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/business_logic_server_checked_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/cmdi_fixed_argv_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/cmdi_ossystem_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/cmdi_subprocess_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/crypto_aesgcm_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/crypto_ecb_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/data_protection_plaintext_pii_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/data_protection_tokenized_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/dependency_config_tls_verify_off_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/dependency_config_tls_verify_on_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/deserialize_json_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/deserialize_pickle_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/error_logging_redacted_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/error_logging_secret_leak_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ioh_escaped_output_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ioh_innerhtml_output_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ioh_json_response_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ioh_output_to_sql_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ioh_schema_validated_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/literal_eval_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/path_basename_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/path_contained_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/path_traversal_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/pi_delimited_data_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/pi_format_role_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/pi_indirect_rag_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/pi_user_content_concat_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/pi_user_role_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sc_pinned_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sc_safetensors_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sc_torch_load_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/secrets_env_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/secrets_hardcoded_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/session_fixation_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/session_secure_cookie_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sql_constant_concat_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sqli_format_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sqli_fstring_query.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sqli_indirect_var_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/sqli_parameterized_query.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ssrf_allowlist_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ssrf_constant_url_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ssrf_substring_allowlist_bypass_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/ssrf_user_url_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/xfile_idor_no_check_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/xfile_idor_owner_checked_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/xfile_path_sanitized_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/xfile_path_tainted_vuln.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/xss_innerhtml_constant_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/golden/xss_textcontent_safe.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/suppressions.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/taint.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/tasks/audit_diff_debate.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/data/tasks/quick_scan_single.yaml +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/domain/__init__.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/domain/artifact.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/domain/capability.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/domain/context.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/domain/observation.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/domain/result.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/evaluation.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/infrastructure/json_parse.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/integrations/__init__.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/integrations/github.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/orchestrators/adaptive.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/orchestrators/base.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/orchestrators/debate.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/orchestrators/pipeline.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/orchestrators/reflexion.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/orchestrators/single.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/orchestrators/taint_gate.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/providers/__init__.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/providers/anthropic.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/providers/base.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/providers/litellm.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/providers/mock.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/providers/openai.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/providers/openai_format.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/providers/retry.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/resources.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/sources/__init__.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/sources/base.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/sources/callers.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/sources/chunker.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/sources/diff.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/sources/function.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/sources/mock.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/sources/repo.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/suppression.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/tasks/__init__.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury/tasks/registry.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/setup.cfg +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_adaptive.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_assembly.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_audit_pipeline.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_baseline.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_cache.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_callers.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_capability.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_challenge.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_cli_audit.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_context.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_debate_agents.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_debate_orchestrator.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_diff_source.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_evaluation.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_function_source.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_integrations.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_json_parse.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_litellm_provider.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_openai_provider.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_orchestrator.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_parsing.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_pipeline_orchestrator.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_provenance.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_reflexion_orchestrator.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_repo_source.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_reporting.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_retry_provider.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_sarif.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_suppression.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_taint_crossfile.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_taint_gate.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_tasks.py +0 -0
- {codejury-0.10.4 → codejury-0.10.6}/tests/test_verifier.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
253
|
+
scopes = _scope_index(tree)
|
|
246
254
|
for call in find_calls(tree, name):
|
|
247
|
-
sites.append((
|
|
255
|
+
sites.append((scopes.get(id(call), tree), call))
|
|
248
256
|
return sites
|
|
249
257
|
|
|
250
258
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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 =
|
|
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],
|
|
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,15 @@
|
|
|
1
|
+
capability: business_logic
|
|
2
|
+
vulnerable: false
|
|
3
|
+
expected_verdict: SECURE
|
|
4
|
+
cwe: ""
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
A single atomic conditional update debits only when the balance suffices, so
|
|
8
|
+
there is no check-then-act race.
|
|
9
|
+
code: |
|
|
10
|
+
def withdraw(account, amount):
|
|
11
|
+
result = db.execute(
|
|
12
|
+
"UPDATE accounts SET balance = balance - %s WHERE id = %s AND balance >= %s",
|
|
13
|
+
(amount, account.id, amount),
|
|
14
|
+
)
|
|
15
|
+
return result.rowcount == 1
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
capability: business_logic
|
|
2
|
+
vulnerable: true
|
|
3
|
+
expected_verdict: VULNERABLE
|
|
4
|
+
cwe: CWE-367
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
The balance is checked and then debited without a lock or atomic update, so two
|
|
8
|
+
concurrent withdrawals both pass the check and overdraw the account.
|
|
9
|
+
code: |
|
|
10
|
+
def withdraw(account, amount):
|
|
11
|
+
if account.balance >= amount:
|
|
12
|
+
account.balance -= amount
|
|
13
|
+
return True
|
|
14
|
+
return False
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
capability: crypto
|
|
2
|
+
vulnerable: false
|
|
3
|
+
expected_verdict: SECURE
|
|
4
|
+
cwe: ""
|
|
5
|
+
source: synthetic
|
|
6
|
+
split: held-out
|
|
7
|
+
notes: >
|
|
8
|
+
Authenticated encryption (AES-GCM) with a fresh random nonce per message. No
|
|
9
|
+
fixed IV, no unauthenticated mode.
|
|
10
|
+
code: |
|
|
11
|
+
def encrypt(data, key):
|
|
12
|
+
nonce = os.urandom(12)
|
|
13
|
+
return nonce + AESGCM(key).encrypt(nonce, data, None)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
capability: crypto
|
|
2
|
+
vulnerable: true
|
|
3
|
+
expected_verdict: VULNERABLE
|
|
4
|
+
cwe: CWE-329
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
A fixed all-zero IV with CBC makes encryption deterministic, so identical
|
|
8
|
+
plaintexts produce identical ciphertexts and patterns leak. The IV must be
|
|
9
|
+
random per message.
|
|
10
|
+
code: |
|
|
11
|
+
def encrypt(data, key):
|
|
12
|
+
iv = b"\x00" * 16
|
|
13
|
+
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
14
|
+
return cipher.encrypt(pad(data))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
capability: data_protection
|
|
2
|
+
vulnerable: true
|
|
3
|
+
expected_verdict: VULNERABLE
|
|
4
|
+
cwe: CWE-319
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
PII is posted over plaintext http, so it can be read by anyone on the network
|
|
8
|
+
path. It must travel over TLS.
|
|
9
|
+
code: |
|
|
10
|
+
def sync(user):
|
|
11
|
+
requests.post("http://crm.internal/contacts", json={"ssn": user.ssn, "email": user.email})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
capability: data_protection
|
|
2
|
+
vulnerable: false
|
|
3
|
+
expected_verdict: SECURE
|
|
4
|
+
cwe: ""
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
The SSN is encrypted before it is stored, so no plaintext PII is persisted.
|
|
8
|
+
code: |
|
|
9
|
+
def save(user):
|
|
10
|
+
ciphertext = AESGCM(KEY).encrypt(os.urandom(12), user.ssn.encode(), None)
|
|
11
|
+
db.store(user.id, ciphertext)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
capability: dependency_config
|
|
2
|
+
vulnerable: false
|
|
3
|
+
expected_verdict: SECURE
|
|
4
|
+
cwe: ""
|
|
5
|
+
source: synthetic
|
|
6
|
+
split: held-out
|
|
7
|
+
notes: >
|
|
8
|
+
An ordinary run with the debugger left at its safe default. A normal app.run
|
|
9
|
+
must not be flagged.
|
|
10
|
+
code: |
|
|
11
|
+
def main():
|
|
12
|
+
app.run(host="127.0.0.1")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
capability: dependency_config
|
|
2
|
+
vulnerable: true
|
|
3
|
+
expected_verdict: VULNERABLE
|
|
4
|
+
cwe: CWE-489
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
Running with the debugger enabled in production exposes an interactive console
|
|
8
|
+
that can execute code on the server.
|
|
9
|
+
code: |
|
|
10
|
+
def main():
|
|
11
|
+
app.run(host="0.0.0.0", debug=True)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
capability: error_logging
|
|
2
|
+
vulnerable: false
|
|
3
|
+
expected_verdict: SECURE
|
|
4
|
+
cwe: ""
|
|
5
|
+
source: synthetic
|
|
6
|
+
split: held-out
|
|
7
|
+
notes: >
|
|
8
|
+
The error is logged and re-raised, so the failure is recorded and not hidden.
|
|
9
|
+
code: |
|
|
10
|
+
def charge(card):
|
|
11
|
+
try:
|
|
12
|
+
gateway.charge(card)
|
|
13
|
+
except PaymentError:
|
|
14
|
+
logger.exception("charge failed")
|
|
15
|
+
raise
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
capability: error_logging
|
|
2
|
+
vulnerable: true
|
|
3
|
+
expected_verdict: VULNERABLE
|
|
4
|
+
cwe: CWE-390
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
The bare except discards every error, including security-relevant failures, so
|
|
8
|
+
a failed charge looks like a success and nothing is logged.
|
|
9
|
+
code: |
|
|
10
|
+
def charge(card):
|
|
11
|
+
try:
|
|
12
|
+
gateway.charge(card)
|
|
13
|
+
except Exception:
|
|
14
|
+
pass
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
capability: output_encoding
|
|
2
|
+
vulnerable: true
|
|
3
|
+
expected_verdict: VULNERABLE
|
|
4
|
+
cwe: CWE-113
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
User input is concatenated into a response header without stripping CR or LF, so
|
|
8
|
+
a crafted value can inject extra headers or split the response.
|
|
9
|
+
code: |
|
|
10
|
+
def download(response, filename):
|
|
11
|
+
response.headers["Content-Disposition"] = "attachment; filename=" + filename
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
capability: secrets
|
|
2
|
+
vulnerable: false
|
|
3
|
+
expected_verdict: SECURE
|
|
4
|
+
cwe: ""
|
|
5
|
+
source: synthetic
|
|
6
|
+
split: held-out
|
|
7
|
+
notes: >
|
|
8
|
+
The token is read from settings at runtime, not a literal. A variable named
|
|
9
|
+
token that holds a config lookup must not be flagged.
|
|
10
|
+
code: |
|
|
11
|
+
def client():
|
|
12
|
+
return Service(token=settings.API_TOKEN)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
capability: secrets
|
|
2
|
+
vulnerable: true
|
|
3
|
+
expected_verdict: VULNERABLE
|
|
4
|
+
cwe: CWE-798
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
A credential literal committed in source is exposed to everyone with repo
|
|
8
|
+
access and lives forever in version history. It belongs in the environment.
|
|
9
|
+
code: |
|
|
10
|
+
API_TOKEN = "9c1185a5c5e9fc54612808977ee8f548b2258d31"
|
|
11
|
+
|
|
12
|
+
def client():
|
|
13
|
+
return Service(token=API_TOKEN)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
capability: session
|
|
2
|
+
vulnerable: false
|
|
3
|
+
expected_verdict: SECURE
|
|
4
|
+
cwe: ""
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
The session cookie sets Secure, HttpOnly, and SameSite.
|
|
8
|
+
code: |
|
|
9
|
+
def set_session(response, sid):
|
|
10
|
+
response.set_cookie("session", sid, secure=True, httponly=True, samesite="Lax")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
capability: session
|
|
2
|
+
vulnerable: true
|
|
3
|
+
expected_verdict: VULNERABLE
|
|
4
|
+
cwe: CWE-614
|
|
5
|
+
source: synthetic
|
|
6
|
+
notes: >
|
|
7
|
+
The session cookie is set without Secure, HttpOnly, or SameSite, so it is
|
|
8
|
+
readable by JavaScript and sent over plaintext.
|
|
9
|
+
code: |
|
|
10
|
+
def set_session(response, sid):
|
|
11
|
+
response.set_cookie("session", sid)
|
|
@@ -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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
+
)
|
|
@@ -56,21 +56,31 @@ codejury/data/golden/authn_sha256_password.yaml
|
|
|
56
56
|
codejury/data/golden/authn_weak_hash_indirect_vuln.yaml
|
|
57
57
|
codejury/data/golden/authz_idor_vuln.yaml
|
|
58
58
|
codejury/data/golden/authz_owner_safe.yaml
|
|
59
|
+
codejury/data/golden/business_logic_atomic_safe.yaml
|
|
59
60
|
codejury/data/golden/business_logic_price_tamper_vuln.yaml
|
|
60
61
|
codejury/data/golden/business_logic_server_checked_safe.yaml
|
|
62
|
+
codejury/data/golden/business_logic_toctou_vuln.yaml
|
|
61
63
|
codejury/data/golden/cmdi_fixed_argv_safe.yaml
|
|
62
64
|
codejury/data/golden/cmdi_ossystem_vuln.yaml
|
|
63
65
|
codejury/data/golden/cmdi_subprocess_safe.yaml
|
|
66
|
+
codejury/data/golden/crypto_aesgcm_nonce_safe.yaml
|
|
64
67
|
codejury/data/golden/crypto_aesgcm_safe.yaml
|
|
65
68
|
codejury/data/golden/crypto_ecb_vuln.yaml
|
|
69
|
+
codejury/data/golden/crypto_static_iv_vuln.yaml
|
|
70
|
+
codejury/data/golden/data_protection_cleartext_http_vuln.yaml
|
|
71
|
+
codejury/data/golden/data_protection_encrypted_at_rest_safe.yaml
|
|
66
72
|
codejury/data/golden/data_protection_plaintext_pii_vuln.yaml
|
|
67
73
|
codejury/data/golden/data_protection_tokenized_safe.yaml
|
|
74
|
+
codejury/data/golden/dependency_config_debug_off_safe.yaml
|
|
75
|
+
codejury/data/golden/dependency_config_debug_true_vuln.yaml
|
|
68
76
|
codejury/data/golden/dependency_config_tls_verify_off_vuln.yaml
|
|
69
77
|
codejury/data/golden/dependency_config_tls_verify_on_safe.yaml
|
|
70
78
|
codejury/data/golden/deserialize_json_safe.yaml
|
|
71
79
|
codejury/data/golden/deserialize_pickle_vuln.yaml
|
|
72
80
|
codejury/data/golden/error_logging_redacted_safe.yaml
|
|
81
|
+
codejury/data/golden/error_logging_reraise_safe.yaml
|
|
73
82
|
codejury/data/golden/error_logging_secret_leak_vuln.yaml
|
|
83
|
+
codejury/data/golden/error_logging_swallow_vuln.yaml
|
|
74
84
|
codejury/data/golden/ioh_escaped_output_safe.yaml
|
|
75
85
|
codejury/data/golden/ioh_exec_output_vuln.yaml
|
|
76
86
|
codejury/data/golden/ioh_innerhtml_output_vuln.yaml
|
|
@@ -78,6 +88,7 @@ codejury/data/golden/ioh_json_response_safe.yaml
|
|
|
78
88
|
codejury/data/golden/ioh_output_to_sql_vuln.yaml
|
|
79
89
|
codejury/data/golden/ioh_schema_validated_safe.yaml
|
|
80
90
|
codejury/data/golden/literal_eval_safe.yaml
|
|
91
|
+
codejury/data/golden/output_encoding_header_injection_vuln.yaml
|
|
81
92
|
codejury/data/golden/path_basename_safe.yaml
|
|
82
93
|
codejury/data/golden/path_contained_safe.yaml
|
|
83
94
|
codejury/data/golden/path_traversal_vuln.yaml
|
|
@@ -92,8 +103,12 @@ codejury/data/golden/sc_safetensors_safe.yaml
|
|
|
92
103
|
codejury/data/golden/sc_torch_load_vuln.yaml
|
|
93
104
|
codejury/data/golden/sc_trust_remote_code_vuln.yaml
|
|
94
105
|
codejury/data/golden/secrets_env_safe.yaml
|
|
106
|
+
codejury/data/golden/secrets_from_config_safe.yaml
|
|
107
|
+
codejury/data/golden/secrets_hardcoded_token_vuln.yaml
|
|
95
108
|
codejury/data/golden/secrets_hardcoded_vuln.yaml
|
|
109
|
+
codejury/data/golden/session_cookie_flags_safe.yaml
|
|
96
110
|
codejury/data/golden/session_fixation_vuln.yaml
|
|
111
|
+
codejury/data/golden/session_insecure_cookie_vuln.yaml
|
|
97
112
|
codejury/data/golden/session_secure_cookie_safe.yaml
|
|
98
113
|
codejury/data/golden/sql_constant_concat_safe.yaml
|
|
99
114
|
codejury/data/golden/sqli_format_vuln.yaml
|
|
@@ -171,6 +186,7 @@ tests/test_function_source.py
|
|
|
171
186
|
tests/test_integrations.py
|
|
172
187
|
tests/test_json_parse.py
|
|
173
188
|
tests/test_litellm_provider.py
|
|
189
|
+
tests/test_openai_format.py
|
|
174
190
|
tests/test_openai_provider.py
|
|
175
191
|
tests/test_orchestrator.py
|
|
176
192
|
tests/test_parsing.py
|
|
@@ -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) == ""
|