controlzero 1.11.8__tar.gz → 1.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {controlzero-1.11.8 → controlzero-1.12.0}/CHANGELOG.md +76 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/PKG-INFO +27 -1
- {controlzero-1.11.8 → controlzero-1.12.0}/README.md +26 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/__init__.py +1 -1
- controlzero-1.12.0/controlzero/_internal/_messages.py +273 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/bundle.py +15 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/enforcer.py +105 -39
- controlzero-1.12.0/controlzero/_internal/source_labels.py +165 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/types.py +7 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/audit_remote.py +16 -1
- controlzero-1.12.0/controlzero/cli/agent_config.py +221 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/debug_bundle.py +66 -13
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/base.py +7 -1
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/main.py +821 -99
- controlzero-1.12.0/controlzero/cli/output.py +172 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/client.py +43 -4
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hosted_policy.py +133 -14
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/policy_loader.py +33 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/pyproject.toml +1 -1
- controlzero-1.12.0/tests/test_audit_project_id_attribution_pr2.py +305 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_debug_bundle.py +2 -1
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_validator_keys.py +6 -1
- controlzero-1.12.0/tests/test_hook_mask_failclosed.py +95 -0
- controlzero-1.12.0/tests/test_localized_reason.py +302 -0
- controlzero-1.12.0/tests/test_per_client_isolation_1011.py +251 -0
- controlzero-1.12.0/tests/test_pr4_install_messaging.py +377 -0
- controlzero-1.12.0/tests/test_pr4_output.py +144 -0
- controlzero-1.12.0/tests/test_pr4_status.py +160 -0
- controlzero-1.12.0/tests/test_source_labels.py +95 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_synthetic_policy_id_t79.py +6 -2
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_t99_install_prefetch_bundle.py +5 -5
- {controlzero-1.11.8 → controlzero-1.12.0}/.gitignore +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/Dockerfile.test +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/LICENSE +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/__main__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/io_capture_producer.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/audit_local.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/canonical.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/__main__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/console.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/exec_cmd.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hook_entry.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/cursor.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/cursor_cli.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/kiro_adapter.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/cli/update_cmd.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/device.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/enrollment.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/error_codes.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/errors.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hitl/status.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hook_integrity.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/google.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/layout_migration.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/secrets/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/secrets/reference.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/secrets/resolver.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/secrets/secretstr.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_state.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/tamper.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/controlzero/tracecontext.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/examples/hello_world.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/_fixtures/antigravity-envelope-expected.json +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/conftest.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/integrations/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/integrations/test_google.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/__init__.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/conftest.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_action_aliases.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_adapter_interface.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_agy_docs_no_exit2_1373.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_antigravity_adapter.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_antigravity_envelope_parity.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_antigravity_ga_blockers_1248.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_antigravity_tool_vocab_1303.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_audit_remote.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_audit_remote_io_capture.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_bundle_cache_freshness_1303.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_capability_matrix_doc.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_hook.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_hook_entry.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_init.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_tail.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_test.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cli_validate.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_conditions.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_config_format_parity_1303.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_conformance.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_console.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_credential_hook.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_cursor_adapter.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_default_action.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_device.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_dlp_mask.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_doctor.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_enrollment.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_error_codes.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_failopen_1303.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_glob_matching.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hook_integrity.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hosts_adapter.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_install_hooks.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_io_capture_producer.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_kiro_cli_e2e.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_kiro_cli_hook_pin_1265.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_kiro_install.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_log_rotation.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_migrate.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_part3_active_policy_count_1303.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_policy_load_failclosed_f3.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_policy_settings.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_probe_audit_isolation.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_quarantine.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_reason_code.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_refresh.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_secrets.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_secrets_reference_resolver.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_tamper.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_tracecontext.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_update_cmd.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tests/test_upgrade_nudge.py +0 -0
- {controlzero-1.11.8 → controlzero-1.12.0}/tools/cz-kiro-adapter +0 -0
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.12.0 -- 2026-06-25 (per-agent key authority + per-agent bundle cache + whoami)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`controlzero whoami`.** Prints which API key the SDK will actually use for a
|
|
8
|
+
surface (per-agent / env / global), its prefix only (never the full key), and
|
|
9
|
+
the project + org it resolves to. Diagnoses the most common "my audit logs are
|
|
10
|
+
missing" cause: a `CONTROLZERO_API_KEY` exported in the shell that maps to a
|
|
11
|
+
different project than the integration you installed, so events land in the
|
|
12
|
+
wrong tenant and the dashboard shows nothing.
|
|
13
|
+
- **Per-agent bundle cache.** Each governed surface now caches its policy bundle
|
|
14
|
+
under `~/.controlzero/integrations/<agent>/cache/` (next to the per-agent
|
|
15
|
+
`config.yaml`), so two surfaces on one machine each bound to their own project
|
|
16
|
+
no longer share a bundle. A read fallback keeps existing installs working on
|
|
17
|
+
upgrade: an empty per-agent cache transparently reads the legacy shared cache
|
|
18
|
+
until the first fetch re-caches it (no fail-closed on upgrade).
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Per-agent key is now AUTHORITATIVE in the enforcement hook.** Hook-check
|
|
23
|
+
precedence becomes per-agent (for the active surface) > env > global. A key
|
|
24
|
+
installed via `install <agent> --api-key` / `kiro init --api-key` now wins over
|
|
25
|
+
a stale ambient `CONTROLZERO_API_KEY`, with a loud one-line warning when they
|
|
26
|
+
differ. This closes the silent wrong-tenant bug where a hook spooled + uploaded
|
|
27
|
+
+ ingested correctly but attributed the events to whatever key was exported in
|
|
28
|
+
the shell, so the dashboard for the installed project showed "no audit entries".
|
|
29
|
+
Non-hook resolution (`status`, install-time display) is unchanged
|
|
30
|
+
(env > per-agent > global).
|
|
31
|
+
- **Cache bundle extension is now `.czpolicy`** (was `.bin`), matching the UI
|
|
32
|
+
policy export (both are the same signed bundle format). Legacy `.bin` caches
|
|
33
|
+
are still read and garbage-collected, so nothing breaks on upgrade.
|
|
34
|
+
|
|
35
|
+
## 1.11.9 -- 2026-06-25 (policy-posture remediation: honest hosted/local CLI, per-agent keys, key-validity diagnostics)
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **Severity-level CLI output (`controlzero.cli.output`).** All CLI prose now
|
|
40
|
+
routes through one renderer with INFO / WARNING / SUCCESS / ERROR levels:
|
|
41
|
+
WARNING + ERROR go to stderr, INFO + SUCCESS to stdout, colour honours
|
|
42
|
+
`NO_COLOR` / `CONTROLZERO_NO_COLOR` / `TERM=dumb` / non-TTY. No emojis. The
|
|
43
|
+
hook decision path and wire payloads are untouched -- the renderer is never
|
|
44
|
+
used inside `hook_check`, so decision reasons emit verbatim.
|
|
45
|
+
- **Per-integration (per-agent) API keys.** Each coding agent can bind its own
|
|
46
|
+
project key at `integrations/<agent>/config.yaml`. Resolution order is
|
|
47
|
+
`env (explicit) > per-agent (for that agent) > global`, fully back-compatible
|
|
48
|
+
(no per-agent key -> falls back to the global key). Per-agent dir `0700` /
|
|
49
|
+
file `0600`; the raw key is never echoed. This lets one machine run different
|
|
50
|
+
agents bound to different projects, each enforcing its own hosted bundle.
|
|
51
|
+
- **`controlzero status` rewrite.** Shows resolved runtime truth (mode, config
|
|
52
|
+
source, key, enrolled, audit dest) plus a per-agent bindings table and
|
|
53
|
+
per-key backend validity (active / expired / revoked) so a rejected key is
|
|
54
|
+
diagnosable from the CLI instead of only via the coarse E1101 at first tool
|
|
55
|
+
call (#1472).
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- **Honest hosted/local posture messaging.** One canonical wording each for the
|
|
60
|
+
HOSTED and LOCAL install paths, routed through every installer. The HOSTED
|
|
61
|
+
banner states that the local `policy.yaml` is IGNORED while an API key is set,
|
|
62
|
+
and the unmatched-tool default (DENY fail-closed unless the project is set to
|
|
63
|
+
Allow by default) -- removing the "install said ALLOW ALL but runtime denied"
|
|
64
|
+
ambiguity.
|
|
65
|
+
- **Archive-on-hosted.** When an API key is configured, the local `policy.yaml`
|
|
66
|
+
(and its `.sig`) is moved (not deleted) into `archive/policy-<UTC-ts>.yaml`,
|
|
67
|
+
symlink-safe, every error non-fatal -- a hosted machine never silently runs a
|
|
68
|
+
stale local policy.
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- **`synthetic:UNATTRIBUTED_DENY`** added to the synthetic policy-id catalog so
|
|
73
|
+
an unattributed fail-closed deny renders a stable, documented policy id across
|
|
74
|
+
every surface instead of an opaque blank (#1472).
|
|
75
|
+
- **Audit attribution.** SDK audit wire builders forward `project_id`
|
|
76
|
+
unconditionally so signed-sink audit events are attributed to the right
|
|
77
|
+
project (pairs with the backend ingest fix).
|
|
78
|
+
|
|
3
79
|
## 1.11.8 -- 2026-06-22 (DLP mask enforcement + I/O capture: enrolled-machine capture fix + server-side PII redaction)
|
|
4
80
|
|
|
5
81
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.0
|
|
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
|
|
@@ -185,6 +185,32 @@ rules:
|
|
|
185
185
|
Rules are evaluated top to bottom. The first match wins. If no rule matches,
|
|
186
186
|
the call is denied (fail-closed).
|
|
187
187
|
|
|
188
|
+
## Localized block messages (`reason_localized`)
|
|
189
|
+
|
|
190
|
+
The `reason` shown when a rule fires is plain UTF-8 text, so you can write it
|
|
191
|
+
in any language. To serve more than one language from the same policy, add a
|
|
192
|
+
`reason_localized` map and select a locale with the `CONTROLZERO_LOCALE`
|
|
193
|
+
environment variable (e.g. `ko`, `ko-KR`):
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
version: '1'
|
|
197
|
+
rules:
|
|
198
|
+
- deny: 'delete_*'
|
|
199
|
+
reason: 'Deletes need human approval' # English default
|
|
200
|
+
reason_localized:
|
|
201
|
+
ko: '삭제는 사람의 승인이 필요합니다'
|
|
202
|
+
- allow: '*'
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- When `CONTROLZERO_LOCALE` is unset (or set to a locale the rule has no entry
|
|
206
|
+
for), the plain `reason` is used -- existing behavior is unchanged.
|
|
207
|
+
- The SDK's own built-in messages (no-rule-match, DLP block, empty/observe
|
|
208
|
+
bundle) ship an English default plus a Korean translation, selected the same
|
|
209
|
+
way. The packs are pure in-binary maps (no runtime i18n dependency), so this
|
|
210
|
+
is air-gap safe.
|
|
211
|
+
- Localization is display-only: it never changes which rule matches or the
|
|
212
|
+
decided effect.
|
|
213
|
+
|
|
188
214
|
## Tamper detection and quarantine
|
|
189
215
|
|
|
190
216
|
The policy YAML supports a `settings:` section that controls how the SDK
|
|
@@ -131,6 +131,32 @@ rules:
|
|
|
131
131
|
Rules are evaluated top to bottom. The first match wins. If no rule matches,
|
|
132
132
|
the call is denied (fail-closed).
|
|
133
133
|
|
|
134
|
+
## Localized block messages (`reason_localized`)
|
|
135
|
+
|
|
136
|
+
The `reason` shown when a rule fires is plain UTF-8 text, so you can write it
|
|
137
|
+
in any language. To serve more than one language from the same policy, add a
|
|
138
|
+
`reason_localized` map and select a locale with the `CONTROLZERO_LOCALE`
|
|
139
|
+
environment variable (e.g. `ko`, `ko-KR`):
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
version: '1'
|
|
143
|
+
rules:
|
|
144
|
+
- deny: 'delete_*'
|
|
145
|
+
reason: 'Deletes need human approval' # English default
|
|
146
|
+
reason_localized:
|
|
147
|
+
ko: '삭제는 사람의 승인이 필요합니다'
|
|
148
|
+
- allow: '*'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- When `CONTROLZERO_LOCALE` is unset (or set to a locale the rule has no entry
|
|
152
|
+
for), the plain `reason` is used -- existing behavior is unchanged.
|
|
153
|
+
- The SDK's own built-in messages (no-rule-match, DLP block, empty/observe
|
|
154
|
+
bundle) ship an English default plus a Korean translation, selected the same
|
|
155
|
+
way. The packs are pure in-binary maps (no runtime i18n dependency), so this
|
|
156
|
+
is air-gap safe.
|
|
157
|
+
- Localization is display-only: it never changes which rule matches or the
|
|
158
|
+
decided effect.
|
|
159
|
+
|
|
134
160
|
## Tamper detection and quarantine
|
|
135
161
|
|
|
136
162
|
The policy YAML supports a `settings:` section that controls how the SDK
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""In-binary localization pack for the SDK-GENERATED decision reasons.
|
|
2
|
+
|
|
3
|
+
Why this exists ( checklist #25, gh#1439)
|
|
4
|
+
--------------------------------------------------
|
|
5
|
+
The user-facing ``reason`` string is carried end-to-end as UTF-8, so an
|
|
6
|
+
operator can already localize ANY rule today by typing the target-language
|
|
7
|
+
text into that rule's ``reason:`` -- or, for per-locale overrides, into the
|
|
8
|
+
new ``reason_localized:`` map (a ``{locale: message}`` dict on the rule). That
|
|
9
|
+
covers operator-authored rules. This module covers the OTHER half: the
|
|
10
|
+
messages the SDK itself GENERATES for the no-rule-match / empty-bundle /
|
|
11
|
+
missing-bundle / tampered / DLP paths. Those have no operator-authored text to
|
|
12
|
+
carry, so they need an in-binary pack.
|
|
13
|
+
|
|
14
|
+
No runtime i18n library is used: these are pure dict lookups, which keeps the
|
|
15
|
+
SDK air-gap safe (the PoC ships self-hosted with no network).
|
|
16
|
+
|
|
17
|
+
Contract
|
|
18
|
+
--------
|
|
19
|
+
- The ``en`` value is the single source of truth for the inline paths that now
|
|
20
|
+
read it (no-match, DLP, tamper, quarantine): it MUST stay byte-identical to
|
|
21
|
+
the legacy hard-coded string so existing consumers that regex-match the
|
|
22
|
+
``reason`` text are unaffected. ``tests/test_localized_reason.py`` pins this.
|
|
23
|
+
- English remains the literal DEFAULT whenever ``CONTROLZERO_LOCALE`` is unset
|
|
24
|
+
or a locale key is missing from a pack entry.
|
|
25
|
+
- For the synthetic empty/missing-bundle rules the English text stays on the
|
|
26
|
+
rule's ``reason`` (unchanged bundle output); the pack supplies ONLY the
|
|
27
|
+
localized (non-en) override, looked up by the rule's ``reason_code``.
|
|
28
|
+
|
|
29
|
+
KOREAN COPY IS A PROFESSIONAL DRAFT and MUST be reviewed/approved by the customer
|
|
30
|
+
before being presented as final. See KO_Localization_2026-06-22 in the project
|
|
31
|
+
vault. The English default is never blocked on that review.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import os
|
|
37
|
+
from typing import Optional
|
|
38
|
+
|
|
39
|
+
DEFAULT_LOCALE = "en"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def resolve_locale(explicit: Optional[str] = None) -> str:
|
|
43
|
+
"""Resolve the active locale.
|
|
44
|
+
|
|
45
|
+
Precedence: explicit arg > ``CONTROLZERO_LOCALE`` env > "" (English).
|
|
46
|
+
Returned lower-cased and stripped so lookups are case-insensitive.
|
|
47
|
+
"""
|
|
48
|
+
if explicit:
|
|
49
|
+
return explicit.strip().lower()
|
|
50
|
+
return (os.environ.get("CONTROLZERO_LOCALE", "") or "").strip().lower()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _locale_candidates(locale: str) -> list[str]:
|
|
54
|
+
"""Return lookup candidates for a locale, most specific first.
|
|
55
|
+
|
|
56
|
+
``ko-KR`` / ``ko_KR`` -> ``["ko-kr", "ko"]`` so a pack keyed by the bare
|
|
57
|
+
primary subtag ("ko") still matches a region-qualified request.
|
|
58
|
+
"""
|
|
59
|
+
loc = (locale or "").strip().lower()
|
|
60
|
+
if not loc:
|
|
61
|
+
return []
|
|
62
|
+
out = [loc]
|
|
63
|
+
for sep in ("-", "_"):
|
|
64
|
+
if sep in loc:
|
|
65
|
+
primary = loc.split(sep, 1)[0]
|
|
66
|
+
if primary and primary not in out:
|
|
67
|
+
out.append(primary)
|
|
68
|
+
break
|
|
69
|
+
return out
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# message key -> {locale -> template}. Templates use str.format placeholders
|
|
73
|
+
# ({action}, {rule_name}, {category}). The English values for the inline paths
|
|
74
|
+
# (NO_RULE_MATCH:*, DLP_BLOCKED:*, BUNDLE_TAMPERED, MACHINE_QUARANTINED) are
|
|
75
|
+
# byte-identical to the legacy strings. For the synthetic-rule codes
|
|
76
|
+
# (OBSERVE_MODE_NO_POLICY, NO_ACTIVE_POLICIES, BUNDLE_MISSING) the English is
|
|
77
|
+
# documentation/parity only -- the live English comes from the rule's reason.
|
|
78
|
+
SYSTEM_MESSAGES: dict[str, dict[str, str]] = {
|
|
79
|
+
# No-rule-match fallthrough (the most common block a Korean operator sees).
|
|
80
|
+
"NO_RULE_MATCH:deny": {
|
|
81
|
+
"en": (
|
|
82
|
+
"No matching policy rule for '{action}' (fail-closed default; "
|
|
83
|
+
"default_on_missing=deny). This tool is neither allowed nor "
|
|
84
|
+
"denied by any rule in your hosted policy, so it is blocked. "
|
|
85
|
+
"Add a rule that allows it (e.g. allow: '*' as a catch-all, or "
|
|
86
|
+
"allow: '{action}'), or set the project/org default to allow, "
|
|
87
|
+
"in the Control Zero dashboard."
|
|
88
|
+
),
|
|
89
|
+
# ko: "'{action}'에 일치하는 정책 규칙이 없어 기본 차단됩니다 ..."
|
|
90
|
+
"ko": (
|
|
91
|
+
"'{action}'에 일치하는 정책 규칙이 없습니다(기본 차단; "
|
|
92
|
+
"default_on_missing=deny). 이 도구는 정책에서 허용되지도 "
|
|
93
|
+
"거부되지도 않았으므로 차단됩니다. Control Zero 대시보드에서 "
|
|
94
|
+
"이를 허용하는 규칙을 추가하거나(예: 포괄 규칙 allow: '*' 또는 "
|
|
95
|
+
"allow: '{action}'), 프로젝트/조직 기본값을 allow로 "
|
|
96
|
+
"설정하십시오."
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
"NO_RULE_MATCH:allow": {
|
|
100
|
+
"en": "No matching policy rule for '{action}' (default_action=allow)",
|
|
101
|
+
# ko: "'{action}'에 일치하는 정책 규칙이 없습니다 (default_action=allow)"
|
|
102
|
+
"ko": "'{action}'에 일치하는 정책 규칙이 없습니다(default_action=allow)",
|
|
103
|
+
},
|
|
104
|
+
"NO_RULE_MATCH:warn": {
|
|
105
|
+
"en": "No matching policy rule for '{action}' (default_action=warn)",
|
|
106
|
+
# ko: "'{action}'에 일치하는 정책 규칙이 없습니다 (default_action=warn)"
|
|
107
|
+
"ko": "'{action}'에 일치하는 정책 규칙이 없습니다(default_action=warn)",
|
|
108
|
+
},
|
|
109
|
+
# DLP overrides (the tool-name policy allowed, but a DLP rule vetoed).
|
|
110
|
+
"DLP_BLOCKED:match": {
|
|
111
|
+
"en": (
|
|
112
|
+
"DLP rule '{rule_name}' matched {category} content in tool "
|
|
113
|
+
"arguments"
|
|
114
|
+
),
|
|
115
|
+
# ko: "DLP 규칙 '{rule_name}'이(가) 도구 인자에서 {category} 콘텐츠와 ..."
|
|
116
|
+
"ko": (
|
|
117
|
+
"DLP 규칙 '{rule_name}'이(가) 도구 인자에서 {category} "
|
|
118
|
+
"콘텐츠와 일치하여 호출이 차단되었습니다."
|
|
119
|
+
),
|
|
120
|
+
},
|
|
121
|
+
"DLP_BLOCKED:boundary": {
|
|
122
|
+
"en": (
|
|
123
|
+
"DLP rule '{rule_name}' matched {category} content that spans "
|
|
124
|
+
"argument boundaries and could not be safely masked; blocking "
|
|
125
|
+
"(fail-closed) rather than forwarding an un-redacted value"
|
|
126
|
+
),
|
|
127
|
+
# ko: "DLP 규칙 '{rule_name}'이(가) 인자 경계에 걸친 {category} 콘텐츠와 ..."
|
|
128
|
+
"ko": (
|
|
129
|
+
"DLP 규칙 '{rule_name}'이(가) 인자 경계에 걸쳐 있는 "
|
|
130
|
+
"{category} 콘텐츠와 일치하여 안전하게 마스킹할 수 없으므로, "
|
|
131
|
+
"마스킹되지 않은 값을 전달하는 대신 차단합니다(기본 차단)."
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
# Tamper / quarantine (constructed in client.py + cli/main.py).
|
|
135
|
+
"BUNDLE_TAMPERED": {
|
|
136
|
+
"en": (
|
|
137
|
+
"Policy tampering detected. Call blocked by tamper enforcement "
|
|
138
|
+
"policy."
|
|
139
|
+
),
|
|
140
|
+
# ko: "정책 변조가 감지되었습니다. 변조 시행 정책에 의해 호출이 차단되었습니다."
|
|
141
|
+
"ko": (
|
|
142
|
+
"정책 변조가 감지되었습니다. 변조 시행 정책에 의해 호출이 "
|
|
143
|
+
"차단되었습니다."
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
# CLI hook-check tamper enforcement (cli/main.py). Carries a {source}
|
|
147
|
+
# discriminator (policy_hmac / audit_chain) and the [Control Zero] prefix.
|
|
148
|
+
"BUNDLE_TAMPERED:cli": {
|
|
149
|
+
"en": (
|
|
150
|
+
"[Control Zero] Tamper detected ({source}). Blocked by enforcement "
|
|
151
|
+
"policy."
|
|
152
|
+
),
|
|
153
|
+
# ko: "[Control Zero] 변조가 감지되었습니다({source}). 시행 정책에 의해 ..."
|
|
154
|
+
"ko": (
|
|
155
|
+
"[Control Zero] 변조가 감지되었습니다({source}). 시행 정책에 "
|
|
156
|
+
"의해 차단되었습니다."
|
|
157
|
+
),
|
|
158
|
+
},
|
|
159
|
+
"MACHINE_QUARANTINED": {
|
|
160
|
+
"en": (
|
|
161
|
+
"Machine quarantined: policy tampering detected. All calls denied "
|
|
162
|
+
"until recovery."
|
|
163
|
+
),
|
|
164
|
+
# ko: "머신이 격리되었습니다: 정책 변조가 감지되었습니다. 복구될 때까지 ..."
|
|
165
|
+
"ko": (
|
|
166
|
+
"머신이 격리되었습니다: 정책 변조가 감지되었습니다. 복구될 "
|
|
167
|
+
"때까지 모든 호출이 거부됩니다."
|
|
168
|
+
),
|
|
169
|
+
},
|
|
170
|
+
# Synthetic empty / missing-bundle rules. English here is parity-only --
|
|
171
|
+
# the live English comes from the rule's `reason` (bundle output unchanged);
|
|
172
|
+
# the enforcer reads ONLY the localized (non-en) value below, by code.
|
|
173
|
+
"OBSERVE_MODE_NO_POLICY": {
|
|
174
|
+
"en": (
|
|
175
|
+
"OBSERVE MODE: no policies are active on this project, so "
|
|
176
|
+
"Control Zero is monitoring and auditing tool calls but "
|
|
177
|
+
"NOT enforcing -- every call is allowed and logged. Attach "
|
|
178
|
+
"a policy (or set the empty-project default to deny) in the "
|
|
179
|
+
"Control Zero dashboard to start enforcing."
|
|
180
|
+
),
|
|
181
|
+
# ko: "관찰 모드: 이 프로젝트에 활성 정책이 없으므로 ... 차단하지 않습니다 ..."
|
|
182
|
+
"ko": (
|
|
183
|
+
"관찰 모드: 이 프로젝트에 활성 정책이 없으므로 Control Zero는 "
|
|
184
|
+
"도구 호출을 모니터링하고 감사 기록을 남기지만 차단하지는 "
|
|
185
|
+
"않습니다 -- 모든 호출이 허용되고 기록됩니다. 시행을 시작하려면 "
|
|
186
|
+
"Control Zero 대시보드에서 정책을 연결하거나(또는 빈 프로젝트 "
|
|
187
|
+
"기본값을 deny로 설정) 하십시오."
|
|
188
|
+
),
|
|
189
|
+
},
|
|
190
|
+
"NO_ACTIVE_POLICIES": {
|
|
191
|
+
"en": (
|
|
192
|
+
"No policies are active on this project. If the dashboard "
|
|
193
|
+
"shows attached policies, regenerate the policy bundle."
|
|
194
|
+
),
|
|
195
|
+
# ko: "이 프로젝트에 활성 정책이 없습니다. 대시보드에 연결된 정책이 표시되면 ..."
|
|
196
|
+
"ko": (
|
|
197
|
+
"이 프로젝트에 활성 정책이 없습니다. 대시보드에 연결된 정책이 "
|
|
198
|
+
"표시되면 정책 번들을 다시 생성하십시오."
|
|
199
|
+
),
|
|
200
|
+
},
|
|
201
|
+
# BUNDLE_MISSING covers two English variants (degraded bundle vs. bundle
|
|
202
|
+
# not loaded), both reason_code=BUNDLE_MISSING. The localized override is a
|
|
203
|
+
# single accurate general message; the precise English stays on the rule.
|
|
204
|
+
"BUNDLE_MISSING": {
|
|
205
|
+
"en": (
|
|
206
|
+
"Policy bundle could not be loaded or produced zero enforceable "
|
|
207
|
+
"rules. Control Zero is failing CLOSED (deny) rather than allowing "
|
|
208
|
+
"every tool call. Regenerate the policy bundle in the Control Zero "
|
|
209
|
+
"dashboard; contact support if this persists."
|
|
210
|
+
),
|
|
211
|
+
# ko: "정책 번들을 로드할 수 없거나 시행 가능한 규칙을 생성하지 못했습니다 ..."
|
|
212
|
+
"ko": (
|
|
213
|
+
"정책 번들을 로드할 수 없거나 시행 가능한 규칙을 생성하지 "
|
|
214
|
+
"못했습니다(미동기화, 백엔드 연결 불가, 복호화 실패 또는 손상/"
|
|
215
|
+
"부분/오래된 번들). Control Zero는 모든 호출을 허용하는 대신 "
|
|
216
|
+
"차단(기본 차단)합니다. Control Zero 대시보드에서 정책 번들을 "
|
|
217
|
+
"다시 생성하고, 문제가 지속되면 지원팀에 문의하십시오."
|
|
218
|
+
),
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def system_message(key: str, locale: Optional[str] = None, **fmt: object) -> Optional[str]:
|
|
224
|
+
"""Return the fully-resolved system message for ``key``, English-default.
|
|
225
|
+
|
|
226
|
+
Resolution: ``pack[key][locale]`` (region subtag tolerated) falling back to
|
|
227
|
+
``pack[key]["en"]``. Returns ``None`` only when ``key`` is unknown. Used by
|
|
228
|
+
the inline paths (no-match, DLP, tamper, quarantine) where the pack is the
|
|
229
|
+
source of the English text.
|
|
230
|
+
"""
|
|
231
|
+
entry = SYSTEM_MESSAGES.get(key)
|
|
232
|
+
if not entry:
|
|
233
|
+
return None
|
|
234
|
+
template = None
|
|
235
|
+
for cand in _locale_candidates(resolve_locale(locale)):
|
|
236
|
+
if cand in entry:
|
|
237
|
+
template = entry[cand]
|
|
238
|
+
break
|
|
239
|
+
if template is None:
|
|
240
|
+
template = entry.get(DEFAULT_LOCALE)
|
|
241
|
+
if template is None:
|
|
242
|
+
return None
|
|
243
|
+
if fmt:
|
|
244
|
+
try:
|
|
245
|
+
return template.format(**fmt)
|
|
246
|
+
except (KeyError, IndexError):
|
|
247
|
+
return template
|
|
248
|
+
return template
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def localized_override(key: str, locale: Optional[str] = None, **fmt: object) -> Optional[str]:
|
|
252
|
+
"""Return ONLY a non-English localized message for ``key``, else ``None``.
|
|
253
|
+
|
|
254
|
+
Unlike :func:`system_message` this never falls back to English. Used for
|
|
255
|
+
the synthetic empty/missing-bundle rules, whose English text stays on the
|
|
256
|
+
rule's ``reason`` (bundle output unchanged) -- the enforcer applies this
|
|
257
|
+
override only when an actual localized string exists for the active locale.
|
|
258
|
+
"""
|
|
259
|
+
entry = SYSTEM_MESSAGES.get(key)
|
|
260
|
+
if not entry:
|
|
261
|
+
return None
|
|
262
|
+
for cand in _locale_candidates(resolve_locale(locale)):
|
|
263
|
+
if cand == DEFAULT_LOCALE:
|
|
264
|
+
continue
|
|
265
|
+
if cand in entry:
|
|
266
|
+
template = entry[cand]
|
|
267
|
+
if fmt:
|
|
268
|
+
try:
|
|
269
|
+
return template.format(**fmt)
|
|
270
|
+
except (KeyError, IndexError):
|
|
271
|
+
return template
|
|
272
|
+
return template
|
|
273
|
+
return None
|
|
@@ -943,4 +943,19 @@ def _translate_rule(rule: dict, policy_id: str) -> Optional[dict]:
|
|
|
943
943
|
if escalate is not None:
|
|
944
944
|
translated["escalate_on_deny"] = bool(escalate)
|
|
945
945
|
|
|
946
|
+
# #25 (gh#1439): the SAME allowlist-strip trap (the gh#175 /
|
|
947
|
+
# #1303 family). A backend-signed bundle copies only an allowlist of
|
|
948
|
+
# rule fields into the local rule shape; without this line a hosted
|
|
949
|
+
# policy's `reason_localized` map is silently dropped and a Korean
|
|
950
|
+
# operator would see only the English fallback. Forward it intact.
|
|
951
|
+
# (The backend bundle wire struct must ALSO carry the field -- see
|
|
952
|
+
# bundle_handler.go PolicyRule -- or it never reaches this translator.)
|
|
953
|
+
reason_localized = rule.get("reason_localized")
|
|
954
|
+
if isinstance(reason_localized, dict) and reason_localized:
|
|
955
|
+
translated["reason_localized"] = {
|
|
956
|
+
str(k).strip().lower(): str(v)
|
|
957
|
+
for k, v in reason_localized.items()
|
|
958
|
+
if isinstance(k, str) and isinstance(v, str)
|
|
959
|
+
}
|
|
960
|
+
|
|
946
961
|
return translated
|