controlzero 1.13.12__tar.gz → 1.13.13__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.12 → controlzero-1.13.13}/CHANGELOG.md +49 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/PKG-INFO +1 -1
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/__init__.py +1 -1
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/action_aliases.py +68 -1
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/enforcer.py +34 -14
- {controlzero-1.13.12 → controlzero-1.13.13}/pyproject.toml +1 -1
- {controlzero-1.13.12 → controlzero-1.13.13}/.gitignore +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/LICENSE +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/README.md +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/__main__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/_messages.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/action_subject.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/condition_ops.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/dlp_output_observer.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/dlp_remediation.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/enforcement_outcome.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/io_capture_producer.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/source_labels.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/types.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/audit_local.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/audit_remote.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/canonical.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/__main__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/agent_config.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/console.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/enforcement.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/enforcement_verdict.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/exec_cmd.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hook_entry.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/claims.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/cursor.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/cursor_cli.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/kiro_adapter.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/main.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/maturity.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/output.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/setup_wizard.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/update_cmd.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/client.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/device.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/enrollment.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/error_codes.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/errors.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hitl/status.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hook_integrity.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/host_invocation.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/_export_dlp.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/_mask_apply.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/google.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/layout_migration.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/policy_loader.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/secrets/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/secrets/reference.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/secrets/resolver.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/secrets/secretstr.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_state.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/tamper.py +0 -0
- {controlzero-1.13.12 → controlzero-1.13.13}/controlzero/tracecontext.py +0 -0
|
@@ -2,6 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.13.13 (2026-08-16): a read-class METHOD could satisfy an allow the SQL would deny
|
|
6
|
+
|
|
7
|
+
**Security fix. If you use a `database:read` allow rule, read this.**
|
|
8
|
+
|
|
9
|
+
Our own documented read-only-database recipe permitted `DROP TABLE`. Driven
|
|
10
|
+
against the published 1.13.12 wheel, using that page's own policy and its own
|
|
11
|
+
verification command:
|
|
12
|
+
|
|
13
|
+
controlzero test database --method query --policy policy.yaml \
|
|
14
|
+
--args '{"sql":"DROP TABLE users"}' -> ALLOW, policy_id=read-only-db
|
|
15
|
+
|
|
16
|
+
same policy, no --method -> DENY
|
|
17
|
+
|
|
18
|
+
The alias table maps `read -> [query, SELECT, ...]`. A caller-supplied
|
|
19
|
+
`method="query"` is itself a read-class alias, so `database:read` was injected
|
|
20
|
+
FROM THE METHOD and satisfied the allow before the SQL extractor's `admin`
|
|
21
|
+
class was consulted. "Most dangerous class wins" is the documented semantics;
|
|
22
|
+
it did not happen whenever a method was supplied -- which is the SDK call shape
|
|
23
|
+
we document (`cz.guard("database", method="query", args={"sql": ...})`).
|
|
24
|
+
|
|
25
|
+
**Not a regression.** The `query -> read` alias predates the recipe. It has
|
|
26
|
+
never worked. All releases up to and including 1.13.12 are affected.
|
|
27
|
+
|
|
28
|
+
**Fixed:** when a method-derived class and an argument-derived class are both
|
|
29
|
+
present, the most dangerous now governs. Method-only calls keep legacy alias
|
|
30
|
+
expansion; concrete SQL overrides a stale caller-supplied semantic context.
|
|
31
|
+
Python and Node.
|
|
32
|
+
|
|
33
|
+
**If you cannot upgrade yet,** put explicit denies ahead of the allow. Verified
|
|
34
|
+
on the vulnerable 1.13.12 build: DROP / DELETE / TRUNCATE / GRANT and the
|
|
35
|
+
`SELECT 1; DROP TABLE x` piggyback all deny, while `SELECT` still allows.
|
|
36
|
+
|
|
37
|
+
rules:
|
|
38
|
+
- id: no-destructive-sql
|
|
39
|
+
deny: 'database:admin'
|
|
40
|
+
- id: no-writes
|
|
41
|
+
deny: 'database:write'
|
|
42
|
+
- id: read-only-db
|
|
43
|
+
allow: 'database:read'
|
|
44
|
+
|
|
45
|
+
A `deny: 'database:admin'` rule was never affected -- it matches the
|
|
46
|
+
argument-derived class regardless of the method. Only the allow-only shape was
|
|
47
|
+
vulnerable.
|
|
48
|
+
|
|
49
|
+
Tests: 124 explicit cells (31 method modes x 4 SQL classes) plus the four
|
|
50
|
+
literal CLI cases, mirrored in Node. Adding a class or a method alias fails the
|
|
51
|
+
matrix until its cells are filled.
|
|
52
|
+
|
|
53
|
+
|
|
5
54
|
## 1.13.12 (2026-08-16): your audit trail now states whether the HOST enforced, and deny rules that never fired now fire
|
|
6
55
|
|
|
7
56
|
Two things in this release change what a customer can conclude from what we
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.13.
|
|
3
|
+
Version: 1.13.13
|
|
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
|
|
@@ -87,6 +87,68 @@ for cls, aliases in _CLASSES.items():
|
|
|
87
87
|
_CANONICAL_TO_LEGACY[canon] = {_canonical(a) for a in aliases}
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
# Conflict-only ordering for a caller method versus the class derived from the
|
|
91
|
+
# concrete SQL arguments (#2340). ``admin`` and ``exec`` are both privileged,
|
|
92
|
+
# top-tier classes: neither may be treated as safer than the other. When they
|
|
93
|
+
# conflict, the argument-derived class wins the tie because it describes the
|
|
94
|
+
# SQL that will actually execute; the caller method is compatibility metadata.
|
|
95
|
+
_DATABASE_CLASS_DANGER: dict[str, int] = {
|
|
96
|
+
"read": 0,
|
|
97
|
+
"write": 1,
|
|
98
|
+
"admin": 2,
|
|
99
|
+
"exec": 2,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _database_classes_for_action(action: str) -> set[str]:
|
|
104
|
+
"""Return canonical database classes represented by one action."""
|
|
105
|
+
if not isinstance(action, str) or not action or ":" not in action:
|
|
106
|
+
return set()
|
|
107
|
+
tool, method = action.split(":", 1)
|
|
108
|
+
if tool != TOOL:
|
|
109
|
+
return set()
|
|
110
|
+
if method in _DATABASE_CLASS_DANGER:
|
|
111
|
+
return {method}
|
|
112
|
+
return {
|
|
113
|
+
canonical_action.split(":", 1)[1]
|
|
114
|
+
for canonical_action in _LEGACY_TO_CANONICAL.get(method, ())
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def resolve_database_class_conflict(
|
|
119
|
+
method_action: str,
|
|
120
|
+
argument_action: str,
|
|
121
|
+
) -> str | None:
|
|
122
|
+
"""Return the sole governing action for a method/argument class conflict.
|
|
123
|
+
|
|
124
|
+
``None`` means there is no explicit conflict to resolve: at least one side
|
|
125
|
+
is unclassified, or both sides represent exactly the same single class. In
|
|
126
|
+
those cases callers retain the additive compatibility behavior.
|
|
127
|
+
|
|
128
|
+
A returned action is a canonical ``database:<class>``. The evaluator must
|
|
129
|
+
build candidates from that action alone, making a less-dangerous method
|
|
130
|
+
alias absent rather than hoping a later deny rule detects it.
|
|
131
|
+
"""
|
|
132
|
+
method_classes = _database_classes_for_action(method_action)
|
|
133
|
+
argument_classes = _database_classes_for_action(argument_action)
|
|
134
|
+
|
|
135
|
+
# Explicit positive gate: conflict resolution only applies when BOTH
|
|
136
|
+
# sources carry recognized classes and the argument side is unambiguous.
|
|
137
|
+
if not method_classes or len(argument_classes) != 1:
|
|
138
|
+
return None
|
|
139
|
+
argument_class = next(iter(argument_classes))
|
|
140
|
+
if method_classes == {argument_class}:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
method_class = max(method_classes, key=_DATABASE_CLASS_DANGER.__getitem__)
|
|
144
|
+
if (
|
|
145
|
+
_DATABASE_CLASS_DANGER[argument_class]
|
|
146
|
+
>= _DATABASE_CLASS_DANGER[method_class]
|
|
147
|
+
):
|
|
148
|
+
return _canonical(argument_class)
|
|
149
|
+
return _canonical(method_class)
|
|
150
|
+
|
|
151
|
+
|
|
90
152
|
def expand_candidate_actions(actions: Iterable[str]) -> list[str]:
|
|
91
153
|
"""Expand an iterable of candidate actions to include every known alias.
|
|
92
154
|
|
|
@@ -162,4 +224,9 @@ def alias_table_json() -> str:
|
|
|
162
224
|
return json.dumps(payload, indent=2, sort_keys=False)
|
|
163
225
|
|
|
164
226
|
|
|
165
|
-
__all__ = [
|
|
227
|
+
__all__ = [
|
|
228
|
+
"TOOL",
|
|
229
|
+
"alias_table_json",
|
|
230
|
+
"expand_candidate_actions",
|
|
231
|
+
"resolve_database_class_conflict",
|
|
232
|
+
]
|
|
@@ -1171,18 +1171,21 @@ class PolicyEvaluator:
|
|
|
1171
1171
|
|
|
1172
1172
|
# Semantic-class layer (#345): when the action carries a
|
|
1173
1173
|
# canonical SQL class (database:read|write|admin|exec) the
|
|
1174
|
-
# evaluator matches rules against BOTH the per-keyword action
|
|
1175
|
-
# above AND this class action.
|
|
1174
|
+
# evaluator normally matches rules against BOTH the per-keyword action
|
|
1175
|
+
# above AND this class action. When recognized method and argument
|
|
1176
|
+
# classes conflict, #2340 replaces both with the most-dangerous class
|
|
1177
|
+
# below. A rule whose actions list
|
|
1176
1178
|
# contains `database:read` therefore fires for any read-shaped
|
|
1177
1179
|
# SQL (SELECT, EXPLAIN, SHOW, CTE, ...) regardless of dialect
|
|
1178
1180
|
# spelling, while existing per-keyword rules
|
|
1179
1181
|
# (`database:DROP`) keep working unchanged. Two sources for
|
|
1180
1182
|
# the class action: the caller may provide it in
|
|
1181
1183
|
# ``context['action_semantic_class']`` (used by the hook-check
|
|
1182
|
-
# CLI), or we derive it on the fly from ``args['sql']
|
|
1183
|
-
#
|
|
1184
|
+
# CLI), or we derive it on the fly from ``args['sql']``. Concrete SQL
|
|
1185
|
+
# arguments are authoritative when both are present; otherwise a caller
|
|
1186
|
+
# could suppress classification by passing a stale context value.
|
|
1184
1187
|
semantic_action = ctx_dict.get("action_semantic_class") or ""
|
|
1185
|
-
if
|
|
1188
|
+
if tool == "database" and args:
|
|
1186
1189
|
from controlzero._internal.hook_extractors import sql_semantic_class
|
|
1187
1190
|
sql_text = args.get("sql") if isinstance(args, dict) else None
|
|
1188
1191
|
if isinstance(sql_text, str) and sql_text:
|
|
@@ -1262,18 +1265,35 @@ class PolicyEvaluator:
|
|
|
1262
1265
|
# ...) keep matching modern SDK calls that emit canonical
|
|
1263
1266
|
# semantic classes (database:read|write|admin|exec), and vice
|
|
1264
1267
|
# versa. NO BREAKING CHANGES contract -- see #389.
|
|
1265
|
-
from controlzero._internal.action_aliases import
|
|
1268
|
+
from controlzero._internal.action_aliases import (
|
|
1269
|
+
expand_candidate_actions,
|
|
1270
|
+
resolve_database_class_conflict,
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
database_class_override = resolve_database_class_conflict(
|
|
1274
|
+
action, semantic_action
|
|
1275
|
+
)
|
|
1266
1276
|
# PRE-#2245 candidate set: the single surviving tool name plus the
|
|
1267
1277
|
# derived actions. Retained so phase 1 can answer "what would this
|
|
1268
1278
|
# policy have decided before the widening" without guessing.
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1279
|
+
if database_class_override is not None:
|
|
1280
|
+
# #2340: a recognized caller method and a recognized SQL-argument
|
|
1281
|
+
# class disagree. Build the candidate universe from the governing
|
|
1282
|
+
# class ONLY. The less-dangerous alias is therefore unmatchable by
|
|
1283
|
+
# construction; it cannot satisfy an early allow before the actual
|
|
1284
|
+
# SQL class is consulted. This is conflict-only, so method-only and
|
|
1285
|
+
# genuinely same-class calls retain the compatibility expansion.
|
|
1286
|
+
legacy_actions = expand_candidate_actions([database_class_override])
|
|
1287
|
+
candidate_actions = list(legacy_actions)
|
|
1288
|
+
else:
|
|
1289
|
+
legacy_actions = expand_candidate_actions([action, *extra_actions])
|
|
1290
|
+
# WIDENED candidate set: every name this call carried, plus the same
|
|
1291
|
+
# derived actions. A strict superset of `legacy_actions`, which is
|
|
1292
|
+
# what makes the phase-1 comparison below a two-outcome choice rather
|
|
1293
|
+
# than an open-ended merge.
|
|
1294
|
+
candidate_actions = expand_candidate_actions(
|
|
1295
|
+
[*subject.candidates, *extra_actions]
|
|
1296
|
+
)
|
|
1277
1297
|
|
|
1278
1298
|
resource = ctx_dict.get("resource")
|
|
1279
1299
|
evaluated = 0
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "controlzero"
|
|
7
|
-
version = "1.13.
|
|
7
|
+
version = "1.13.13"
|
|
8
8
|
description = "AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/credentials_data/__init__.py
RENAMED
|
File without changes
|
{controlzero-1.13.12 → controlzero-1.13.13}/controlzero/_internal/credentials_data/built_in.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/antigravity/hooks.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{controlzero-1.13.12 → controlzero-1.13.13}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|