toolgovern-cli 0.1.5__tar.gz → 0.2.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.
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/PKG-INFO +4 -3
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/README.md +2 -1
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/pyproject.toml +2 -2
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/shared/paths.py +18 -3
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_classifier_filesystem_scope.py +31 -0
- toolgovern_cli-0.2.0/tests/test_shared_paths.py +43 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/.gitignore +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/LICENSE +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/approval/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/approval/pending_registry.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/credential_access.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/cross_agent_inheritance.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/filesystem_scope.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/index.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/information_flow.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/network_egress.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/shell_risk.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/util.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/cli.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/mcp_server.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/mcp_trust/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/middleware/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/middleware/idempotency_cache.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/middleware/on_tool_call.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/policy/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/policy/load_policy.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/policy/validate_policy.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/py.typed +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/scoping/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/scoping/inheritance_enforcer.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/scoping/scope_declaration.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/shared/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/trace/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/trace/canonical_json.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/trace/trace_reader.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/trace/trace_writer.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/types.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/__init__.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/conftest.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_classifier_credential_access.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_classifier_cross_agent_inheritance.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_classifier_index.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_classifier_information_flow.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_classifier_network_egress.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_classifier_shell_risk.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_cli.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_mcp_trust.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_middleware_on_tool_call.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_pending_registry.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_policy.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_scoping_inheritance_enforcer.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_scoping_scope_declaration.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_trace_canonical_json.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_trace_reader.py +0 -0
- {toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_trace_writer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: toolgovern-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Runtime governance middleware for AI agent tool calls -- gate shell, filesystem, network, and credential access before a tool executes.
|
|
5
5
|
Project-URL: Homepage, https://github.com/RudrenduPaul/toolgovern
|
|
6
6
|
Project-URL: Repository, https://github.com/RudrenduPaul/toolgovern
|
|
@@ -27,7 +27,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
27
27
|
Classifier: Topic :: Security
|
|
28
28
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
29
|
Requires-Python: >=3.9
|
|
30
|
-
Requires-Dist: cryptography<
|
|
30
|
+
Requires-Dist: cryptography<51,>=48.0.1
|
|
31
31
|
Requires-Dist: pyyaml<7,>=6.0
|
|
32
32
|
Provides-Extra: dev
|
|
33
33
|
Requires-Dist: build<2,>=1.0; extra == 'dev'
|
|
@@ -38,6 +38,7 @@ Requires-Dist: mcp[cli]>=2.0.0; extra == 'mcp'
|
|
|
38
38
|
Description-Content-Type: text/markdown
|
|
39
39
|
|
|
40
40
|
<!-- mcp-name: io.github.RudrenduPaul/toolgovern -->
|
|
41
|
+
|
|
41
42
|
# toolgovern (Python)
|
|
42
43
|
|
|
43
44
|
Gate every tool call an AI agent makes -- shell, filesystem, network, credential access -- before
|
|
@@ -339,7 +340,7 @@ run(args=["validate", "./toolgovern.policy.yml", "--json"])
|
|
|
339
340
|
This is a generic subprocess wrapper around the real CLI (not a second implementation of each
|
|
340
341
|
subcommand), so it stays in sync with `validate`, `audit`, and any future subcommand
|
|
341
342
|
automatically. This is distinct from toolgovern's `mcp_trust` module, which is a client-side
|
|
342
|
-
tool for verifying the trustworthiness of
|
|
343
|
+
tool for verifying the trustworthiness of _other_ MCP servers an agent connects to -- this
|
|
343
344
|
section is about toolgovern-cli exposing its own MCP server for agents to call.
|
|
344
345
|
|
|
345
346
|
## The signed audit trail
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<!-- mcp-name: io.github.RudrenduPaul/toolgovern -->
|
|
2
|
+
|
|
2
3
|
# toolgovern (Python)
|
|
3
4
|
|
|
4
5
|
Gate every tool call an AI agent makes -- shell, filesystem, network, credential access -- before
|
|
@@ -300,7 +301,7 @@ run(args=["validate", "./toolgovern.policy.yml", "--json"])
|
|
|
300
301
|
This is a generic subprocess wrapper around the real CLI (not a second implementation of each
|
|
301
302
|
subcommand), so it stays in sync with `validate`, `audit`, and any future subcommand
|
|
302
303
|
automatically. This is distinct from toolgovern's `mcp_trust` module, which is a client-side
|
|
303
|
-
tool for verifying the trustworthiness of
|
|
304
|
+
tool for verifying the trustworthiness of _other_ MCP servers an agent connects to -- this
|
|
304
305
|
section is about toolgovern-cli exposing its own MCP server for agents to call.
|
|
305
306
|
|
|
306
307
|
## The signed audit trail
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "toolgovern-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Runtime governance middleware for AI agent tool calls -- gate shell, filesystem, network, and credential access before a tool executes."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -31,7 +31,7 @@ classifiers = [
|
|
|
31
31
|
]
|
|
32
32
|
dependencies = [
|
|
33
33
|
"PyYAML>=6.0,<7",
|
|
34
|
-
"cryptography>=48.0.1,<
|
|
34
|
+
"cryptography>=48.0.1,<51",
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
[project.optional-dependencies]
|
|
@@ -15,8 +15,18 @@ _BRACKETED_HOST_PATTERN = re.compile(r"^\[([^\]]+)\]")
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def normalize_path(raw_path: str) -> str:
|
|
18
|
-
"""Collapses ``./``, trailing slashes, and duplicate slashes for stable prefix comparison.
|
|
18
|
+
"""Collapses ``./``, trailing slashes, and duplicate slashes for stable prefix comparison.
|
|
19
|
+
|
|
20
|
+
Backslashes are treated as path separators too -- the same as forward slashes -- regardless
|
|
21
|
+
of the host OS this code happens to run on. A declared scope must hold whether the target
|
|
22
|
+
path is destined for a Windows filesystem (where ``\\`` is the native separator) or is simply
|
|
23
|
+
an attacker-supplied string mixing separators to dodge a ``/``-only prefix check; without
|
|
24
|
+
this, a path like ``/allowed/sub\\..\\..\\..\\secrets`` compares as a literal child of
|
|
25
|
+
``/allowed`` even though it resolves outside it once backslashes are treated as separators
|
|
26
|
+
downstream.
|
|
27
|
+
"""
|
|
19
28
|
path = raw_path.strip()
|
|
29
|
+
path = path.replace("\\", "/")
|
|
20
30
|
if path.startswith("./"):
|
|
21
31
|
path = path[2:]
|
|
22
32
|
path = re.sub(r"/+", "/", path)
|
|
@@ -37,8 +47,13 @@ def is_path_within(candidate: str, prefix: str) -> bool:
|
|
|
37
47
|
|
|
38
48
|
|
|
39
49
|
def contains_path_traversal(raw_path: str) -> bool:
|
|
40
|
-
"""True if the path contains a ``..`` segment that could escape a scoped prefix via traversal.
|
|
41
|
-
|
|
50
|
+
"""True if the path contains a ``..`` segment that could escape a scoped prefix via traversal.
|
|
51
|
+
|
|
52
|
+
Splits on both ``/`` and ``\\`` (see ``normalize_path`` above for why backslash must count as
|
|
53
|
+
a separator here too) so ``sub\\..\\..\\..\\secrets`` is caught exactly like
|
|
54
|
+
``sub/../../../secrets``.
|
|
55
|
+
"""
|
|
56
|
+
return ".." in re.split(r"[/\\]", raw_path)
|
|
42
57
|
|
|
43
58
|
|
|
44
59
|
def normalize_host(host_like: str) -> str:
|
|
@@ -121,6 +121,37 @@ class TestPathTraversal:
|
|
|
121
121
|
decision, ids = _fired(ctx)
|
|
122
122
|
assert "TG02-path-traversal" in ids
|
|
123
123
|
|
|
124
|
+
# 2026-08-24 security fix: `contains_path_traversal`/`is_path_within`
|
|
125
|
+
# (python/src/toolgovern/shared/paths.py) used to split only on "/", so a backslash-delimited
|
|
126
|
+
# ".." segment (e.g. "sub\..\..\..\secrets") was never recognized as traversal -- the same
|
|
127
|
+
# string compared as a literal in-scope child of the declared prefix because it happened to
|
|
128
|
+
# start with the right characters, letting a sub-agent escape its declared filesystem scope
|
|
129
|
+
# undetected. Identical bug in the TypeScript and .NET ports; all three share this fix.
|
|
130
|
+
def test_fires_on_backslash_delimited_dotdot(self, ctx_factory):
|
|
131
|
+
ctx = ctx_factory(
|
|
132
|
+
{"path": "/workspace/sub\\..\\..\\..\\secrets", "operation": "write"},
|
|
133
|
+
scope=ScopeDeclaration(filesystem=["/workspace"]),
|
|
134
|
+
)
|
|
135
|
+
decision, ids = _fired(ctx)
|
|
136
|
+
assert decision == "deny"
|
|
137
|
+
assert "TG02-path-traversal" in ids
|
|
138
|
+
|
|
139
|
+
def test_fires_on_bare_backslash_traversal(self, ctx_factory):
|
|
140
|
+
ctx = ctx_factory(
|
|
141
|
+
{"path": "..\\..\\secrets", "operation": "read"},
|
|
142
|
+
scope=ScopeDeclaration(filesystem=["/workspace"]),
|
|
143
|
+
)
|
|
144
|
+
decision, ids = _fired(ctx)
|
|
145
|
+
assert "TG02-path-traversal" in ids
|
|
146
|
+
|
|
147
|
+
def test_fires_on_mixed_separator_traversal(self, ctx_factory):
|
|
148
|
+
ctx = ctx_factory(
|
|
149
|
+
{"path": "/workspace/sub/..\\../etc/passwd", "operation": "write"},
|
|
150
|
+
scope=ScopeDeclaration(filesystem=["/workspace"]),
|
|
151
|
+
)
|
|
152
|
+
decision, ids = _fired(ctx)
|
|
153
|
+
assert "TG02-path-traversal" in ids
|
|
154
|
+
|
|
124
155
|
|
|
125
156
|
class TestSymlinkEscape:
|
|
126
157
|
def test_fires_outside_scope(self, ctx_factory):
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Unit tests for toolgovern.shared.paths -- 2026-08-24 security fix.
|
|
2
|
+
|
|
3
|
+
Previously ``normalize_path``/``contains_path_traversal`` only split on "/", so a backslash-
|
|
4
|
+
delimited ".." segment was invisible to traversal detection, and a scope prefix like "/allowed"
|
|
5
|
+
would treat any candidate that merely started with the literal string "/allowed/" as in-scope even
|
|
6
|
+
when the remainder was pure backslash-delimited traversal
|
|
7
|
+
(``/allowed/sub\\..\\..\\..\\secrets``). This undercut the "sub-agent can't reach outside its
|
|
8
|
+
declared scope" guarantee identically across the TypeScript, Python, and .NET ports.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from toolgovern.shared.paths import contains_path_traversal, is_path_within, normalize_path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_normalize_path_converts_backslashes_to_forward_slashes():
|
|
15
|
+
assert normalize_path("workspace\\sub\\file.txt") == "workspace/sub/file.txt"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_normalize_path_collapses_mixed_separators_consistently():
|
|
19
|
+
assert normalize_path("./workspace\\sub/../file.txt") == "workspace/sub/../file.txt"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_contains_path_traversal_detects_backslash_delimited_dotdot():
|
|
23
|
+
assert contains_path_traversal("sub\\..\\..\\..\\secrets") is True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_contains_path_traversal_detects_mixed_separator_dotdot():
|
|
27
|
+
assert contains_path_traversal("sub/..\\../secrets") is True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_contains_path_traversal_false_for_clean_backslash_path():
|
|
31
|
+
assert contains_path_traversal("workspace\\sub\\file.txt") is False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_contains_path_traversal_still_detects_forward_slash_case():
|
|
35
|
+
assert contains_path_traversal("../../etc/passwd") is True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_is_path_within_still_recognizes_nested_path_as_in_scope():
|
|
39
|
+
assert is_path_within("/allowed/sub/dir/file.txt", "/allowed") is True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_is_path_within_still_rejects_unrelated_path():
|
|
43
|
+
assert is_path_within("/other/dir/file.txt", "/allowed") is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/credential_access.py
RENAMED
|
File without changes
|
{toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/classifier/cross_agent_inheritance.py
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
|
{toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/middleware/idempotency_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/src/toolgovern/scoping/inheritance_enforcer.py
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
|
{toolgovern_cli-0.1.5 → toolgovern_cli-0.2.0}/tests/test_classifier_cross_agent_inheritance.py
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
|