codejury 0.10.0__tar.gz → 0.10.2__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.0 → codejury-0.10.2}/PKG-INFO +13 -13
- {codejury-0.10.0 → codejury-0.10.2}/README.md +12 -12
- {codejury-0.10.0 → codejury-0.10.2}/codejury/__init__.py +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/__init__.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/debate.py +9 -6
- {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/mock.py +2 -3
- codejury-0.10.2/codejury/agents/parsing.py +45 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/refuter.py +3 -3
- {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/verifier.py +11 -7
- {codejury-0.10.0 → codejury-0.10.2}/codejury/analysis/__init__.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/analysis/provenance.py +5 -5
- {codejury-0.10.0 → codejury-0.10.2}/codejury/analysis/taint.py +6 -6
- {codejury-0.10.0 → codejury-0.10.2}/codejury/assembly.py +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/baseline.py +13 -17
- {codejury-0.10.0 → codejury-0.10.2}/codejury/cli.py +22 -14
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/business_logic.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/dependency_config.yaml +3 -3
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/excessive_agency.yaml +3 -3
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/input_validation.yaml +6 -6
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/insecure_output_handling.yaml +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/prompt_injection.yaml +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/secrets.yaml +5 -5
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_fixed_enum_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_model_confirmed_vuln.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_sha256_checksum_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/deserialize_json_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/deserialize_pickle_vuln.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_json_response_safe.yaml +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_output_to_sql_vuln.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_user_content_concat_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/session_secure_cookie_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sql_constant_concat_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ssrf_constant_url_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xfile_idor_no_check_vuln.yaml +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xfile_idor_owner_checked_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xfile_path_sanitized_safe.yaml +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xfile_path_tainted_vuln.yaml +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xss_textcontent_safe.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/suppressions.yaml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/taint.yaml +4 -4
- {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/__init__.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/artifact.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/capability.py +17 -4
- {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/context.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/observation.py +27 -6
- {codejury-0.10.0 → codejury-0.10.2}/codejury/domain/result.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/evaluation.py +14 -8
- codejury-0.10.2/codejury/infrastructure/__init__.py +4 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/infrastructure/cache.py +4 -4
- {codejury-0.10.0 → codejury-0.10.2}/codejury/infrastructure/json_parse.py +8 -3
- codejury-0.10.2/codejury/integrations/__init__.py +1 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/integrations/github.py +24 -9
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/__init__.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/adaptive.py +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/base.py +10 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/challenge.py +12 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/debate.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/pipeline.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/reflexion.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/single.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/orchestrators/taint_gate.py +3 -3
- {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/__init__.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/anthropic.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/litellm.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/mock.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/openai.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/retry.py +12 -5
- {codejury-0.10.0 → codejury-0.10.2}/codejury/reporting.py +17 -12
- {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/__init__.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/callers.py +8 -6
- {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/chunker.py +3 -3
- {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/diff.py +6 -5
- {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/function.py +13 -4
- {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/mock.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/repo.py +5 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/suppression.py +14 -16
- {codejury-0.10.0 → codejury-0.10.2}/codejury/tasks/__init__.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/codejury/tasks/base.py +4 -2
- {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/PKG-INFO +13 -13
- {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/SOURCES.txt +2 -0
- {codejury-0.10.0 → codejury-0.10.2}/pyproject.toml +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_cache.py +1 -1
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_evaluation.py +1 -1
- codejury-0.10.2/tests/test_parsing.py +32 -0
- codejury-0.10.2/tests/test_review_fixes.py +100 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_taint.py +2 -2
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_taint_gate.py +2 -2
- codejury-0.10.0/codejury/agents/parsing.py +0 -42
- codejury-0.10.0/codejury/infrastructure/__init__.py +0 -4
- codejury-0.10.0/codejury/integrations/__init__.py +0 -1
- {codejury-0.10.0 → codejury-0.10.2}/LICENSE +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/agents/base.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/authentication.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/authorization.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/crypto.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/data_protection.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/error_logging.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/output_encoding.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/capabilities/session.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_allowlist_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_arbitrary_tool_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_destructive_no_confirm_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ag_human_approval_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_bcrypt_password.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_jwt_noverify_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_jwt_verified_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_sha256_password.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authn_weak_hash_indirect_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authz_idor_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/authz_owner_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/business_logic_price_tamper_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/business_logic_server_checked_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/cmdi_fixed_argv_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/cmdi_ossystem_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/cmdi_subprocess_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/crypto_aesgcm_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/crypto_ecb_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/data_protection_plaintext_pii_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/data_protection_tokenized_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/dependency_config_tls_verify_off_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/dependency_config_tls_verify_on_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/error_logging_redacted_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/error_logging_secret_leak_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_escaped_output_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_exec_output_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_innerhtml_output_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ioh_schema_validated_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/literal_eval_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/path_basename_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/path_contained_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/path_traversal_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_delimited_data_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_format_role_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_indirect_rag_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_system_concat_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/pi_user_role_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/secrets_env_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/secrets_hardcoded_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/session_fixation_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sqli_format_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sqli_fstring_query.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sqli_indirect_var_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/sqli_parameterized_query.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ssrf_allowlist_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ssrf_substring_allowlist_bypass_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/ssrf_user_url_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xss_innerhtml_constant_safe.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/golden/xss_innerhtml_vuln.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/tasks/audit_diff_debate.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/data/tasks/quick_scan_single.yaml +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/base.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/providers/openai_format.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/resources.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/sources/base.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury/tasks/registry.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/dependency_links.txt +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/entry_points.txt +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/requires.txt +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/codejury.egg-info/top_level.txt +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/setup.cfg +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_adaptive.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_anthropic_provider.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_assembly.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_audit_pipeline.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_baseline.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_callers.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_capability.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_challenge.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_cli_audit.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_context.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_debate_agents.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_debate_orchestrator.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_diff_source.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_function_source.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_integrations.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_json_parse.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_litellm_provider.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_openai_provider.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_orchestrator.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_pipeline_orchestrator.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_provenance.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_reflexion_orchestrator.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_repo_source.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_reporting.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_retry_provider.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_sarif.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_suppression.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_taint_crossfile.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_tasks.py +0 -0
- {codejury-0.10.0 → codejury-0.10.2}/tests/test_verifier.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codejury
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.2
|
|
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
|
|
@@ -32,22 +32,22 @@ Dynamic: license-file
|
|
|
32
32
|
|
|
33
33
|
An AI security auditor for code whose knowledge lives in versioned YAML, not in
|
|
34
34
|
prompts. It reviews a diff or a whole repository against the OWASP ASVS and
|
|
35
|
-
reports a verdict per dimension
|
|
35
|
+
reports a verdict per dimension: both what is **vulnerable** and what is
|
|
36
36
|
**verified safe**.
|
|
37
37
|
|
|
38
|
-
The name is the core idea: code goes before a "jury" of adversarial roles
|
|
39
|
-
Finder / Challenger / Judge
|
|
38
|
+
The name is the core idea: code goes before a "jury" of adversarial roles,
|
|
39
|
+
Finder / Challenger / Judge, that argue and converge on a verdict.
|
|
40
40
|
|
|
41
41
|
Why it is built this way:
|
|
42
42
|
|
|
43
43
|
- **Knowledge is data.** Each OWASP ASVS area (and now OWASP LLM Top 10 areas) is a YAML capability
|
|
44
|
-
(safe patterns + anti-patterns, with CWE and examples)
|
|
44
|
+
(safe patterns + anti-patterns, with CWE and examples), versioned, reviewable
|
|
45
45
|
in a PR, and editable by non-engineers. The framework core stays small.
|
|
46
46
|
- **Verdicts, not just alerts.** Every capability yields `SECURE` / `VULNERABLE`
|
|
47
47
|
/ `PARTIAL` / `NOT_PRESENT`, so a report shows what was checked and *passed*,
|
|
48
48
|
not only what failed.
|
|
49
49
|
- **Composable.** Four orchestration strategies, four model backends, and
|
|
50
|
-
diff / repo inputs are chosen per run
|
|
50
|
+
diff / repo inputs are chosen per run; mix and match.
|
|
51
51
|
|
|
52
52
|
## Install
|
|
53
53
|
|
|
@@ -65,7 +65,7 @@ git diff origin/main... | codejury audit --fail-on high -
|
|
|
65
65
|
# Post inline review comments on a GitHub pull request (needs GITHUB_TOKEN)
|
|
66
66
|
git diff origin/main... | codejury audit --github your-org/your-repo#123 -
|
|
67
67
|
|
|
68
|
-
# No API key needed
|
|
68
|
+
# No API key needed: prove the pipeline runs end to end with mock layers
|
|
69
69
|
codejury dry-run
|
|
70
70
|
|
|
71
71
|
# A real audit: set a key, then review your staged changes
|
|
@@ -104,7 +104,7 @@ caller/callee context). It downgrades only on positive proof, so it removes fals
|
|
|
104
104
|
positives without dropping real findings.
|
|
105
105
|
|
|
106
106
|
`--orchestrator adaptive` keeps cost down: it runs the single verifier first and
|
|
107
|
-
escalates to a full debate only for artifacts worth it
|
|
107
|
+
escalates to a full debate only for artifacts worth it: any VULNERABLE verdict
|
|
108
108
|
(verify a flag before reporting) or a low-confidence PARTIAL/UNKNOWN one. Clean,
|
|
109
109
|
confident files pay a single call; only the rest pay for debate.
|
|
110
110
|
|
|
@@ -118,7 +118,7 @@ outside C/C++) are dropped by versioned rules in
|
|
|
118
118
|
|
|
119
119
|
`codejury eval` takes `--dataset <dir>` (golden YAML directory), `--split <name>`
|
|
120
120
|
(score only cases tagged with that `split:`, e.g. a held-out set), and
|
|
121
|
-
`--format {text,json}
|
|
121
|
+
`--format {text,json}`; the JSON report is a stable schema (overall plus
|
|
122
122
|
per-capability confusion matrix and precision / recall / F1).
|
|
123
123
|
|
|
124
124
|
Runs are deterministic: providers query at temperature 0, and `audit` / `scan`
|
|
@@ -139,7 +139,7 @@ codejury scan ./myrepo --only secrets,input_validation,crypto
|
|
|
139
139
|
## Configuration
|
|
140
140
|
|
|
141
141
|
Provider keys are read from the environment (codejury does **not** auto-load
|
|
142
|
-
`.env
|
|
142
|
+
`.env`; copy `.env.example` and `source` it):
|
|
143
143
|
|
|
144
144
|
| Variable | Used by |
|
|
145
145
|
|---|---|
|
|
@@ -180,7 +180,7 @@ what `--only` and a task's `capabilities:` accept:
|
|
|
180
180
|
`business_logic` · `dependency_config` · `prompt_injection` ·
|
|
181
181
|
`insecure_output_handling` · `excessive_agency`
|
|
182
182
|
|
|
183
|
-
To tune for your codebase, edit these files (add patterns / sharpen wording)
|
|
183
|
+
To tune for your codebase, edit these files (add patterns / sharpen wording);
|
|
184
184
|
no code change needed.
|
|
185
185
|
|
|
186
186
|
## Architecture
|
|
@@ -210,13 +210,13 @@ independently.
|
|
|
210
210
|
can't see whether a value is attacker-controlled. Mitigations that add context
|
|
211
211
|
but do not fully solve it: `scan --callers` (where this file's functions are
|
|
212
212
|
called) and `scan --callees` (the called code it delegates to, so a sink in
|
|
213
|
-
another file is visible)
|
|
213
|
+
another file is visible); pair them for both directions; `--orchestrator
|
|
214
214
|
challenge` (a recall-safe
|
|
215
215
|
refutation pass that drops only provably-safe flags); `--only` to scope; or
|
|
216
216
|
`--orchestrator debate`. Real taint precision still needs data-flow analysis,
|
|
217
217
|
not model skepticism.
|
|
218
218
|
- **`scan` cost scales as files x capabilities.** It is a periodic deep audit,
|
|
219
|
-
not a quick check
|
|
219
|
+
not a quick check; scope it with `--only`. Day to day, audit the diff.
|
|
220
220
|
|
|
221
221
|
## Development
|
|
222
222
|
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
An AI security auditor for code whose knowledge lives in versioned YAML, not in
|
|
4
4
|
prompts. It reviews a diff or a whole repository against the OWASP ASVS and
|
|
5
|
-
reports a verdict per dimension
|
|
5
|
+
reports a verdict per dimension: both what is **vulnerable** and what is
|
|
6
6
|
**verified safe**.
|
|
7
7
|
|
|
8
|
-
The name is the core idea: code goes before a "jury" of adversarial roles
|
|
9
|
-
Finder / Challenger / Judge
|
|
8
|
+
The name is the core idea: code goes before a "jury" of adversarial roles,
|
|
9
|
+
Finder / Challenger / Judge, that argue and converge on a verdict.
|
|
10
10
|
|
|
11
11
|
Why it is built this way:
|
|
12
12
|
|
|
13
13
|
- **Knowledge is data.** Each OWASP ASVS area (and now OWASP LLM Top 10 areas) is a YAML capability
|
|
14
|
-
(safe patterns + anti-patterns, with CWE and examples)
|
|
14
|
+
(safe patterns + anti-patterns, with CWE and examples), versioned, reviewable
|
|
15
15
|
in a PR, and editable by non-engineers. The framework core stays small.
|
|
16
16
|
- **Verdicts, not just alerts.** Every capability yields `SECURE` / `VULNERABLE`
|
|
17
17
|
/ `PARTIAL` / `NOT_PRESENT`, so a report shows what was checked and *passed*,
|
|
18
18
|
not only what failed.
|
|
19
19
|
- **Composable.** Four orchestration strategies, four model backends, and
|
|
20
|
-
diff / repo inputs are chosen per run
|
|
20
|
+
diff / repo inputs are chosen per run; mix and match.
|
|
21
21
|
|
|
22
22
|
## Install
|
|
23
23
|
|
|
@@ -35,7 +35,7 @@ git diff origin/main... | codejury audit --fail-on high -
|
|
|
35
35
|
# Post inline review comments on a GitHub pull request (needs GITHUB_TOKEN)
|
|
36
36
|
git diff origin/main... | codejury audit --github your-org/your-repo#123 -
|
|
37
37
|
|
|
38
|
-
# No API key needed
|
|
38
|
+
# No API key needed: prove the pipeline runs end to end with mock layers
|
|
39
39
|
codejury dry-run
|
|
40
40
|
|
|
41
41
|
# A real audit: set a key, then review your staged changes
|
|
@@ -74,7 +74,7 @@ caller/callee context). It downgrades only on positive proof, so it removes fals
|
|
|
74
74
|
positives without dropping real findings.
|
|
75
75
|
|
|
76
76
|
`--orchestrator adaptive` keeps cost down: it runs the single verifier first and
|
|
77
|
-
escalates to a full debate only for artifacts worth it
|
|
77
|
+
escalates to a full debate only for artifacts worth it: any VULNERABLE verdict
|
|
78
78
|
(verify a flag before reporting) or a low-confidence PARTIAL/UNKNOWN one. Clean,
|
|
79
79
|
confident files pay a single call; only the rest pay for debate.
|
|
80
80
|
|
|
@@ -88,7 +88,7 @@ outside C/C++) are dropped by versioned rules in
|
|
|
88
88
|
|
|
89
89
|
`codejury eval` takes `--dataset <dir>` (golden YAML directory), `--split <name>`
|
|
90
90
|
(score only cases tagged with that `split:`, e.g. a held-out set), and
|
|
91
|
-
`--format {text,json}
|
|
91
|
+
`--format {text,json}`; the JSON report is a stable schema (overall plus
|
|
92
92
|
per-capability confusion matrix and precision / recall / F1).
|
|
93
93
|
|
|
94
94
|
Runs are deterministic: providers query at temperature 0, and `audit` / `scan`
|
|
@@ -109,7 +109,7 @@ codejury scan ./myrepo --only secrets,input_validation,crypto
|
|
|
109
109
|
## Configuration
|
|
110
110
|
|
|
111
111
|
Provider keys are read from the environment (codejury does **not** auto-load
|
|
112
|
-
`.env
|
|
112
|
+
`.env`; copy `.env.example` and `source` it):
|
|
113
113
|
|
|
114
114
|
| Variable | Used by |
|
|
115
115
|
|---|---|
|
|
@@ -150,7 +150,7 @@ what `--only` and a task's `capabilities:` accept:
|
|
|
150
150
|
`business_logic` · `dependency_config` · `prompt_injection` ·
|
|
151
151
|
`insecure_output_handling` · `excessive_agency`
|
|
152
152
|
|
|
153
|
-
To tune for your codebase, edit these files (add patterns / sharpen wording)
|
|
153
|
+
To tune for your codebase, edit these files (add patterns / sharpen wording);
|
|
154
154
|
no code change needed.
|
|
155
155
|
|
|
156
156
|
## Architecture
|
|
@@ -180,13 +180,13 @@ independently.
|
|
|
180
180
|
can't see whether a value is attacker-controlled. Mitigations that add context
|
|
181
181
|
but do not fully solve it: `scan --callers` (where this file's functions are
|
|
182
182
|
called) and `scan --callees` (the called code it delegates to, so a sink in
|
|
183
|
-
another file is visible)
|
|
183
|
+
another file is visible); pair them for both directions; `--orchestrator
|
|
184
184
|
challenge` (a recall-safe
|
|
185
185
|
refutation pass that drops only provably-safe flags); `--only` to scope; or
|
|
186
186
|
`--orchestrator debate`. Real taint precision still needs data-flow analysis,
|
|
187
187
|
not model skepticism.
|
|
188
188
|
- **`scan` cost scales as files x capabilities.** It is a periodic deep audit,
|
|
189
|
-
not a quick check
|
|
189
|
+
not a quick check; scope it with `--only`. Day to day, audit the diff.
|
|
190
190
|
|
|
191
191
|
## Development
|
|
192
192
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""codejury
|
|
1
|
+
"""codejury: a general-purpose Application Security AI audit framework.
|
|
2
2
|
|
|
3
|
-
Five layers: Task
|
|
3
|
+
Five layers: Task, then Capability plus Orchestrator plus Source plus Agent, then Provider, then Infrastructure.
|
|
4
4
|
Domain knowledge lives in YAML capability files as a first-class citizen,
|
|
5
5
|
aligned with OWASP ASVS.
|
|
6
6
|
"""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""codejury.agents
|
|
1
|
+
"""codejury.agents: the audit roles (finder / challenger / judge / verifier).
|
|
2
2
|
|
|
3
3
|
An agent reads an AnalysisContext and emits observations. It talks to a model
|
|
4
4
|
only through a Provider, never a vendor SDK, so the role logic stays independent
|
|
@@ -19,21 +19,24 @@ from codejury.agents.parsing import one_of, str_list, to_evidence, to_float
|
|
|
19
19
|
from codejury.domain.artifact import CodeArtifact
|
|
20
20
|
from codejury.domain.capability import Capability
|
|
21
21
|
from codejury.domain.context import AnalysisContext
|
|
22
|
-
from codejury.domain.observation import Concession, Finding, Observation
|
|
22
|
+
from codejury.domain.observation import Concession, Finding, Observation, Severity
|
|
23
23
|
from codejury.infrastructure.json_parse import extract_json_object
|
|
24
24
|
from codejury.providers.base import Message, Provider
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
from typing import get_args
|
|
27
|
+
|
|
28
|
+
_SEVERITY = set(get_args(Severity)) # single source of truth: the Severity Literal
|
|
27
29
|
|
|
28
30
|
_FINDING_SHAPE = (
|
|
29
31
|
'{"capability": "id.sub", "title": "...", "severity": "HIGH", "cwe": "CWE-89", '
|
|
30
|
-
'"description": "...", "
|
|
32
|
+
'"description": "...", "recommendation": "...", "matched_anti": ["id"], '
|
|
33
|
+
'"evidence": [{"file": "...", "line": 0, "code": "..."}], "confidence": 0.0}'
|
|
31
34
|
)
|
|
32
35
|
|
|
33
36
|
_DEEP_LENS = (
|
|
34
37
|
"Look past surface patterns for the deepest flaw:\n"
|
|
35
|
-
"- Trust anchors: what does this code trust to authenticate or authorize
|
|
36
|
-
"signature, role, or caller
|
|
38
|
+
"- Trust anchors: what does this code trust to authenticate or authorize, such as a key, token, header, "
|
|
39
|
+
"signature, role, or caller, and who controls that value? If the attacker supplies what is used to "
|
|
37
40
|
"verify them (e.g. their own public key, an unconfigured key that disables verification), passing the "
|
|
38
41
|
"check proves nothing.\n"
|
|
39
42
|
"- Order of operations: is an external, irreversible, or privileged action performed before the local "
|
|
@@ -98,7 +101,7 @@ class ChallengerAgent(_DebateAgent):
|
|
|
98
101
|
def run(self, ctx: AnalysisContext) -> list[Observation]:
|
|
99
102
|
parts = [
|
|
100
103
|
"Challenge the findings below. For each one you believe is a false positive, write a rebuttal. "
|
|
101
|
-
"Add new_findings for any real issue that was missed
|
|
104
|
+
"Add new_findings for any real issue that was missed, especially a deeper flaw the finder "
|
|
102
105
|
"stopped short of.",
|
|
103
106
|
_DEEP_LENS,
|
|
104
107
|
_code(ctx.artifact),
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
"""MockAgent
|
|
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
4
|
path), then emits one Verdict per in-scope capability, parking the model's reply
|
|
5
|
-
in ``reasoning``. It does no real parsing or judgement;
|
|
6
|
-
will.
|
|
5
|
+
in ``reasoning``. It does no real parsing or judgement; ``VerifierAgent`` does.
|
|
7
6
|
"""
|
|
8
7
|
|
|
9
8
|
from __future__ import annotations
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Shared coercion from loosely-typed model JSON into domain values.
|
|
2
|
+
|
|
3
|
+
Agents parse model output that may omit, mistype, or invent fields. These
|
|
4
|
+
helpers coerce defensively: they never raise on bad input, they fall back.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
from codejury.domain.observation import Evidence
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def str_list(value: object) -> list[str]:
|
|
13
|
+
return [str(x) for x in value] if isinstance(value, list) else []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def to_float(value: object, default: float) -> float:
|
|
17
|
+
# reject bool (it is an int subclass) and non-finite values (json.loads accepts
|
|
18
|
+
# NaN/Infinity); a NaN confidence would silently corrupt every downstream sort.
|
|
19
|
+
if isinstance(value, bool):
|
|
20
|
+
return default
|
|
21
|
+
try:
|
|
22
|
+
result = float(value) # type: ignore[arg-type]
|
|
23
|
+
except (TypeError, ValueError):
|
|
24
|
+
return default
|
|
25
|
+
return result if math.isfinite(result) else default
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def one_of(value: object, allowed: set[str], default: str) -> str:
|
|
29
|
+
# `value in allowed` would raise TypeError on an unhashable model value
|
|
30
|
+
# (e.g. status -> [] or {}); guard so bad input falls back, never raises.
|
|
31
|
+
return value if isinstance(value, str) and value in allowed else default
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def to_evidence(items: object) -> list[Evidence]:
|
|
35
|
+
if not isinstance(items, list):
|
|
36
|
+
return []
|
|
37
|
+
out: list[Evidence] = []
|
|
38
|
+
for e in items:
|
|
39
|
+
if not isinstance(e, dict):
|
|
40
|
+
continue
|
|
41
|
+
line = e.get("line")
|
|
42
|
+
# a location needs a real 1-based line; reject 0, negatives, and bool (an int subclass)
|
|
43
|
+
valid_line = line if isinstance(line, int) and not isinstance(line, bool) and line >= 1 else None
|
|
44
|
+
out.append(Evidence(file=str(e.get("file", "")), line=valid_line, code=str(e.get("code", ""))))
|
|
45
|
+
return out
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"""RefuterAgent
|
|
1
|
+
"""RefuterAgent: a skeptic that tries to dismiss flagged verdicts as false positives.
|
|
2
2
|
|
|
3
3
|
Used by the challenge orchestrator: the verifier flags issues, then the refuter
|
|
4
4
|
gets the code plus the VULNERABLE verdicts (via ``ctx.history``) and argues which
|
|
5
|
-
are false positives
|
|
5
|
+
are false positives, e.g. a value that is not actually attacker-controlled or a
|
|
6
6
|
sink that is not reachable. It returns a Concession per verdict it refutes.
|
|
7
7
|
|
|
8
8
|
This is the cheap, focused alternative to a full debate: only flagged verdicts
|
|
@@ -53,7 +53,7 @@ class RefuterAgent(Agent):
|
|
|
53
53
|
"is not attacker-controlled: a stored data field, or traced (here or in the call sites) to "
|
|
54
54
|
"a trusted, config, or operator-supplied source. If its origin is not shown or could "
|
|
55
55
|
"plausibly be external input, do NOT refute. For other issue types (hardcoded secrets, "
|
|
56
|
-
"weak crypto, ...), a literal value is often the vulnerability itself
|
|
56
|
+
"weak crypto, ...), a literal value is often the vulnerability itself; do NOT refute "
|
|
57
57
|
"those just because a value is constant.\n\n"
|
|
58
58
|
"Respond with a single JSON object exactly like:\n" + _JSON_SHAPE
|
|
59
59
|
)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""VerifierAgent
|
|
1
|
+
"""VerifierAgent: check code against a capability's correct/anti patterns.
|
|
2
2
|
|
|
3
3
|
It renders the capability into a prompt, calls the provider once per capability,
|
|
4
4
|
and parses the JSON reply into Verdicts. It asks for one verdict per
|
|
5
5
|
sub_capability including SECURE / NOT_PRESENT, so a report can say what was
|
|
6
|
-
checked and what passed
|
|
6
|
+
checked and what passed, not only what failed.
|
|
7
7
|
|
|
8
8
|
Parsing is defensive: a missing or malformed reply yields no verdicts rather
|
|
9
9
|
than raising, and unknown status values fall back to UNKNOWN.
|
|
@@ -15,11 +15,13 @@ from codejury.agents.base import Agent
|
|
|
15
15
|
from codejury.agents.parsing import one_of, str_list, to_evidence, to_float
|
|
16
16
|
from codejury.domain.capability import Capability
|
|
17
17
|
from codejury.domain.context import AnalysisContext
|
|
18
|
-
from codejury.domain.observation import Observation, Verdict
|
|
18
|
+
from codejury.domain.observation import Observation, Verdict, VerdictStatus
|
|
19
19
|
from codejury.infrastructure.json_parse import extract_json_object
|
|
20
20
|
from codejury.providers.base import Message, Provider
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
from typing import get_args
|
|
23
|
+
|
|
24
|
+
_VALID_STATUS = set(get_args(VerdictStatus)) # single source of truth: the VerdictStatus Literal
|
|
23
25
|
|
|
24
26
|
_SYSTEM = (
|
|
25
27
|
"You are a security verifier. You check code against a checklist of correct and "
|
|
@@ -73,7 +75,7 @@ def _render_capability(cap: Capability) -> str:
|
|
|
73
75
|
def _build_prompt(path: str, content: str, cap: Capability, context: str = "") -> str:
|
|
74
76
|
sub_names = ", ".join(cap.sub_capabilities) or "(none)"
|
|
75
77
|
context_block = (
|
|
76
|
-
f"Related code (call sites / usages elsewhere
|
|
78
|
+
f"Related code (call sites / usages elsewhere, for tracing where values come from, "
|
|
77
79
|
f"NOT under review):\n```\n{context}\n```\n\n"
|
|
78
80
|
if context
|
|
79
81
|
else ""
|
|
@@ -88,7 +90,7 @@ def _build_prompt(path: str, content: str, cap: Capability, context: str = "") -
|
|
|
88
90
|
"For input-driven issues (injection, path traversal, SSRF), mark VULNERABLE only when "
|
|
89
91
|
"untrusted/external input could plausibly reach the sink in the code shown. A constant, "
|
|
90
92
|
"a stored data field, a value from trusted config, or a path or argument the operator "
|
|
91
|
-
"supplies (e.g. a CLI argument) is not attacker-controlled
|
|
93
|
+
"supplies (e.g. a CLI argument) is not attacker-controlled; do not flag it.\n\n"
|
|
92
94
|
"Respond with a single JSON object exactly like:\n" + _JSON_SHAPE
|
|
93
95
|
)
|
|
94
96
|
|
|
@@ -108,8 +110,10 @@ def _anti_pattern_cwes(cap: Capability) -> dict[str, tuple[int, str]]:
|
|
|
108
110
|
|
|
109
111
|
|
|
110
112
|
def _resolve_cwe(matched_anti: list[str], cwe_by_id: dict[str, tuple[int, str]]) -> str:
|
|
113
|
+
# the most severe matched anti-pattern's CWE; ties broken by CWE id so the result
|
|
114
|
+
# is fully determined by the inputs, not by dict/list ordering.
|
|
111
115
|
matched = [cwe_by_id[a] for a in matched_anti if a in cwe_by_id]
|
|
112
|
-
return max(matched, key=lambda rank_cwe: rank_cwe[0])[1] if matched else ""
|
|
116
|
+
return max(matched, key=lambda rank_cwe: (rank_cwe[0], rank_cwe[1]))[1] if matched else ""
|
|
113
117
|
|
|
114
118
|
|
|
115
119
|
def _parse_verdicts(text: str, cap: Capability) -> list[Verdict]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Static analysis for provenance (P1).
|
|
2
2
|
|
|
3
|
-
The code-graph / data-flow engine that gives the verifier provenance
|
|
3
|
+
The code-graph / data-flow engine that gives the verifier provenance: whether a
|
|
4
4
|
value reaching a sink is attacker-controlled, sanitized, or a trusted constant.
|
|
5
5
|
This is the real fix for the taint precision floor that single-file LLM review
|
|
6
6
|
cannot reach (see ROADMAP P1). Python / AST based to start.
|
|
@@ -8,7 +8,7 @@ and literals a value derives from.
|
|
|
8
8
|
The analysis is deliberately conservative and flow-insensitive: a name assigned
|
|
9
9
|
more than once contributes the union of all its right-hand sides, so a possible
|
|
10
10
|
source is never dropped (recall over precision). A value built only from literals
|
|
11
|
-
is reported as ``is_constant
|
|
11
|
+
is reported as ``is_constant``, the signal that distinguishes, for example,
|
|
12
12
|
SQL concatenated from constants (safe) from SQL concatenated from a parameter.
|
|
13
13
|
|
|
14
14
|
This module finds where a value comes from; it does not decide what is a source
|
|
@@ -43,7 +43,7 @@ class Origin:
|
|
|
43
43
|
|
|
44
44
|
@property
|
|
45
45
|
def is_constant(self) -> bool:
|
|
46
|
-
"""True when the value is built only from literals
|
|
46
|
+
"""True when the value is built only from literals: no param, call, attr,
|
|
47
47
|
free name, or unmodelled expression contributes."""
|
|
48
48
|
return not (self.params or self.calls or self.attrs or self.globals_ or self.unknown)
|
|
49
49
|
|
|
@@ -65,7 +65,7 @@ def parse_function(source: str, name: str) -> ast.FunctionDef | ast.AsyncFunctio
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
def find_calls(scope: ast.AST, callee: str) -> list[ast.Call]:
|
|
68
|
-
"""Every call within ``scope`` whose function is named ``callee
|
|
68
|
+
"""Every call within ``scope`` whose function is named ``callee``, matching a
|
|
69
69
|
bare name (``open``) or the final attribute (``execute`` in ``cur.execute``)."""
|
|
70
70
|
return [node for node in ast.walk(scope) if isinstance(node, ast.Call) and _call_name(node) == callee]
|
|
71
71
|
|
|
@@ -97,7 +97,7 @@ def trace_value(func: ast.FunctionDef | ast.AsyncFunctionDef, expr: ast.AST) ->
|
|
|
97
97
|
on_param=lambda n: Origin(params=frozenset({n})),
|
|
98
98
|
on_global=lambda n: Origin(globals_=frozenset({n})),
|
|
99
99
|
on_constant=lambda: _LITERAL,
|
|
100
|
-
on_cycle=Origin, # assignment cycle
|
|
100
|
+
on_cycle=Origin, # assignment cycle, no new information
|
|
101
101
|
)
|
|
102
102
|
|
|
103
103
|
|
|
@@ -182,7 +182,7 @@ def reduce_value(
|
|
|
182
182
|
if isinstance(expr, (ast.List, ast.Tuple, ast.Set)):
|
|
183
183
|
return combine([recurse(e) for e in expr.elts])
|
|
184
184
|
if isinstance(expr, ast.Name):
|
|
185
|
-
if expr.id in seen: # assignment cycle
|
|
185
|
+
if expr.id in seen: # assignment cycle, stop
|
|
186
186
|
return on_cycle()
|
|
187
187
|
if expr.id in params:
|
|
188
188
|
return on_param(expr.id)
|
|
@@ -6,7 +6,7 @@ sanitizer makes it SANITIZED (taint stops), a propagator carries taint through t
|
|
|
6
6
|
the result, and a trusted origin or a literal is clean.
|
|
7
7
|
|
|
8
8
|
The point is to let a later layer (P1-04) downgrade a taint finding only when the
|
|
9
|
-
value is *provably* not attacker-controlled
|
|
9
|
+
value is *provably* not attacker-controlled (``classification in SAFE``), so
|
|
10
10
|
recall is preserved: anything uncertain is UNKNOWN or PARAM, never quietly safe.
|
|
11
11
|
|
|
12
12
|
Two documented precision leans: a bare module-global name (e.g. ``STATIC_DIR``)
|
|
@@ -42,7 +42,7 @@ from codejury.resources import TAINT_FILE
|
|
|
42
42
|
|
|
43
43
|
class Taint(str, Enum):
|
|
44
44
|
EXTERNAL = "external" # derives from an attacker source, not sanitized
|
|
45
|
-
UNKNOWN = "unknown" # an unknown call / access
|
|
45
|
+
UNKNOWN = "unknown" # an unknown call / access; cannot prove either way
|
|
46
46
|
PARAM = "param" # depends on a parameter; resolve at the call site (cross-file)
|
|
47
47
|
SANITIZED = "sanitized" # had an external component, but a sanitizer neutralized it
|
|
48
48
|
TRUSTED = "trusted" # operator/config/global origin
|
|
@@ -131,14 +131,14 @@ def taint_of(
|
|
|
131
131
|
return Taint.TRUSTED
|
|
132
132
|
if _callee_in(node, vocab.propagators) or _callee_in(node, vocab.safe_sinks):
|
|
133
133
|
return _combine([recurse(a) for a in node.args])
|
|
134
|
-
return Taint.UNKNOWN # unknown call
|
|
134
|
+
return Taint.UNKNOWN # unknown call; a cross-file hop may resolve it later
|
|
135
135
|
if isinstance(node, (ast.Attribute, ast.Subscript)):
|
|
136
136
|
path = access_path(node)
|
|
137
137
|
if path and _access_in(path, vocab.sources):
|
|
138
138
|
return Taint.EXTERNAL
|
|
139
139
|
if path and _access_in(path, vocab.trusted):
|
|
140
140
|
return Taint.TRUSTED
|
|
141
|
-
if access_root(node) in params: # attribute of a parameter
|
|
141
|
+
if access_root(node) in params: # attribute of a parameter; resolve at the call site
|
|
142
142
|
return on_param(access_root(node))
|
|
143
143
|
return Taint.UNKNOWN # unknown object attribute (e.g. self.x): not provably safe
|
|
144
144
|
return Taint.UNKNOWN
|
|
@@ -150,7 +150,7 @@ def taint_of(
|
|
|
150
150
|
combine=_combine,
|
|
151
151
|
leaf=leaf,
|
|
152
152
|
on_param=on_param,
|
|
153
|
-
on_global=lambda n: Taint.TRUSTED, # free module global / builtin
|
|
153
|
+
on_global=lambda n: Taint.TRUSTED, # free module global / builtin; conventionally a constant
|
|
154
154
|
on_constant=lambda: Taint.CONSTANT,
|
|
155
155
|
on_cycle=lambda: Taint.CONSTANT, # assignment cycle: no new information
|
|
156
156
|
)
|
|
@@ -261,7 +261,7 @@ def worst_sink_taint(content: str, files: dict[str, str], vocab: TaintVocab) ->
|
|
|
261
261
|
A "potential sink" is any call that is not a safe sink, sanitizer, or
|
|
262
262
|
propagator (those are not where injection happens). Each such call's argument
|
|
263
263
|
taint is classified with the cross-file resolver. A safe sink that consumes
|
|
264
|
-
tainted data (e.g. ``json.loads(request.data)``) contributes SANITIZED
|
|
264
|
+
tainted data (e.g. ``json.loads(request.data)``) contributes SANITIZED: the
|
|
265
265
|
data was handled safely. The worst contribution is returned.
|
|
266
266
|
|
|
267
267
|
``Taint.UNKNOWN`` when no inspectable sink is found (the artifact may still be
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Assembly
|
|
1
|
+
"""Assembly: build an orchestration from a strategy name and run it over a source.
|
|
2
2
|
|
|
3
3
|
Shared by the CLI and the task layer so the "which agents + which orchestrator"
|
|
4
4
|
mapping and the per-artifact run loop live in one place.
|
|
@@ -88,7 +88,7 @@ def build_orchestration(
|
|
|
88
88
|
def provider_tag(provider: Provider) -> str:
|
|
89
89
|
"""A stable short name for a provider (unwrapping RetryProvider) for cache keys,
|
|
90
90
|
so two providers that accept the same model string do not share cached verdicts."""
|
|
91
|
-
return type(getattr(provider, "
|
|
91
|
+
return type(getattr(provider, "inner", provider)).__name__
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
def orchestration_descriptor(provider: Provider, strategy: str, model: str, max_tokens: int) -> str:
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
"""Diff baseline
|
|
1
|
+
"""Diff baseline: report only findings new since a stored baseline report.
|
|
2
2
|
|
|
3
|
-
The keystone for PR-time noise control: run against a saved baseline report
|
|
4
|
-
target branch's findings
|
|
3
|
+
The keystone for PR-time noise control: run against a saved baseline report, the
|
|
4
|
+
target branch's findings, and keep only the problem observations whose
|
|
5
5
|
fingerprint is absent from the baseline, so a review shows what this change
|
|
6
|
-
introduced
|
|
6
|
+
introduced rather than the codebase's pre-existing findings. Paired with --fail-on,
|
|
7
7
|
CI then gates on new issues only.
|
|
8
8
|
|
|
9
|
-
The fingerprint is line-number-tolerant
|
|
9
|
+
The fingerprint is line-number-tolerant because lines shift between versions: it keys
|
|
10
10
|
on the capability, the kind/severity/status, the matched patterns, and the
|
|
11
|
-
normalized evidence snippet
|
|
12
|
-
|
|
13
|
-
NOT_PRESENT verdicts and concessions are always kept.
|
|
11
|
+
normalized evidence snippet, never the line number. Only problem observations,
|
|
12
|
+
namely Findings and VULNERABLE/PARTIAL Verdicts, are compared and dropped; SECURE
|
|
13
|
+
and NOT_PRESENT verdicts and concessions are always kept.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
-
from codejury.domain.observation import Concession, Finding, Observation, Verdict
|
|
18
|
+
from codejury.domain.observation import Concession, Finding, Observation, Verdict, is_problem
|
|
19
19
|
from codejury.domain.result import AnalysisResult
|
|
20
20
|
|
|
21
21
|
Results = list[tuple[str, AnalysisResult]]
|
|
22
22
|
|
|
23
|
-
_PROBLEM_STATUSES = ("VULNERABLE", "PARTIAL")
|
|
24
|
-
|
|
25
23
|
|
|
26
24
|
def finding_key(o: Observation) -> tuple:
|
|
27
25
|
"""A location-tolerant fingerprint for matching a finding across versions."""
|
|
@@ -39,13 +37,13 @@ def filter_new(results: Results, baseline: Results) -> tuple[Results, int]:
|
|
|
39
37
|
|
|
40
38
|
Returns (filtered_results, dropped_count). Non-problem observations are kept.
|
|
41
39
|
"""
|
|
42
|
-
seen = {finding_key(o) for _, r in baseline for o in r.observations if
|
|
40
|
+
seen = {finding_key(o) for _, r in baseline for o in r.observations if is_problem(o)}
|
|
43
41
|
filtered: Results = []
|
|
44
42
|
dropped = 0
|
|
45
43
|
for path, result in results:
|
|
46
44
|
kept: list[Observation] = []
|
|
47
45
|
for o in result.observations:
|
|
48
|
-
if
|
|
46
|
+
if is_problem(o) and finding_key(o) in seen:
|
|
49
47
|
dropped += 1
|
|
50
48
|
else:
|
|
51
49
|
kept.append(o)
|
|
@@ -53,10 +51,8 @@ def filter_new(results: Results, baseline: Results) -> tuple[Results, int]:
|
|
|
53
51
|
return filtered, dropped
|
|
54
52
|
|
|
55
53
|
|
|
56
|
-
def _is_problem(o: Observation) -> bool:
|
|
57
|
-
return isinstance(o, Finding) or (isinstance(o, Verdict) and o.status in _PROBLEM_STATUSES)
|
|
58
|
-
|
|
59
|
-
|
|
60
54
|
def _evidence_sig(o: Observation) -> str:
|
|
55
|
+
# fingerprints only the first evidence snippet, enough to distinguish findings
|
|
56
|
+
# in practice; two findings differing only in a later snippet would collide.
|
|
61
57
|
evidence = getattr(o, "evidence", [])
|
|
62
58
|
return " ".join(evidence[0].code.split()) if evidence and evidence[0].code else ""
|
|
@@ -280,6 +280,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
280
280
|
scan_p.add_argument("--no-suppress", action="store_true", help="disable the known-noise suppression filter")
|
|
281
281
|
scan_p.add_argument("--no-cache", action="store_true", help="bypass the verdict cache (always re-query the model)")
|
|
282
282
|
scan_p.add_argument("--baseline", default=None, help="a prior JSON report; report only findings new since it")
|
|
283
|
+
scan_p.add_argument("--retries", type=int, default=0, help="provider retry attempts on failure")
|
|
283
284
|
scan_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")
|
|
284
285
|
|
|
285
286
|
run_p = sub.add_parser("run", help="run a named task preset against a unified diff")
|
|
@@ -301,9 +302,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
301
302
|
eval_p.add_argument("--model", default=DEFAULT_MODEL)
|
|
302
303
|
eval_p.add_argument("--api-base", default=DEFAULT_API_BASE, help="provider base URL (env: CODEJURY_API_BASE)")
|
|
303
304
|
eval_p.add_argument("--api-key", default=DEFAULT_API_KEY, help="provider API key (env: CODEJURY_API_KEY)")
|
|
305
|
+
eval_p.add_argument("--retries", type=int, default=0, help="provider retry attempts on failure")
|
|
304
306
|
|
|
305
307
|
args = parser.parse_args(argv)
|
|
308
|
+
try:
|
|
309
|
+
return _dispatch(args, parser)
|
|
310
|
+
except Exception as exc:
|
|
311
|
+
# expected failures (missing diff file, provider auth, malformed input) become
|
|
312
|
+
# one stderr line, not a traceback. eval's own message is preserved by command name.
|
|
313
|
+
print(f"{args.command or 'codejury'} failed: {exc}", file=sys.stderr)
|
|
314
|
+
return 1
|
|
306
315
|
|
|
316
|
+
|
|
317
|
+
def _dispatch(args, parser) -> int:
|
|
307
318
|
if args.command == "audit":
|
|
308
319
|
results = audit(
|
|
309
320
|
_read_diff(args.diff),
|
|
@@ -326,12 +337,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
326
337
|
capabilities = load_capabilities(args.capabilities)
|
|
327
338
|
if args.only:
|
|
328
339
|
wanted = {x.strip() for x in args.only.split(",")}
|
|
340
|
+
unknown = wanted - {c.id for c in capabilities}
|
|
341
|
+
if unknown:
|
|
342
|
+
print(f"warning: --only names unknown capability id(s): {', '.join(sorted(unknown))}", file=sys.stderr)
|
|
329
343
|
capabilities = [c for c in capabilities if c.id in wanted]
|
|
330
344
|
extensions = tuple(e if e.startswith(".") else "." + e for e in args.ext.split(","))
|
|
331
345
|
results = scan(
|
|
332
346
|
args.directory,
|
|
333
347
|
capabilities,
|
|
334
|
-
provider=make_provider(args.provider, api_key=args.api_key, api_base=args.api_base),
|
|
348
|
+
provider=make_provider(args.provider, api_key=args.api_key, api_base=args.api_base, retries=args.retries),
|
|
335
349
|
model=args.model,
|
|
336
350
|
max_tokens=args.max_tokens,
|
|
337
351
|
strategy=args.orchestrator,
|
|
@@ -359,19 +373,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
359
373
|
return _gate_exit(results, args.fail_on)
|
|
360
374
|
|
|
361
375
|
if args.command == "eval":
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
)
|
|
370
|
-
except Exception as exc:
|
|
371
|
-
# e.g. a missing API key surfaces as a provider auth error -- report it
|
|
372
|
-
# as one line, not a traceback (audit gets this via the orchestrator).
|
|
373
|
-
print(f"eval failed: {exc}")
|
|
374
|
-
return 1
|
|
376
|
+
report = evaluate(
|
|
377
|
+
load_cases(args.dataset, split=args.split),
|
|
378
|
+
load_capabilities(args.capabilities),
|
|
379
|
+
provider=make_provider(args.provider, api_key=args.api_key, api_base=args.api_base, retries=args.retries),
|
|
380
|
+
model=args.model,
|
|
381
|
+
strategy=args.orchestrator,
|
|
382
|
+
)
|
|
375
383
|
print(json.dumps(report.to_dict(), indent=2) if args.fmt == "json" else _render_eval(report))
|
|
376
384
|
return 0
|
|
377
385
|
|