codejury 0.9.1__tar.gz → 0.10.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.9.1 → codejury-0.10.0}/PKG-INFO +7 -2
- {codejury-0.9.1 → codejury-0.10.0}/README.md +6 -1
- {codejury-0.9.1 → codejury-0.10.0}/codejury/analysis/provenance.py +83 -38
- {codejury-0.9.1 → codejury-0.10.0}/codejury/analysis/taint.py +46 -55
- {codejury-0.9.1 → codejury-0.10.0}/codejury/assembly.py +7 -1
- codejury-0.10.0/codejury/orchestrators/adaptive.py +55 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury.egg-info/PKG-INFO +7 -2
- {codejury-0.9.1 → codejury-0.10.0}/codejury.egg-info/SOURCES.txt +2 -0
- {codejury-0.9.1 → codejury-0.10.0}/pyproject.toml +1 -1
- codejury-0.10.0/tests/test_adaptive.py +71 -0
- {codejury-0.9.1 → codejury-0.10.0}/LICENSE +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/agents/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/agents/base.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/agents/debate.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/agents/mock.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/agents/parsing.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/agents/refuter.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/agents/verifier.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/analysis/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/baseline.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/cli.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/authentication.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/authorization.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/business_logic.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/crypto.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/data_protection.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/dependency_config.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/error_logging.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/excessive_agency.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/input_validation.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/insecure_output_handling.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/output_encoding.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/prompt_injection.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/secrets.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/capabilities/session.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ag_allowlist_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ag_arbitrary_tool_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ag_destructive_no_confirm_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ag_fixed_enum_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ag_human_approval_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ag_model_confirmed_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/authn_bcrypt_password.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/authn_jwt_noverify_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/authn_jwt_verified_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/authn_sha256_checksum_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/authn_sha256_password.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/authn_weak_hash_indirect_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/authz_idor_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/authz_owner_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/business_logic_price_tamper_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/business_logic_server_checked_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/cmdi_fixed_argv_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/cmdi_ossystem_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/cmdi_subprocess_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/crypto_aesgcm_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/crypto_ecb_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/data_protection_plaintext_pii_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/data_protection_tokenized_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/dependency_config_tls_verify_off_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/dependency_config_tls_verify_on_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/deserialize_json_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/deserialize_pickle_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/error_logging_redacted_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/error_logging_secret_leak_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ioh_escaped_output_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ioh_exec_output_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ioh_innerhtml_output_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ioh_json_response_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ioh_output_to_sql_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ioh_schema_validated_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/literal_eval_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/path_basename_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/path_contained_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/path_traversal_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/pi_delimited_data_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/pi_format_role_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/pi_indirect_rag_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/pi_system_concat_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/pi_user_content_concat_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/pi_user_role_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/secrets_env_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/secrets_hardcoded_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/session_fixation_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/session_secure_cookie_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/sql_constant_concat_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/sqli_format_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/sqli_fstring_query.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/sqli_indirect_var_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/sqli_parameterized_query.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ssrf_allowlist_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ssrf_constant_url_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ssrf_substring_allowlist_bypass_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/ssrf_user_url_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/xfile_idor_no_check_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/xfile_idor_owner_checked_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/xfile_path_sanitized_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/xfile_path_tainted_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/xss_innerhtml_constant_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/xss_innerhtml_vuln.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/xss_textcontent_safe.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/suppressions.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/taint.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/tasks/audit_diff_debate.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/data/tasks/quick_scan_single.yaml +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/domain/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/domain/artifact.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/domain/capability.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/domain/context.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/domain/observation.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/domain/result.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/evaluation.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/infrastructure/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/infrastructure/cache.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/infrastructure/json_parse.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/integrations/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/integrations/github.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/orchestrators/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/orchestrators/base.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/orchestrators/challenge.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/orchestrators/debate.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/orchestrators/pipeline.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/orchestrators/reflexion.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/orchestrators/single.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/orchestrators/taint_gate.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/providers/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/providers/anthropic.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/providers/base.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/providers/litellm.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/providers/mock.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/providers/openai.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/providers/openai_format.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/providers/retry.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/reporting.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/resources.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/sources/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/sources/base.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/sources/callers.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/sources/chunker.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/sources/diff.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/sources/function.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/sources/mock.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/sources/repo.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/suppression.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/tasks/__init__.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/tasks/base.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury/tasks/registry.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/setup.cfg +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_assembly.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_audit_pipeline.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_baseline.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_cache.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_callers.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_capability.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_challenge.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_cli_audit.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_context.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_debate_agents.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_debate_orchestrator.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_diff_source.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_evaluation.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_function_source.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_integrations.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_json_parse.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_litellm_provider.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_openai_provider.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_orchestrator.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_pipeline_orchestrator.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_provenance.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_reflexion_orchestrator.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_repo_source.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_reporting.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_retry_provider.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_sarif.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_suppression.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_taint.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_taint_crossfile.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_taint_gate.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_tasks.py +0 -0
- {codejury-0.9.1 → codejury-0.10.0}/tests/test_verifier.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: General-purpose Application Security AI audit framework -- five-layer architecture, capabilities as first-class data
|
|
5
5
|
Author: AISecLabs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -83,7 +83,7 @@ git diff | codejury audit --provider anthropic
|
|
|
83
83
|
| `codejury run <task>` | Run a named task preset (see [Tasks](#tasks)). |
|
|
84
84
|
| `codejury eval` | Score the golden cases; report precision / recall / F1, overall and per capability. |
|
|
85
85
|
|
|
86
|
-
Shared flags: `--orchestrator {single,pipeline,debate,reflexion,challenge,taint}`,
|
|
86
|
+
Shared flags: `--orchestrator {single,pipeline,debate,reflexion,challenge,taint,adaptive}`,
|
|
87
87
|
`--provider {anthropic,openai,litellm}`, `--model`,
|
|
88
88
|
`--format {text,markdown,json,sarif}`.
|
|
89
89
|
|
|
@@ -103,6 +103,11 @@ value reaching the sink is constant, sanitized, or trusted (using cross-file
|
|
|
103
103
|
caller/callee context). It downgrades only on positive proof, so it removes false
|
|
104
104
|
positives without dropping real findings.
|
|
105
105
|
|
|
106
|
+
`--orchestrator adaptive` keeps cost down: it runs the single verifier first and
|
|
107
|
+
escalates to a full debate only for artifacts worth it -- any VULNERABLE verdict
|
|
108
|
+
(verify a flag before reporting) or a low-confidence PARTIAL/UNKNOWN one. Clean,
|
|
109
|
+
confident files pay a single call; only the rest pay for debate.
|
|
110
|
+
|
|
106
111
|
`--format sarif` emits a SARIF 2.1.0 log (validates against the official schema)
|
|
107
112
|
for CI and security dashboards: each problem with a code location becomes a
|
|
108
113
|
result carrying its capability (as the rule id), CWE, and a precise location.
|
|
@@ -53,7 +53,7 @@ git diff | codejury audit --provider anthropic
|
|
|
53
53
|
| `codejury run <task>` | Run a named task preset (see [Tasks](#tasks)). |
|
|
54
54
|
| `codejury eval` | Score the golden cases; report precision / recall / F1, overall and per capability. |
|
|
55
55
|
|
|
56
|
-
Shared flags: `--orchestrator {single,pipeline,debate,reflexion,challenge,taint}`,
|
|
56
|
+
Shared flags: `--orchestrator {single,pipeline,debate,reflexion,challenge,taint,adaptive}`,
|
|
57
57
|
`--provider {anthropic,openai,litellm}`, `--model`,
|
|
58
58
|
`--format {text,markdown,json,sarif}`.
|
|
59
59
|
|
|
@@ -73,6 +73,11 @@ value reaching the sink is constant, sanitized, or trusted (using cross-file
|
|
|
73
73
|
caller/callee context). It downgrades only on positive proof, so it removes false
|
|
74
74
|
positives without dropping real findings.
|
|
75
75
|
|
|
76
|
+
`--orchestrator adaptive` keeps cost down: it runs the single verifier first and
|
|
77
|
+
escalates to a full debate only for artifacts worth it -- any VULNERABLE verdict
|
|
78
|
+
(verify a flag before reporting) or a low-confidence PARTIAL/UNKNOWN one. Clean,
|
|
79
|
+
confident files pay a single call; only the rest pay for debate.
|
|
80
|
+
|
|
76
81
|
`--format sarif` emits a SARIF 2.1.0 log (validates against the official schema)
|
|
77
82
|
for CI and security dashboards: each problem with a code location becomes a
|
|
78
83
|
result carrying its capability (as the rule id), CWE, and a precise location.
|
|
@@ -72,7 +72,40 @@ def find_calls(scope: ast.AST, callee: str) -> list[ast.Call]:
|
|
|
72
72
|
|
|
73
73
|
def trace_value(func: ast.FunctionDef | ast.AsyncFunctionDef, expr: ast.AST) -> Origin:
|
|
74
74
|
"""Trace where ``expr`` (an expression inside ``func``) gets its value from."""
|
|
75
|
-
|
|
75
|
+
params = _params(func)
|
|
76
|
+
|
|
77
|
+
def leaf(node: ast.AST, recurse) -> Origin:
|
|
78
|
+
if isinstance(node, ast.Call):
|
|
79
|
+
# the return value's provenance depends on the callee's semantics, which
|
|
80
|
+
# P1-03 decides with the vocabulary; here we just name the callee.
|
|
81
|
+
name = _call_name(node)
|
|
82
|
+
return Origin(calls=frozenset({name})) if name else _UNKNOWN
|
|
83
|
+
if isinstance(node, (ast.Attribute, ast.Subscript)):
|
|
84
|
+
dotted = _dotted(node)
|
|
85
|
+
origin = Origin(attrs=frozenset({dotted})) if dotted else _UNKNOWN
|
|
86
|
+
if _root_name(node) in params: # e.g. request.args[...] where `request` is a parameter
|
|
87
|
+
origin = origin.merge(Origin(params=frozenset({_root_name(node)})))
|
|
88
|
+
return origin
|
|
89
|
+
return _UNKNOWN
|
|
90
|
+
|
|
91
|
+
return reduce_value(
|
|
92
|
+
expr,
|
|
93
|
+
params=params,
|
|
94
|
+
assigns=_assignments(func),
|
|
95
|
+
combine=_merge_origins,
|
|
96
|
+
leaf=leaf,
|
|
97
|
+
on_param=lambda n: Origin(params=frozenset({n})),
|
|
98
|
+
on_global=lambda n: Origin(globals_=frozenset({n})),
|
|
99
|
+
on_constant=lambda: _LITERAL,
|
|
100
|
+
on_cycle=Origin, # assignment cycle -- no new information
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _merge_origins(origins: list[Origin]) -> Origin:
|
|
105
|
+
out = Origin()
|
|
106
|
+
for o in origins:
|
|
107
|
+
out = out.merge(o)
|
|
108
|
+
return out
|
|
76
109
|
|
|
77
110
|
|
|
78
111
|
def parameters(func: ast.FunctionDef | ast.AsyncFunctionDef) -> set[str]:
|
|
@@ -100,51 +133,63 @@ def access_root(node: ast.AST) -> str | None:
|
|
|
100
133
|
return _root_name(node)
|
|
101
134
|
|
|
102
135
|
|
|
103
|
-
def
|
|
136
|
+
def reduce_value(
|
|
137
|
+
expr: ast.AST,
|
|
138
|
+
*,
|
|
139
|
+
params: set[str],
|
|
140
|
+
assigns: dict[str, list[ast.AST]],
|
|
141
|
+
combine,
|
|
142
|
+
leaf,
|
|
143
|
+
on_param,
|
|
144
|
+
on_global,
|
|
145
|
+
on_constant,
|
|
146
|
+
on_cycle,
|
|
147
|
+
seen: frozenset[str] = frozenset(),
|
|
148
|
+
):
|
|
149
|
+
"""Walk a value expression, folding it to a single result of the caller's type.
|
|
150
|
+
|
|
151
|
+
This is the structural recursion shared by provenance (P1-01) and taint
|
|
152
|
+
classification (P1-03): the composite forms that pass a value through (binops,
|
|
153
|
+
f-strings, conditionals, collections) and the local-name dispatch (parameter /
|
|
154
|
+
assignment chain / assignment cycle / free global) are handled here once. The
|
|
155
|
+
caller supplies the policy:
|
|
156
|
+
|
|
157
|
+
- ``combine(list)`` -> fold component results (also defines the empty result);
|
|
158
|
+
- ``on_constant() / on_param(name) / on_global(name) / on_cycle()`` -> leaf
|
|
159
|
+
results for a literal, a parameter, a free name, and an assignment cycle;
|
|
160
|
+
- ``leaf(node, recurse)`` -> everything else (calls, attribute/subscript
|
|
161
|
+
access, unmodelled nodes); ``recurse`` re-enters the walk for sub-expressions
|
|
162
|
+
(e.g. a taint propagator recursing into its arguments).
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def recurse(e: ast.AST, _seen: frozenset[str] | None = None):
|
|
166
|
+
return reduce_value(
|
|
167
|
+
e, params=params, assigns=assigns, combine=combine, leaf=leaf,
|
|
168
|
+
on_param=on_param, on_global=on_global, on_constant=on_constant,
|
|
169
|
+
on_cycle=on_cycle, seen=seen if _seen is None else _seen,
|
|
170
|
+
)
|
|
171
|
+
|
|
104
172
|
if isinstance(expr, ast.Constant):
|
|
105
|
-
return
|
|
173
|
+
return on_constant()
|
|
106
174
|
if isinstance(expr, ast.JoinedStr): # f-string: literal parts + interpolated exprs
|
|
107
|
-
|
|
108
|
-
for value in expr.values:
|
|
109
|
-
if isinstance(value, ast.FormattedValue):
|
|
110
|
-
origin = origin.merge(_classify(value.value, params, assigns, seen))
|
|
111
|
-
return origin
|
|
175
|
+
return combine([on_constant()] + [recurse(v.value) for v in expr.values if isinstance(v, ast.FormattedValue)])
|
|
112
176
|
if isinstance(expr, ast.BinOp):
|
|
113
|
-
return
|
|
114
|
-
if isinstance(expr,
|
|
115
|
-
return
|
|
116
|
-
if isinstance(expr, ast.IfExp): #
|
|
117
|
-
return
|
|
177
|
+
return combine([recurse(expr.left), recurse(expr.right)])
|
|
178
|
+
if isinstance(expr, ast.BoolOp):
|
|
179
|
+
return combine([recurse(v) for v in expr.values])
|
|
180
|
+
if isinstance(expr, ast.IfExp): # one branch or the other; the test does not flow in
|
|
181
|
+
return combine([recurse(expr.body), recurse(expr.orelse)])
|
|
118
182
|
if isinstance(expr, (ast.List, ast.Tuple, ast.Set)):
|
|
119
|
-
return
|
|
120
|
-
if isinstance(expr, ast.Call):
|
|
121
|
-
# the return value's taint depends on the callee's semantics, which P1-03
|
|
122
|
-
# decides with the sanitizer/propagator catalog; here we just name the callee.
|
|
123
|
-
name = _call_name(expr)
|
|
124
|
-
return Origin(calls=frozenset({name})) if name else _UNKNOWN
|
|
125
|
-
if isinstance(expr, (ast.Attribute, ast.Subscript)):
|
|
126
|
-
dotted = _dotted(expr)
|
|
127
|
-
origin = Origin(attrs=frozenset({dotted})) if dotted else _UNKNOWN
|
|
128
|
-
root = _root_name(expr)
|
|
129
|
-
if root in params: # e.g. request.args[...] where `request` is a parameter
|
|
130
|
-
origin = origin.merge(Origin(params=frozenset({root})))
|
|
131
|
-
return origin
|
|
183
|
+
return combine([recurse(e) for e in expr.elts])
|
|
132
184
|
if isinstance(expr, ast.Name):
|
|
133
185
|
if expr.id in seen: # assignment cycle -- stop
|
|
134
|
-
return
|
|
186
|
+
return on_cycle()
|
|
135
187
|
if expr.id in params:
|
|
136
|
-
return
|
|
188
|
+
return on_param(expr.id)
|
|
137
189
|
if expr.id in assigns:
|
|
138
|
-
return
|
|
139
|
-
return
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _merge_all(exprs: list[ast.AST], params, assigns, seen) -> Origin:
|
|
144
|
-
origin = Origin()
|
|
145
|
-
for e in exprs:
|
|
146
|
-
origin = origin.merge(_classify(e, params, assigns, seen))
|
|
147
|
-
return origin
|
|
190
|
+
return combine([recurse(r, seen | {expr.id}) for r in assigns[expr.id]])
|
|
191
|
+
return on_global(expr.id) # module global, import, or builtin
|
|
192
|
+
return leaf(expr, recurse)
|
|
148
193
|
|
|
149
194
|
|
|
150
195
|
def _params(func: ast.AST) -> set[str]:
|
|
@@ -35,6 +35,7 @@ from codejury.analysis.provenance import (
|
|
|
35
35
|
callee,
|
|
36
36
|
find_calls,
|
|
37
37
|
parameters,
|
|
38
|
+
reduce_value,
|
|
38
39
|
)
|
|
39
40
|
from codejury.resources import TAINT_FILE
|
|
40
41
|
|
|
@@ -108,65 +109,55 @@ def taint_of(
|
|
|
108
109
|
|
|
109
110
|
``resolve_param`` is an optional ``(name) -> Taint`` callback; when given, a
|
|
110
111
|
value that reaches a parameter is resolved through it (the cross-file caller
|
|
111
|
-
hop, P1-03b) instead of returning PARAM.
|
|
112
|
+
hop, P1-03b) instead of returning PARAM. Shares the structural recursion with
|
|
113
|
+
provenance via ``reduce_value``; this is the vocabulary-aware policy.
|
|
112
114
|
"""
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return Taint.UNKNOWN # unknown object attribute (e.g. self.x): not provably safe
|
|
156
|
-
if isinstance(expr, ast.Name):
|
|
157
|
-
if expr.id in seen:
|
|
158
|
-
return Taint.CONSTANT # assignment cycle: no new information
|
|
159
|
-
if expr.id in params:
|
|
160
|
-
return resolve(expr.id) if resolve else Taint.PARAM
|
|
161
|
-
if expr.id in assigns:
|
|
162
|
-
return _combine([_walk(func, r, vocab, assigns, params, seen | {expr.id}, resolve)
|
|
163
|
-
for r in assigns[expr.id]])
|
|
164
|
-
return Taint.TRUSTED # free module global / builtin -- conventionally a constant
|
|
165
|
-
return Taint.UNKNOWN
|
|
115
|
+
params = parameters(func)
|
|
116
|
+
|
|
117
|
+
def on_param(name: str) -> Taint:
|
|
118
|
+
return resolve_param(name) if resolve_param else Taint.PARAM
|
|
119
|
+
|
|
120
|
+
def leaf(node: ast.AST, recurse) -> Taint:
|
|
121
|
+
if isinstance(node, ast.Call):
|
|
122
|
+
if _callee_in(node, vocab.sanitizers):
|
|
123
|
+
return Taint.SANITIZED # a sanitizer cleans its result regardless of input
|
|
124
|
+
if _callee_in(node, vocab.sources):
|
|
125
|
+
return Taint.EXTERNAL # e.g. input()
|
|
126
|
+
# a method ON a source/trusted object, e.g. request.args.get("id")
|
|
127
|
+
func_path = access_path(node.func)
|
|
128
|
+
if func_path and _access_in(func_path, vocab.sources):
|
|
129
|
+
return Taint.EXTERNAL
|
|
130
|
+
if func_path and _access_in(func_path, vocab.trusted):
|
|
131
|
+
return Taint.TRUSTED
|
|
132
|
+
if _callee_in(node, vocab.propagators) or _callee_in(node, vocab.safe_sinks):
|
|
133
|
+
return _combine([recurse(a) for a in node.args])
|
|
134
|
+
return Taint.UNKNOWN # unknown call -- a cross-file hop may resolve it later
|
|
135
|
+
if isinstance(node, (ast.Attribute, ast.Subscript)):
|
|
136
|
+
path = access_path(node)
|
|
137
|
+
if path and _access_in(path, vocab.sources):
|
|
138
|
+
return Taint.EXTERNAL
|
|
139
|
+
if path and _access_in(path, vocab.trusted):
|
|
140
|
+
return Taint.TRUSTED
|
|
141
|
+
if access_root(node) in params: # attribute of a parameter -- resolve at the call site
|
|
142
|
+
return on_param(access_root(node))
|
|
143
|
+
return Taint.UNKNOWN # unknown object attribute (e.g. self.x): not provably safe
|
|
144
|
+
return Taint.UNKNOWN
|
|
145
|
+
|
|
146
|
+
return reduce_value(
|
|
147
|
+
expr,
|
|
148
|
+
params=params,
|
|
149
|
+
assigns=assignments(func),
|
|
150
|
+
combine=_combine,
|
|
151
|
+
leaf=leaf,
|
|
152
|
+
on_param=on_param,
|
|
153
|
+
on_global=lambda n: Taint.TRUSTED, # free module global / builtin -- conventionally a constant
|
|
154
|
+
on_constant=lambda: Taint.CONSTANT,
|
|
155
|
+
on_cycle=lambda: Taint.CONSTANT, # assignment cycle: no new information
|
|
156
|
+
)
|
|
166
157
|
|
|
167
158
|
|
|
168
159
|
def _combine(taints: list[Taint]) -> Taint:
|
|
169
|
-
return max(taints, key=lambda t: _RANK[t])
|
|
160
|
+
return max(taints, key=lambda t: _RANK[t]) if taints else Taint.CONSTANT
|
|
170
161
|
|
|
171
162
|
|
|
172
163
|
def _callee_in(call: ast.Call, names: tuple[str, ...]) -> bool:
|
|
@@ -18,6 +18,7 @@ from codejury.domain.context import AnalysisContext
|
|
|
18
18
|
from codejury.domain.result import AnalysisResult
|
|
19
19
|
from codejury.infrastructure.cache import VerdictCache, verdict_key
|
|
20
20
|
from codejury.orchestrators.base import Orchestrator
|
|
21
|
+
from codejury.orchestrators.adaptive import AdaptiveOrchestrator
|
|
21
22
|
from codejury.orchestrators.challenge import ChallengeOrchestrator
|
|
22
23
|
from codejury.orchestrators.debate import DebateOrchestrator
|
|
23
24
|
from codejury.orchestrators.pipeline import PipelineOrchestrator
|
|
@@ -31,7 +32,7 @@ from codejury.providers.openai import OpenAIProvider
|
|
|
31
32
|
from codejury.providers.retry import RetryProvider
|
|
32
33
|
from codejury.sources.base import Source
|
|
33
34
|
|
|
34
|
-
STRATEGIES = ("single", "pipeline", "debate", "reflexion", "challenge", "taint")
|
|
35
|
+
STRATEGIES = ("single", "pipeline", "debate", "reflexion", "challenge", "taint", "adaptive")
|
|
35
36
|
PROVIDERS = ("anthropic", "openai", "litellm")
|
|
36
37
|
DEFAULT_MODEL = os.environ.get("CODEJURY_MODEL", "claude-sonnet-4-6")
|
|
37
38
|
DEFAULT_API_BASE = os.environ.get("CODEJURY_API_BASE")
|
|
@@ -59,6 +60,11 @@ def build_orchestration(
|
|
|
59
60
|
roles = (FinderAgent, ChallengerAgent, JudgeAgent)
|
|
60
61
|
agents = {cls.role: cls(provider=provider, model=model, max_tokens=max_tokens) for cls in roles}
|
|
61
62
|
return agents, DebateOrchestrator()
|
|
63
|
+
if strategy == "adaptive":
|
|
64
|
+
roles = (FinderAgent, ChallengerAgent, JudgeAgent)
|
|
65
|
+
agents = {cls.role: cls(provider=provider, model=model, max_tokens=max_tokens) for cls in roles}
|
|
66
|
+
agents["verifier"] = VerifierAgent(provider=provider, model=model, max_tokens=max_tokens)
|
|
67
|
+
return agents, AdaptiveOrchestrator()
|
|
62
68
|
if strategy == "reflexion":
|
|
63
69
|
agents = {
|
|
64
70
|
"actor": FinderAgent(provider=provider, model=model, max_tokens=max_tokens),
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""AdaptiveOrchestrator -- cheap triage, deep debate only where it pays off.
|
|
2
|
+
|
|
3
|
+
Most files are clean, and debate is several model calls; running it everywhere
|
|
4
|
+
wastes them. So this runs the single verifier first, then escalates to a full
|
|
5
|
+
Finder -> Challenger -> Judge debate only when the cheap pass found something
|
|
6
|
+
worth a second opinion:
|
|
7
|
+
|
|
8
|
+
- **high-risk**: any VULNERABLE verdict -- a flagged vulnerability is verified by
|
|
9
|
+
the adversarial pass before it is reported / gates CI;
|
|
10
|
+
- **disputed**: a PARTIAL or UNKNOWN verdict the verifier is unsure about
|
|
11
|
+
(confidence below the threshold).
|
|
12
|
+
|
|
13
|
+
A clean artifact (only confident SECURE / NOT_PRESENT verdicts) pays one verifier
|
|
14
|
+
call and stops. An escalated artifact is handed to the debate, whose deeper
|
|
15
|
+
ruling supersedes the triage verdicts for that artifact.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from codejury.agents.base import Agent
|
|
21
|
+
from codejury.domain.context import AnalysisContext
|
|
22
|
+
from codejury.domain.observation import Verdict
|
|
23
|
+
from codejury.domain.result import AnalysisResult
|
|
24
|
+
from codejury.orchestrators.base import Orchestrator
|
|
25
|
+
from codejury.orchestrators.debate import DebateOrchestrator
|
|
26
|
+
|
|
27
|
+
_DISPUTABLE = ("PARTIAL", "UNKNOWN")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AdaptiveOrchestrator(Orchestrator):
|
|
31
|
+
def __init__(self, *, confidence_threshold: float = 0.7, debate: DebateOrchestrator | None = None) -> None:
|
|
32
|
+
self._threshold = confidence_threshold
|
|
33
|
+
self._debate = debate if debate is not None else DebateOrchestrator()
|
|
34
|
+
|
|
35
|
+
def run(self, agents: dict[str, Agent], context: AnalysisContext) -> AnalysisResult:
|
|
36
|
+
if "verifier" not in agents:
|
|
37
|
+
return AnalysisResult(error="adaptive requires agents: verifier")
|
|
38
|
+
try:
|
|
39
|
+
verdicts = agents["verifier"].run(context)
|
|
40
|
+
except Exception as exc:
|
|
41
|
+
return AnalysisResult(error=f"agent 'verifier' failed: {exc}")
|
|
42
|
+
|
|
43
|
+
if not self._needs_escalation(verdicts):
|
|
44
|
+
return AnalysisResult(observations=verdicts) # cheap path: confident, low-risk
|
|
45
|
+
return self._debate.run(agents, context) # deep path supersedes the triage
|
|
46
|
+
|
|
47
|
+
def _needs_escalation(self, verdicts: list) -> bool:
|
|
48
|
+
for v in verdicts:
|
|
49
|
+
if not isinstance(v, Verdict):
|
|
50
|
+
continue
|
|
51
|
+
if v.status == "VULNERABLE": # high-risk: verify a flagged vuln adversarially
|
|
52
|
+
return True
|
|
53
|
+
if v.status in _DISPUTABLE and v.confidence < self._threshold: # disputed
|
|
54
|
+
return True
|
|
55
|
+
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: General-purpose Application Security AI audit framework -- five-layer architecture, capabilities as first-class data
|
|
5
5
|
Author: AISecLabs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -83,7 +83,7 @@ git diff | codejury audit --provider anthropic
|
|
|
83
83
|
| `codejury run <task>` | Run a named task preset (see [Tasks](#tasks)). |
|
|
84
84
|
| `codejury eval` | Score the golden cases; report precision / recall / F1, overall and per capability. |
|
|
85
85
|
|
|
86
|
-
Shared flags: `--orchestrator {single,pipeline,debate,reflexion,challenge,taint}`,
|
|
86
|
+
Shared flags: `--orchestrator {single,pipeline,debate,reflexion,challenge,taint,adaptive}`,
|
|
87
87
|
`--provider {anthropic,openai,litellm}`, `--model`,
|
|
88
88
|
`--format {text,markdown,json,sarif}`.
|
|
89
89
|
|
|
@@ -103,6 +103,11 @@ value reaching the sink is constant, sanitized, or trusted (using cross-file
|
|
|
103
103
|
caller/callee context). It downgrades only on positive proof, so it removes false
|
|
104
104
|
positives without dropping real findings.
|
|
105
105
|
|
|
106
|
+
`--orchestrator adaptive` keeps cost down: it runs the single verifier first and
|
|
107
|
+
escalates to a full debate only for artifacts worth it -- any VULNERABLE verdict
|
|
108
|
+
(verify a flag before reporting) or a low-confidence PARTIAL/UNKNOWN one. Clean,
|
|
109
|
+
confident files pay a single call; only the rest pay for debate.
|
|
110
|
+
|
|
106
111
|
`--format sarif` emits a SARIF 2.1.0 log (validates against the official schema)
|
|
107
112
|
for CI and security dashboards: each problem with a code location becomes a
|
|
108
113
|
result carrying its capability (as the rule id), CWE, and a precise location.
|
|
@@ -120,6 +120,7 @@ codejury/infrastructure/json_parse.py
|
|
|
120
120
|
codejury/integrations/__init__.py
|
|
121
121
|
codejury/integrations/github.py
|
|
122
122
|
codejury/orchestrators/__init__.py
|
|
123
|
+
codejury/orchestrators/adaptive.py
|
|
123
124
|
codejury/orchestrators/base.py
|
|
124
125
|
codejury/orchestrators/challenge.py
|
|
125
126
|
codejury/orchestrators/debate.py
|
|
@@ -146,6 +147,7 @@ codejury/sources/repo.py
|
|
|
146
147
|
codejury/tasks/__init__.py
|
|
147
148
|
codejury/tasks/base.py
|
|
148
149
|
codejury/tasks/registry.py
|
|
150
|
+
tests/test_adaptive.py
|
|
149
151
|
tests/test_anthropic_provider.py
|
|
150
152
|
tests/test_assembly.py
|
|
151
153
|
tests/test_audit_pipeline.py
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from codejury.assembly import build_orchestration
|
|
4
|
+
from codejury.domain.artifact import CodeArtifact
|
|
5
|
+
from codejury.domain.capability import Capability
|
|
6
|
+
from codejury.domain.context import AnalysisContext
|
|
7
|
+
from codejury.orchestrators.adaptive import AdaptiveOrchestrator
|
|
8
|
+
from codejury.providers.mock import MockProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _verdict(status, confidence=0.9):
|
|
12
|
+
return json.dumps({"verdicts": [{"sub_capability": "x", "status": status, "confidence": confidence}]})
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# A finder/judge JSON so an escalated debate produces a recognizable Finding.
|
|
16
|
+
_FINDING = json.dumps({"findings": [{"title": "deep issue", "severity": "HIGH"}]})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _ctx():
|
|
20
|
+
return AnalysisContext(
|
|
21
|
+
artifact=CodeArtifact(kind="file", path="a.py", content="code"),
|
|
22
|
+
capabilities=[Capability(id="input_validation", name="input_validation")],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _build(provider):
|
|
27
|
+
return build_orchestration("adaptive", provider=provider, model="m", max_tokens=8)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_clean_artifact_does_not_escalate():
|
|
31
|
+
# confident SECURE -> cheap path: the verifier's verdict is returned as-is,
|
|
32
|
+
# and the debate agents are never queried.
|
|
33
|
+
provider = MockProvider(default=_verdict("SECURE"))
|
|
34
|
+
agents, orch = _build(provider)
|
|
35
|
+
result = orch.run(agents, _ctx())
|
|
36
|
+
assert [v.status for v in result.observations if v.kind == "verdict"] == ["SECURE"]
|
|
37
|
+
assert len(provider.calls) == 1 # only the verifier ran
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_vulnerable_verdict_escalates_to_debate():
|
|
41
|
+
# high-risk: a VULNERABLE verdict triggers the debate (more than one model call).
|
|
42
|
+
provider = MockProvider(responses=[_verdict("VULNERABLE")], default=_FINDING)
|
|
43
|
+
agents, orch = _build(provider)
|
|
44
|
+
orch.run(agents, _ctx())
|
|
45
|
+
assert len(provider.calls) > 1 # verifier + debate rounds
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_low_confidence_partial_escalates():
|
|
49
|
+
provider = MockProvider(responses=[_verdict("PARTIAL", confidence=0.3)], default=_FINDING)
|
|
50
|
+
agents, orch = _build(provider)
|
|
51
|
+
orch.run(agents, _ctx())
|
|
52
|
+
assert len(provider.calls) > 1
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_confident_partial_does_not_escalate():
|
|
56
|
+
provider = MockProvider(default=_verdict("PARTIAL", confidence=0.95))
|
|
57
|
+
agents, orch = _build(provider)
|
|
58
|
+
orch.run(agents, _ctx())
|
|
59
|
+
assert len(provider.calls) == 1 # confident enough -> no debate
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_unknown_low_confidence_escalates():
|
|
63
|
+
provider = MockProvider(responses=[_verdict("UNKNOWN", confidence=0.5)], default=_FINDING)
|
|
64
|
+
agents, orch = _build(provider)
|
|
65
|
+
orch.run(agents, _ctx())
|
|
66
|
+
assert len(provider.calls) > 1
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_missing_verifier_errors():
|
|
70
|
+
result = AdaptiveOrchestrator().run({}, _ctx())
|
|
71
|
+
assert result.error and "verifier" in result.error
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/business_logic_price_tamper_vuln.yaml
RENAMED
|
File without changes
|
{codejury-0.9.1 → codejury-0.10.0}/codejury/data/golden/business_logic_server_checked_safe.yaml
RENAMED
|
File without changes
|
|
File without changes
|