controlzero 1.13.8__tar.gz → 1.13.9__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.
- {controlzero-1.13.8 → controlzero-1.13.9}/CHANGELOG.md +46 -2
- {controlzero-1.13.8 → controlzero-1.13.9}/PKG-INFO +1 -1
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/__init__.py +1 -1
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/_messages.py +45 -0
- controlzero-1.13.9/controlzero/_internal/dlp_remediation.py +245 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/enforcer.py +25 -1
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/enforcement.py +24 -1
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/claude_code.py +21 -0
- controlzero-1.13.9/controlzero/cli/hosts/kiro.py +364 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/kiro_adapter.py +26 -1
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/main.py +273 -8
- {controlzero-1.13.8 → controlzero-1.13.9}/pyproject.toml +1 -1
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/kiro.json +4 -2
- controlzero-1.13.9/tests/test_cli_coverage.py +62 -0
- controlzero-1.13.9/tests/test_dlp_agent_remediation_1938.py +366 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_adapter.py +196 -1
- controlzero-1.13.9/tests/test_kiro_agent_harness_routing.py +196 -0
- controlzero-1.13.9/tests/test_kiro_ide_v2_hooks_1944.py +480 -0
- controlzero-1.13.9/tests/test_kiro_version_schemes.py +123 -0
- controlzero-1.13.8/controlzero/cli/hosts/kiro.py +0 -181
- {controlzero-1.13.8 → controlzero-1.13.9}/.gitignore +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/Dockerfile.test +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/LICENSE +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/README.md +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/__main__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/condition_ops.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/io_capture_producer.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/source_labels.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/_internal/types.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/audit_local.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/audit_remote.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/canonical.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/__main__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/agent_config.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/console.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/exec_cmd.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hook_entry.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/cursor.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/cursor_cli.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/output.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/setup_wizard.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/cli/update_cmd.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/client.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/device.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/enrollment.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/error_codes.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/errors.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hitl/status.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hook_integrity.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/google.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/layout_migration.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/policy_loader.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/secrets/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/secrets/reference.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/secrets/resolver.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/secrets/secretstr.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_state.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/tamper.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/controlzero/tracecontext.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/examples/hello_world.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/ruff.toml +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/conftest.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/antigravity.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/claude_code.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/codex_cli.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/cursor.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/fixtures/adapter-contracts/gemini_cli.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/integrations/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/integrations/test_google.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/__init__.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/conftest.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_action_aliases.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_adapter_contracts.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_adapter_interface.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_agy_docs_no_exit2_1373.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_adapter.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_envelope_parity.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_ga_blockers_1248.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_antigravity_tool_vocab_1303.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_api_url_config.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_project_id_attribution_pr2.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_remote.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_remote_io_capture.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_bundle_cache_freshness_1303.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_capability_matrix_doc.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_hook.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_hook_entry.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_init.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_tail.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_test.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cli_validate.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_condition_operators_1495.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_conditions.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_config_format_parity_1303.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_conformance.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_console.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_credential_hook.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cross_adapter_shell_enforcement.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_cursor_adapter.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dashboard_shell_template_1904.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_default_action.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_deny_rule_reconciliation_1011.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_deny_wins_default_catchall_1620.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_device.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_mask.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_mask_hosts_1938.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_origin_contract.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_scan_coverage_1927_1931.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_doctor.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_enrollment.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_error_codes.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_failopen_1303.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_git_target_1734.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_glob_matching.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_command_pinning_1705.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_integrity.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_mask_failclosed.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hook_warn_surfaces_reason_1903.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hosts_adapter.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_install_api_url.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_install_hooks.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_io_capture_producer.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_cli_e2e.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_cli_v210_hooks.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_install.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_kiro_install_standardisation.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_localized_reason.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_log_rotation.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_migrate.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_part3_active_policy_count_1303.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_per_client_isolation_1011.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_load_failclosed_f3.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_settings.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_pr4_install_messaging.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_pr4_output.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_pr4_status.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_probe_audit_isolation.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_quarantine.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_reason_code.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_reason_code_hitl_grant_1596.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_refresh.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_secrets.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_secrets_reference_resolver.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_setup_wizard.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_shell_semantic_class.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_source_labels.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tamper.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tls_ca_bundle.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_tracecontext.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_update_cmd.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_upgrade_nudge.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tests/test_version_aware_fail_closed.py +0 -0
- {controlzero-1.13.8 → controlzero-1.13.9}/tools/cz-kiro-adapter +0 -0
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.13.
|
|
3
|
+
## 1.13.9 (2026-07-28): Kiro IDE hooks execute on 1.0.x and stay in the correct project
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Kiro IDE hooks no longer appear enabled while silently executing on
|
|
8
|
+
nothing on Kiro 1.0.x (#1952).** The installer now emits `preToolUse` and
|
|
9
|
+
`promptSubmit` hooks in the `.json` format read by Kiro's 1.0 hook loader,
|
|
10
|
+
while retaining the legacy hook files needed by older builds. It does not
|
|
11
|
+
dual-emit `fileEdited`, because both loaders can execute that event and
|
|
12
|
+
doing so would duplicate scans and audit rows. `promptSubmit` now actually
|
|
13
|
+
runs on current Kiro; `preToolUse` is installed in the working format but
|
|
14
|
+
remains disabled by its existing opt-in flag.
|
|
15
|
+
- **Kiro payloads can no longer be claimed by the Claude Code adapter and
|
|
16
|
+
evaluated under the wrong project and DLP profile (#1945).** Kiro IDE 1.0+
|
|
17
|
+
and Kiro CLI v3 use PascalCase agent-harness events that overlap with Claude
|
|
18
|
+
Code. Kiro-specific payload signals now win routing, so attribution, API-key
|
|
19
|
+
selection, policy, and DLP configuration stay with the Kiro integration
|
|
20
|
+
instead of silently crossing tenants.
|
|
21
|
+
- **An inherited `CONTROLZERO_CLIENT` can no longer redirect a Kiro IDE call
|
|
22
|
+
into another host's configuration (#1951).** The Kiro shim now pins its
|
|
23
|
+
client identity rather than preserving a value inherited from the launching
|
|
24
|
+
environment, preventing the same silent wrong-project and wrong-DLP-profile
|
|
25
|
+
outcome.
|
|
26
|
+
- **A DLP mask match on a host that cannot rewrite tool input now tells the
|
|
27
|
+
agent what to fix (#1946).** The denial identifies the affected argument and
|
|
28
|
+
detector type without echoing the sensitive value, so the agent can retry
|
|
29
|
+
with the secret removed instead of dead-ending on a generic unsupported-mask
|
|
30
|
+
message.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`controlzero coverage` is generated from the adapters' declared
|
|
35
|
+
capabilities (#1947).** The CLI now exposes each host's enforcement tier,
|
|
36
|
+
deciding and observation events, argument delivery, rewrite support, and
|
|
37
|
+
known gaps from the same data used by adapter contracts.
|
|
38
|
+
|
|
39
|
+
### Kiro IDE enforcement status
|
|
40
|
+
|
|
41
|
+
Kiro IDE remains `tier=observe`; this release does **not** claim deterministic
|
|
42
|
+
enforcement. It makes the enabled `promptSubmit` hook execute on Kiro 1.0.x and
|
|
43
|
+
puts `preToolUse` one enable flag away from execution. Until that flag is
|
|
44
|
+
enabled and the deterministic block path is verified end to end, the SDK's
|
|
45
|
+
coverage declaration intentionally remains observe-only.
|
|
46
|
+
|
|
47
|
+
## 1.13.8 (2026-07-27): warn decisions reach the agent + Kiro IDE adapter unblocked (P0)
|
|
4
48
|
|
|
5
49
|
### Added
|
|
6
50
|
|
|
@@ -214,7 +258,7 @@
|
|
|
214
258
|
"cannot return rewritten tool arguments". **That was wrong**, and correcting
|
|
215
259
|
it is why this paragraph exists (#202 no-false-claims):
|
|
216
260
|
|
|
217
|
-
| Host | Can rewrite tool input | Masking behaviour in
|
|
261
|
+
| Host | Can rewrite tool input | Masking behaviour in that release |
|
|
218
262
|
| --- | --- | --- |
|
|
219
263
|
| Claude Code | Yes -- `hookSpecificOutput.updatedInput` | **Masks and proceeds** |
|
|
220
264
|
| Gemini CLI | Yes -- `hookSpecificOutput.tool_input` (BeforeTool) | **Masks and proceeds** |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.9
|
|
4
4
|
Summary: AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup.
|
|
5
5
|
Project-URL: Homepage, https://controlzero.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.controlzero.ai
|
|
@@ -131,6 +131,51 @@ SYSTEM_MESSAGES: dict[str, dict[str, str]] = {
|
|
|
131
131
|
"마스킹되지 않은 값을 전달하는 대신 차단합니다(기본 차단)."
|
|
132
132
|
),
|
|
133
133
|
},
|
|
134
|
+
# DLP mask on a host whose hook protocol cannot accept rewritten tool input
|
|
135
|
+
# (every host except Claude Code + Gemini CLI). The mask escalates to a
|
|
136
|
+
# deny, and THIS is the text the model reads on that deny -- so it is
|
|
137
|
+
# written as an INSTRUCTION to the agent, not as a status line for a human.
|
|
138
|
+
# Goal: the agent re-issues the same call with the sensitive value removed,
|
|
139
|
+
# so the user's task completes in one extra round-trip instead of dead-
|
|
140
|
+
# ending. See controlzero/_internal/dlp_remediation.py.
|
|
141
|
+
#
|
|
142
|
+
# {findings} is TYPE + LOCATION only. It NEVER contains the detected value:
|
|
143
|
+
# echoing the secret into the model's context is the exact disclosure DLP
|
|
144
|
+
# exists to prevent (#1881, "PII never in cleartext").
|
|
145
|
+
#
|
|
146
|
+
# NO ``ko`` ENTRY, deliberately. The Korean pack is a professional draft
|
|
147
|
+
# pending customer review (see the module docstring), and an unreviewed
|
|
148
|
+
# machine translation of a SECURITY INSTRUCTION is worse than the English
|
|
149
|
+
# fallback -- a mistranslated "do not re-send the value" is a leak. Korean
|
|
150
|
+
# locales fall back to ``en``, which is exactly what they get today from the
|
|
151
|
+
# hard-coded English this replaces, so there is no regression to fix later.
|
|
152
|
+
"DLP_MASK_UNSUPPORTED:agent": {
|
|
153
|
+
"en": (
|
|
154
|
+
"Blocked: the tool arguments contain sensitive data, and this "
|
|
155
|
+
"agent host cannot accept redacted arguments, so the call was "
|
|
156
|
+
"blocked instead of masked. Findings: {findings}. To proceed, "
|
|
157
|
+
"retry the SAME call with the sensitive value removed -- refer to "
|
|
158
|
+
"it indirectly (an environment variable the tool expands, a "
|
|
159
|
+
"secret-manager reference, or a file path the tool reads) instead "
|
|
160
|
+
"of inlining it. Do not print, echo, restate, or re-send the value "
|
|
161
|
+
"in any form, including in a summary or an explanation."
|
|
162
|
+
),
|
|
163
|
+
},
|
|
164
|
+
# Location-free fallback: a mask matched on the flattened argument text but
|
|
165
|
+
# no single argument leaf carries it (a pattern spanning the boundary
|
|
166
|
+
# between two concatenated values). Still actionable, just less precise.
|
|
167
|
+
"DLP_MASK_UNSUPPORTED:base": {
|
|
168
|
+
"en": (
|
|
169
|
+
"Blocked: the tool arguments contain sensitive data, and this "
|
|
170
|
+
"agent host cannot accept redacted arguments, so the call was "
|
|
171
|
+
"blocked instead of masked. To proceed, retry the SAME call with "
|
|
172
|
+
"the sensitive value removed -- refer to it indirectly (an "
|
|
173
|
+
"environment variable the tool expands, a secret-manager "
|
|
174
|
+
"reference, or a file path the tool reads) instead of inlining it. "
|
|
175
|
+
"Do not print, echo, restate, or re-send the value in any form, "
|
|
176
|
+
"including in a summary or an explanation."
|
|
177
|
+
),
|
|
178
|
+
},
|
|
134
179
|
# Tamper / quarantine (constructed in client.py + cli/main.py).
|
|
135
180
|
"BUNDLE_TAMPERED": {
|
|
136
181
|
"en": (
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Agent-actionable denial text for a DLP finding: TYPE + LOCATION, never VALUE.
|
|
2
|
+
|
|
3
|
+
Why this module exists
|
|
4
|
+
----------------------
|
|
5
|
+
DLP can MASK a secret out of tool arguments, but only on hosts whose hook
|
|
6
|
+
protocol accepts REWRITTEN tool input. Today that is Claude Code and Gemini CLI
|
|
7
|
+
only (``CoverageDecl.can_rewrite_tool_input``); every other host -- Codex CLI,
|
|
8
|
+
Cursor IDE, Cursor CLI, Kiro CLI, Kiro IDE, Antigravity, and the unknown-host
|
|
9
|
+
fallback -- escalates a mask-rule match to a hard DENY.
|
|
10
|
+
|
|
11
|
+
That deny used to say, in full:
|
|
12
|
+
|
|
13
|
+
DLP mask matched but this host cannot rewrite arguments
|
|
14
|
+
|
|
15
|
+
which is true, actionable for nobody, and addressed to the wrong audience. The
|
|
16
|
+
model that issued the call reads it, cannot tell WHICH argument tripped or WHAT
|
|
17
|
+
it tripped on, and typically retries the identical call -- so the user's task
|
|
18
|
+
dead-ends instead of completing.
|
|
19
|
+
|
|
20
|
+
An **agent-actionable denial** closes that gap with no vendor change and no new
|
|
21
|
+
config: the deny reason itself becomes an instruction precise enough that the
|
|
22
|
+
model re-issues the SAME call with the sensitive value removed. One extra
|
|
23
|
+
round-trip instead of a dead end. It works on every non-rewriting host because
|
|
24
|
+
every one of them already routes ``decision.reason`` to a MODEL-visible field:
|
|
25
|
+
|
|
26
|
+
=============== ==========================================================
|
|
27
|
+
host model-visible field carrying ``decision.reason``
|
|
28
|
+
=============== ==========================================================
|
|
29
|
+
codex_cli ``hookSpecificOutput.permissionDecisionReason`` (+ stderr)
|
|
30
|
+
cursor_ide ``agent_message``
|
|
31
|
+
cursor_cli ``agent_message``
|
|
32
|
+
kiro_cli envelope ``reason`` + stderr (exit 2 sends stderr to the model)
|
|
33
|
+
kiro_ide envelope ``reason`` (v1 wraps hook output in ``<HOOK_INSTRUCTION>``)
|
|
34
|
+
antigravity envelope ``reason``
|
|
35
|
+
unknown envelope ``reason``
|
|
36
|
+
=============== ==========================================================
|
|
37
|
+
|
|
38
|
+
So NO adapter needed rewiring for this. The only thing that was missing was
|
|
39
|
+
text worth putting in that channel.
|
|
40
|
+
|
|
41
|
+
THE HARD CONSTRAINT
|
|
42
|
+
-------------------
|
|
43
|
+
**The message must NEVER contain the detected value.** Writing "found AWS key
|
|
44
|
+
AKIA... at offset 34" would echo the secret straight into the model's context
|
|
45
|
+
window -- which is the exact disclosure DLP exists to prevent, and a breach of
|
|
46
|
+
the #1881 "PII never in cleartext" mandate. A deny that leaks the secret to the
|
|
47
|
+
model is strictly worse than the uninformative deny it replaced.
|
|
48
|
+
|
|
49
|
+
Findings are therefore described by TYPE (the rule's name + category, which are
|
|
50
|
+
properties of the RULE definition, never of the matched bytes) and by LOCATION
|
|
51
|
+
(the argument path). Nothing quotes, prefixes, suffixes, hashes-with-a-
|
|
52
|
+
recoverable-prefix, or partially redacts the value; the value never enters this
|
|
53
|
+
module's output by CONSTRUCTION, not by filtering -- there is no code path here
|
|
54
|
+
that reads ``DLPMatch.matched_text``.
|
|
55
|
+
|
|
56
|
+
The one value-derived input is an argument PATH SEGMENT: a dict KEY is attacker/
|
|
57
|
+
model-controlled and could itself BE the secret (``{"AKIA...": "v"}``). That is
|
|
58
|
+
the single echo route, and :func:`_safe_segment` closes it by scanning every
|
|
59
|
+
segment and replacing any that matches a DLP rule with a fixed placeholder. It
|
|
60
|
+
errs toward redacting (an all-caps key like ``ACCESSKEYID`` trips the SWIFT/BIC
|
|
61
|
+
builtin and is redacted); losing a little location precision is the cheap side
|
|
62
|
+
of that trade.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
from __future__ import annotations
|
|
66
|
+
|
|
67
|
+
from dataclasses import dataclass
|
|
68
|
+
from typing import Any, Optional
|
|
69
|
+
|
|
70
|
+
from controlzero._internal._messages import system_message
|
|
71
|
+
|
|
72
|
+
#: Placeholder for an argument-path segment that is ITSELF sensitive. A dict key
|
|
73
|
+
#: is model-controlled, so ``{"AKIA...": "v"}`` would otherwise route the secret
|
|
74
|
+
#: into the location clause. Value-free and fixed, exactly like the mask
|
|
75
|
+
#: placeholder, so it can never carry a fragment of the original bytes.
|
|
76
|
+
REDACTED_SEGMENT = "<redacted-key>"
|
|
77
|
+
|
|
78
|
+
#: Path segment for a value that is not reachable by a name (a bare scalar
|
|
79
|
+
#: passed as the whole argument object, or a leaf whose parents are all lists).
|
|
80
|
+
ROOT_SEGMENT = "<tool arguments>"
|
|
81
|
+
|
|
82
|
+
#: Cap on how many findings are enumerated. A pathological call can match
|
|
83
|
+
#: hundreds of spans; an unbounded list would bury the instruction it exists to
|
|
84
|
+
#: deliver and burn the model's context. The remainder is reported as a count.
|
|
85
|
+
MAX_LISTED_FINDINGS = 5
|
|
86
|
+
|
|
87
|
+
#: Cap on a rendered argument path. Deeply nested payloads produce long paths
|
|
88
|
+
#: that add no actionable precision past a point.
|
|
89
|
+
MAX_PATH_CHARS = 120
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class ArgFinding:
|
|
94
|
+
"""One sensitive span, described by WHERE it is and WHAT KIND it is.
|
|
95
|
+
|
|
96
|
+
Deliberately carries no field that could hold the matched bytes. Adding one
|
|
97
|
+
would defeat the whole point of this module, so the dataclass shape is the
|
|
98
|
+
enforcement: there is nowhere for a value to live.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
arg_path: str
|
|
102
|
+
rule_name: str
|
|
103
|
+
category: str
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _safe_segment(scanner: Any, segment: str) -> str:
|
|
107
|
+
"""Return ``segment`` unless the segment TEXT is itself sensitive.
|
|
108
|
+
|
|
109
|
+
A dict key comes from the model's own tool call, so it can be the secret
|
|
110
|
+
(``{"ghp_...": "note"}``). Scanning the segment and substituting a fixed
|
|
111
|
+
placeholder is what stops the location clause from becoming the leak.
|
|
112
|
+
"""
|
|
113
|
+
if not segment:
|
|
114
|
+
return ROOT_SEGMENT
|
|
115
|
+
try:
|
|
116
|
+
if scanner.scan(segment):
|
|
117
|
+
return REDACTED_SEGMENT
|
|
118
|
+
except Exception: # pragma: no cover - a scanner fault must not leak
|
|
119
|
+
# Fail toward redaction: if we cannot prove the segment is clean, do not
|
|
120
|
+
# print it. This branch mirrors the module-wide asymmetry -- an
|
|
121
|
+
# imprecise location costs a little usefulness, an echoed secret costs
|
|
122
|
+
# everything.
|
|
123
|
+
return REDACTED_SEGMENT
|
|
124
|
+
return segment
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _join(parent: str, child: str) -> str:
|
|
128
|
+
return f"{parent}.{child}" if parent else child
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _walk(
|
|
132
|
+
scanner: Any,
|
|
133
|
+
obj: Any,
|
|
134
|
+
path: str,
|
|
135
|
+
actions: frozenset,
|
|
136
|
+
out: list,
|
|
137
|
+
seen: set,
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Collect ``ArgFinding`` per LEAF so each finding has a real location.
|
|
140
|
+
|
|
141
|
+
Per-leaf, not over the flattened blob ``extract_text_from_args`` produces:
|
|
142
|
+
the flat text's offsets do not map back to an argument, and "somewhere in
|
|
143
|
+
your arguments" is not a location the model can act on.
|
|
144
|
+
"""
|
|
145
|
+
if isinstance(obj, dict):
|
|
146
|
+
for key, value in obj.items():
|
|
147
|
+
_walk(scanner, value, _join(path, _safe_segment(scanner, str(key))), actions, out, seen)
|
|
148
|
+
return
|
|
149
|
+
if isinstance(obj, (list, tuple)):
|
|
150
|
+
for index, item in enumerate(obj):
|
|
151
|
+
_walk(scanner, item, f"{path}[{index}]" if path else f"[{index}]", actions, out, seen)
|
|
152
|
+
return
|
|
153
|
+
if obj is None:
|
|
154
|
+
return
|
|
155
|
+
if isinstance(obj, str):
|
|
156
|
+
text = obj
|
|
157
|
+
elif isinstance(obj, (int, float, bool)):
|
|
158
|
+
# Scanned as text for the same reason the masker stringifies scalars:
|
|
159
|
+
# a Korean card / RRN / account number frequently arrives as a JSON
|
|
160
|
+
# NUMBER, and skipping it here would report no location for a finding
|
|
161
|
+
# the engine already decided to block on.
|
|
162
|
+
text = str(obj)
|
|
163
|
+
else:
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
location = path or ROOT_SEGMENT
|
|
167
|
+
if len(location) > MAX_PATH_CHARS:
|
|
168
|
+
location = location[:MAX_PATH_CHARS] + "..."
|
|
169
|
+
|
|
170
|
+
for match in scanner.scan(text):
|
|
171
|
+
if match.action not in actions:
|
|
172
|
+
continue
|
|
173
|
+
# One line per (location, rule) pair. Ten hits of the same rule in one
|
|
174
|
+
# argument is one instruction, not ten.
|
|
175
|
+
key = (location, match.rule_id)
|
|
176
|
+
if key in seen:
|
|
177
|
+
continue
|
|
178
|
+
seen.add(key)
|
|
179
|
+
out.append(
|
|
180
|
+
ArgFinding(
|
|
181
|
+
arg_path=location,
|
|
182
|
+
rule_name=match.rule_name,
|
|
183
|
+
category=match.category,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def locate_findings(
|
|
189
|
+
scanner: Any,
|
|
190
|
+
args: Any,
|
|
191
|
+
*,
|
|
192
|
+
actions: frozenset = frozenset({"mask"}),
|
|
193
|
+
) -> list:
|
|
194
|
+
"""Describe every ``actions`` match in ``args`` by location + type.
|
|
195
|
+
|
|
196
|
+
``actions`` is a NAMED POSITIVE SET, not a negation of something else, per
|
|
197
|
+
the CLAUDE.md controls mandate: a future DLP action lands OUTSIDE it and is
|
|
198
|
+
simply not described, rather than being silently swept in by an ``!=``.
|
|
199
|
+
"""
|
|
200
|
+
out: list = []
|
|
201
|
+
seen: set = set()
|
|
202
|
+
_walk(scanner, args, "", actions, out, seen)
|
|
203
|
+
return out
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _describe(findings: list) -> str:
|
|
207
|
+
"""Render the findings clause. TYPE + LOCATION only -- never the value."""
|
|
208
|
+
clauses = [
|
|
209
|
+
f'argument "{f.arg_path}" contains a value matching the '
|
|
210
|
+
f'"{f.rule_name}" {f.category} detector'
|
|
211
|
+
for f in findings[:MAX_LISTED_FINDINGS]
|
|
212
|
+
]
|
|
213
|
+
overflow = len(findings) - len(clauses)
|
|
214
|
+
if overflow > 0:
|
|
215
|
+
clauses.append(f"and {overflow} further match(es) in other arguments")
|
|
216
|
+
return "; ".join(clauses)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def agent_remediation_message(
|
|
220
|
+
scanner: Any,
|
|
221
|
+
args: Any,
|
|
222
|
+
*,
|
|
223
|
+
actions: frozenset = frozenset({"mask"}),
|
|
224
|
+
locale: Optional[str] = None,
|
|
225
|
+
) -> str:
|
|
226
|
+
"""The deny reason handed to the MODEL on a non-rewriting host.
|
|
227
|
+
|
|
228
|
+
THE single helper this module exists to provide. Returns text that names
|
|
229
|
+
what was found and where, and tells the agent what to do next -- and that,
|
|
230
|
+
by construction, cannot contain the detected value.
|
|
231
|
+
|
|
232
|
+
Falls back to the location-free base message when nothing could be located
|
|
233
|
+
(e.g. a match that only exists across the boundary between two concatenated
|
|
234
|
+
arguments, so no single leaf carries it). Less useful, still never wrong,
|
|
235
|
+
and never a crash on the deny path.
|
|
236
|
+
"""
|
|
237
|
+
findings = locate_findings(scanner, args, actions=actions)
|
|
238
|
+
key = "DLP_MASK_UNSUPPORTED:agent" if findings else "DLP_MASK_UNSUPPORTED:base"
|
|
239
|
+
message = system_message(key, locale, findings=_describe(findings))
|
|
240
|
+
# system_message returns None only for an unknown key. Both keys above are
|
|
241
|
+
# in the pack and the pack is pinned by tests, but the deny path must not
|
|
242
|
+
# raise, so a missing entry degrades to the legacy text rather than a
|
|
243
|
+
# traceback that would fail the hook open on hosts that read a crash as a
|
|
244
|
+
# pass.
|
|
245
|
+
return message or "DLP mask matched but this host cannot rewrite arguments"
|
|
@@ -27,6 +27,7 @@ from controlzero._internal._messages import (
|
|
|
27
27
|
resolve_locale,
|
|
28
28
|
system_message,
|
|
29
29
|
)
|
|
30
|
+
from controlzero._internal.dlp_remediation import agent_remediation_message
|
|
30
31
|
from controlzero._internal.dlp_scanner import (
|
|
31
32
|
DLPScanner,
|
|
32
33
|
extract_text_from_args,
|
|
@@ -1386,7 +1387,30 @@ class PolicyEvaluator:
|
|
|
1386
1387
|
"host_cannot_rewrite_arguments",
|
|
1387
1388
|
),
|
|
1388
1389
|
policy_id=decision.policy_id,
|
|
1389
|
-
|
|
1390
|
+
# AGENT-ACTIONABLE DENIAL. This deny's reason is routed to a
|
|
1391
|
+
# MODEL-visible field by every non-rewriting host (Codex's
|
|
1392
|
+
# permissionDecisionReason, Cursor's agent_message, the
|
|
1393
|
+
# Kiro / Antigravity / unknown envelope `reason`, plus
|
|
1394
|
+
# stderr), so it is the one channel that can get the user's
|
|
1395
|
+
# task unblocked without a vendor change: name the argument
|
|
1396
|
+
# and the detector precisely enough that the model re-issues
|
|
1397
|
+
# the SAME call with the secret removed.
|
|
1398
|
+
#
|
|
1399
|
+
# The legacy text ("DLP mask matched but this host cannot
|
|
1400
|
+
# rewrite arguments") was true and useless -- the model
|
|
1401
|
+
# could not tell WHICH argument tripped or WHAT on, so it
|
|
1402
|
+
# retried the identical call and the task dead-ended.
|
|
1403
|
+
#
|
|
1404
|
+
# TYPE + LOCATION only. The message can never carry the
|
|
1405
|
+
# detected value: echoing it here would put the secret in
|
|
1406
|
+
# the model's context window, which is the precise
|
|
1407
|
+
# disclosure this control exists to prevent. Enforced by
|
|
1408
|
+
# construction in dlp_remediation (nothing there reads
|
|
1409
|
+
# matched_text) and pinned by
|
|
1410
|
+
# tests/test_dlp_agent_remediation_1938.py.
|
|
1411
|
+
reason=agent_remediation_message(
|
|
1412
|
+
self._dlp_scanner, args, locale=self._locale
|
|
1413
|
+
),
|
|
1390
1414
|
reason_code=REASON_CODE_DLP_BLOCKED,
|
|
1391
1415
|
evaluated_rules=decision.evaluated_rules,
|
|
1392
1416
|
dlp_findings=self._dlp_scanner.get_findings_for_audit(mask_matches),
|
|
@@ -149,7 +149,30 @@ ADAPTER_CONTRACTS: dict[str, AdapterContract] = {
|
|
|
149
149
|
match_all_tokens=("*",),
|
|
150
150
|
# preToolUse matchers are canonical tool names that MUST self-match.
|
|
151
151
|
installer_matchers=("execute_bash", "fs_write", "fs_read", "use_aws"),
|
|
152
|
-
|
|
152
|
+
# This contract describes the **Kiro CLI** surface only (note
|
|
153
|
+
# ``version_command`` below is ``kiro-cli --version``); KiroIDEAdapter
|
|
154
|
+
# is versioned on a different scheme entirely and is NOT reconciled
|
|
155
|
+
# here -- see hosts/kiro.py.
|
|
156
|
+
#
|
|
157
|
+
# Was ">=1.23.0", a stale Amazon-Q-era claim: kiro-cli reported 1.x
|
|
158
|
+
# when this fixture was written (the fixture's own ``version_detect``
|
|
159
|
+
# note still said "kiro-cli is 1.x (rebranded Amazon Q), NOT 2.8.x").
|
|
160
|
+
# kiro-cli has since renumbered to 2.x -- the binary installed for
|
|
161
|
+
# verification reports ``kiro-cli 2.14.2``. The old range only kept
|
|
162
|
+
# producing a "tested" label at line ~483 by semver accident
|
|
163
|
+
# (2.14.2 >= 1.23.0), not because 1.23.0 means anything today.
|
|
164
|
+
#
|
|
165
|
+
# Floor 2.1.0: first build where preToolUse hooks fire at all
|
|
166
|
+
# (kirodotdev/Kiro#7697). Ceiling <3.0.0: bounds the 2.x binary major.
|
|
167
|
+
#
|
|
168
|
+
# CAVEAT, stated because the version range CANNOT enforce it: Kiro
|
|
169
|
+
# "CLI v3" is not a 3.x release -- it ships INSIDE the 2.x binary
|
|
170
|
+
# behind ``kiro-cli --v3`` (kiro.dev/docs/cli/v3.md: "V3 runs
|
|
171
|
+
# alongside your existing 2.x install"), and it changes the hook
|
|
172
|
+
# contract to PascalCase standalone files. So a --v3 run still reports
|
|
173
|
+
# 2.14.2 and still lands inside this range. Surface separation is done
|
|
174
|
+
# by payload signature in hosts/kiro.py, never by this string.
|
|
175
|
+
tested_agent_versions=">=2.1.0,<3.0.0",
|
|
153
176
|
sdk_min_core_version="1.9.0",
|
|
154
177
|
block_mechanism="exit_code",
|
|
155
178
|
version_command=("kiro-cli", "--version"),
|
|
@@ -45,6 +45,7 @@ from __future__ import annotations
|
|
|
45
45
|
from collections.abc import Mapping
|
|
46
46
|
|
|
47
47
|
from controlzero.cli.hosts.base import CoverageDecl, CZDecision, HostAdapter
|
|
48
|
+
from controlzero.cli.hosts.kiro import is_kiro_agent_payload
|
|
48
49
|
|
|
49
50
|
# CONTROLZERO_CLIENT values that pin THIS adapter. A non-empty pin that is NOT
|
|
50
51
|
# one of these means a different host was pinned -> this adapter must DEFER
|
|
@@ -131,6 +132,26 @@ class ClaudeCodeAdapter(HostAdapter):
|
|
|
131
132
|
if env.get(hint):
|
|
132
133
|
return True
|
|
133
134
|
|
|
135
|
+
# Disambiguation #2: Kiro IDE 1.0+ / Kiro CLI v3 run the shared Kiro
|
|
136
|
+
# Agent Server harness, which emits PascalCase ``hook_event_name``
|
|
137
|
+
# ("PreToolUse") on stdin -- the same names Claude Code uses. Claude
|
|
138
|
+
# sits FIRST in the REGISTRY, so without this defer a Kiro
|
|
139
|
+
# agent-harness call is claimed here. The block would still land (both
|
|
140
|
+
# hosts exit 2), which is what makes it dangerous: the surface looks
|
|
141
|
+
# healthy while the audit SOURCE says claude_code and the per-agent
|
|
142
|
+
# config resolves under ~/.controlzero/integrations/claude_code/ --
|
|
143
|
+
# the WRONG project + DLP profile, silently.
|
|
144
|
+
#
|
|
145
|
+
# This runs AFTER the Anthropic env-hint check above, deliberately: an
|
|
146
|
+
# ambient CLAUDECODE is a positive Claude signal and must keep winning
|
|
147
|
+
# (Windows detection leans on it). ``is_kiro_agent_payload`` only
|
|
148
|
+
# claims a shared PreToolUse/PostToolUse name when ``transcript_path``
|
|
149
|
+
# is absent, and Claude Code always sends it -- so a genuine Claude
|
|
150
|
+
# payload is never deferred. See kiro.py for the verified payload
|
|
151
|
+
# shape and its vendor-source citation.
|
|
152
|
+
if is_kiro_agent_payload(payload):
|
|
153
|
+
return False
|
|
154
|
+
|
|
134
155
|
# Stdin payload signature: PreToolUse always carries
|
|
135
156
|
# ``hook_event_name`` (post-2024-10 schema) OR a
|
|
136
157
|
# ``session_id`` + ``transcript_path`` + ``tool_input``
|