toolwall 0.4.0__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {toolwall-0.4.0 → toolwall-0.4.2}/.gitignore +5 -0
- {toolwall-0.4.0 → toolwall-0.4.2}/LICENSE +21 -21
- {toolwall-0.4.0 → toolwall-0.4.2}/PKG-INFO +25 -8
- {toolwall-0.4.0 → toolwall-0.4.2}/README.md +177 -160
- {toolwall-0.4.0 → toolwall-0.4.2}/pyproject.toml +49 -49
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/__init__.py +63 -61
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/gate.py +698 -563
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/intake.py +8 -2
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/mcp_guard.py +34 -14
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/policy.py +42 -0
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/receipt.py +109 -109
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/schema.py +81 -81
- toolwall-0.4.2/src/toolwall/shield.py +424 -0
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/wall.py +2 -1
- toolwall-0.4.0/src/toolwall/shield.py +0 -190
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/cli.py +0 -0
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/meter.py +0 -0
- {toolwall-0.4.0 → toolwall-0.4.2}/src/toolwall/suggest.py +0 -0
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Mohammad Safwan Athar
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohammad Safwan Athar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: toolwall
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Security gateway for AI agent tool calls: fail-closed schema + policy + secret-detection gate before any tool executes
|
|
5
5
|
Project-URL: Homepage, https://github.com/Dev-Saif-Ops/toolwall
|
|
6
6
|
Author: Mohammad Safwan Athar
|
|
@@ -99,16 +99,22 @@ Everything not explicitly allowed is blocked. That is the whole idea.
|
|
|
99
99
|
|
|
100
100
|
- **Fail-closed gate**: unknown tool, schema violation, policy violation, budget hit,
|
|
101
101
|
or unparseable payload all block *before* the tool runs. Registration is the allowlist.
|
|
102
|
-
- **Policy engine**: value constraints (`in_range`, `one_of`, `matches`, `
|
|
102
|
+
- **Policy engine**: value constraints (`in_range`, `one_of`, `matches`, `email_domain`…),
|
|
103
103
|
cross-argument rules, human-approval flags, and budget caps (calls / per-tool / USD).
|
|
104
|
+
For recipients use `email_domain("ourco.com")`, not `ends_with`: a comma-separated
|
|
105
|
+
list ends with your domain too.
|
|
104
106
|
- **Shield, both directions**: detects secrets (AWS, OpenAI, GitHub, Stripe, Slack,
|
|
105
107
|
JWT, PEM, and high-entropy strings) in tool arguments *and in tool return values*,
|
|
106
|
-
then blocks or redacts them
|
|
107
|
-
|
|
108
|
+
then blocks or redacts them, including in dict keys, tuples (database rows), sets,
|
|
109
|
+
bytes and dataclasses. Output that cannot be scanned (generators, cursors, file
|
|
110
|
+
objects, XML elements, models, arrays) is withheld, so return plain data;
|
|
111
|
+
redaction never modifies the tool's own objects. Reasons, reports
|
|
112
|
+
and the audit log are scrubbed of detected secrets.
|
|
108
113
|
- **Dry-run**: run your whole agent with `dry_run=True`: nothing executes, and
|
|
109
114
|
`gate.report()` tells you what it *would* have done. `suggest_policies(gate)` drafts
|
|
110
115
|
a starter policy from the calls it observed.
|
|
111
|
-
- **MCP guard**: `MCPGuard`
|
|
116
|
+
- **MCP guard**: `MCPGuard` runs the same check and execute path (budget, dry-run,
|
|
117
|
+
receipts, output scanning) in front of a function that forwards to your MCP server.
|
|
112
118
|
- **Audit trail**: every verdict exported to JSON/CSV.
|
|
113
119
|
|
|
114
120
|
## Why not just the guardrails in my agent framework?
|
|
@@ -146,7 +152,8 @@ guard = MCPGuard(gate, forward=call_downstream_mcp_server)
|
|
|
146
152
|
decision = guard.handle(tool_name, args) # only ALLOW is forwarded
|
|
147
153
|
```
|
|
148
154
|
|
|
149
|
-
|
|
155
|
+
`forward` is your own function that calls the downstream server; no MCP transport
|
|
156
|
+
ships yet. `pip install "toolwall[mcp]"` only installs the `mcp` package.
|
|
150
157
|
|
|
151
158
|
## Examples
|
|
152
159
|
|
|
@@ -165,12 +172,22 @@ python examples/live_gemini_agent.py # set GEMINI_API_KEY first
|
|
|
165
172
|
|
|
166
173
|
## Honest status
|
|
167
174
|
|
|
168
|
-
`toolwall` is alpha. The published failure suite blocks **
|
|
169
|
-
11 classes with 0 false blocks
|
|
175
|
+
`toolwall` is alpha. The published failure suite blocks **35 of 35 attack cases across
|
|
176
|
+
11 classes with 0 false blocks on its 14 clean cases**, at sub-millisecond per-call
|
|
177
|
+
overhead for small arguments (about 2 ms at 50 KB). It is a regression suite for one
|
|
178
|
+
reference config, not a coverage measure, and it lists ordinary text the shield is
|
|
179
|
+
known to flag. Secret
|
|
170
180
|
detection is pattern + entropy based and is **never 100%**. Structureless passwords are
|
|
171
181
|
out of scope, and the suite report states exactly what is and is not proven. Every claim
|
|
172
182
|
about toolwall cites that report, nothing broader.
|
|
173
183
|
|
|
184
|
+
**The verdict covers the call, not the state of the world.** An approved
|
|
185
|
+
`delete_records(id=42)` deletes whatever 42 points to at execution time; if the resource
|
|
186
|
+
was swapped after the check, the receipt still matches because the argument never
|
|
187
|
+
changed. Argument integrity is not resource integrity. For resources that can change
|
|
188
|
+
owner or meaning, re-verify inside the tool's own transaction (for example
|
|
189
|
+
compare-and-swap on a version column); the gate cannot see your datastore.
|
|
190
|
+
|
|
174
191
|
## Links
|
|
175
192
|
|
|
176
193
|
- **Source, full docs, and the failure suite:** https://github.com/Dev-Saif-Ops/toolwall
|
|
@@ -1,160 +1,177 @@
|
|
|
1
|
-
# toolwall
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/toolwall/)
|
|
4
|
-
[](https://pypi.org/project/toolwall/)
|
|
5
|
-
[](https://github.com/Dev-Saif-Ops/toolwall/blob/main/toolwall/LICENSE)
|
|
6
|
-
[](https://github.com/Dev-Saif-Ops/toolwall)
|
|
7
|
-
|
|
8
|
-
**The security gateway for AI agent tool calls.**
|
|
9
|
-
|
|
10
|
-
Your LLM can generate a *valid* tool call. That doesn't mean it's *safe* to execute.
|
|
11
|
-
|
|
12
|
-
```python
|
|
13
|
-
delete_records(filter={}) # perfectly valid JSON. whole table gone.
|
|
14
|
-
send_email(to="attacker@evil.com") # recipient injected via a poisoned web page
|
|
15
|
-
transfer_money(amount=999999999) # schema-valid. every field the right type.
|
|
16
|
-
db_query(limit=10_000_000) # production melts.
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Every one of these passes JSON schema validation. Structured outputs, schemas, and
|
|
20
|
-
content moderation all wave them through. `toolwall` is the fail-closed checkpoint
|
|
21
|
-
between the LLM's tool call and execution that blocks them.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Install
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
pip install toolwall
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Zero required dependencies. Works with OpenAI, Anthropic, and Gemini native tool
|
|
32
|
-
calling (and plain dicts). Python 3.10+.
|
|
33
|
-
|
|
34
|
-
## Quickstart
|
|
35
|
-
|
|
36
|
-
```python
|
|
37
|
-
from toolwall import ToolWall, Policy, ToolSchema, in_range, not_empty
|
|
38
|
-
|
|
39
|
-
wall = ToolWall() # default-deny, secret detection + audit on
|
|
40
|
-
|
|
41
|
-
wall.register("db_query", db_query,
|
|
42
|
-
schema=ToolSchema(required=["q"], types={"q": str, "limit": int}),
|
|
43
|
-
policy=Policy(constraints={"limit": in_range(1, 100)}))
|
|
44
|
-
wall.register("delete_records", delete_records,
|
|
45
|
-
schema=ToolSchema(required=["filter"], types={"filter": dict}),
|
|
46
|
-
policy=Policy(constraints={"filter": not_empty}, require_approval=True))
|
|
47
|
-
wall.budget(max_calls=20)
|
|
48
|
-
|
|
49
|
-
result = wall.call("delete_records", {"filter": {}})
|
|
50
|
-
# result.blocked -> True
|
|
51
|
-
# result.reason -> "policy violation: arg 'filter' rejected by not_empty"
|
|
52
|
-
|
|
53
|
-
results = wall.guard(openai_response) # or gate a raw OpenAI/Anthropic/Gemini response
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Only an `ALLOW` verdict runs the tool. `Gate` is the lower-level primitive underneath.
|
|
57
|
-
|
|
58
|
-
## What it stops
|
|
59
|
-
|
|
60
|
-
```python
|
|
61
|
-
wall.call("delete_records", {"filter": {}})
|
|
62
|
-
# BLOCKED: policy violation: arg 'filter' rejected by not_empty
|
|
63
|
-
|
|
64
|
-
wall.call("send_email", {"to": "ops@ourco.com", "body": "aws key AKIA..."})
|
|
65
|
-
# BLOCKED: secret detected (aws-access-key) in arg 'body'
|
|
66
|
-
|
|
67
|
-
wall.call("db_query", {"q": "everything", "limit": 10_000_000})
|
|
68
|
-
# BLOCKED: policy violation: arg 'limit' rejected by in_range(1, 100)
|
|
69
|
-
|
|
70
|
-
wall.call("run_shell", {"cmd": "..."})
|
|
71
|
-
# BLOCKED: unknown tool: 'run_shell'
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Everything not explicitly allowed is blocked. That is the whole idea.
|
|
75
|
-
|
|
76
|
-
## What it does
|
|
77
|
-
|
|
78
|
-
- **Fail-closed gate**: unknown tool, schema violation, policy violation, budget hit,
|
|
79
|
-
or unparseable payload all block *before* the tool runs. Registration is the allowlist.
|
|
80
|
-
- **Policy engine**: value constraints (`in_range`, `one_of`, `matches`, `
|
|
81
|
-
cross-argument rules, human-approval flags, and budget caps (calls / per-tool / USD).
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
from
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
1
|
+
# toolwall
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/toolwall/)
|
|
4
|
+
[](https://pypi.org/project/toolwall/)
|
|
5
|
+
[](https://github.com/Dev-Saif-Ops/toolwall/blob/main/toolwall/LICENSE)
|
|
6
|
+
[](https://github.com/Dev-Saif-Ops/toolwall)
|
|
7
|
+
|
|
8
|
+
**The security gateway for AI agent tool calls.**
|
|
9
|
+
|
|
10
|
+
Your LLM can generate a *valid* tool call. That doesn't mean it's *safe* to execute.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
delete_records(filter={}) # perfectly valid JSON. whole table gone.
|
|
14
|
+
send_email(to="attacker@evil.com") # recipient injected via a poisoned web page
|
|
15
|
+
transfer_money(amount=999999999) # schema-valid. every field the right type.
|
|
16
|
+
db_query(limit=10_000_000) # production melts.
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Every one of these passes JSON schema validation. Structured outputs, schemas, and
|
|
20
|
+
content moderation all wave them through. `toolwall` is the fail-closed checkpoint
|
|
21
|
+
between the LLM's tool call and execution that blocks them.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install toolwall
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Zero required dependencies. Works with OpenAI, Anthropic, and Gemini native tool
|
|
32
|
+
calling (and plain dicts). Python 3.10+.
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from toolwall import ToolWall, Policy, ToolSchema, in_range, not_empty
|
|
38
|
+
|
|
39
|
+
wall = ToolWall() # default-deny, secret detection + audit on
|
|
40
|
+
|
|
41
|
+
wall.register("db_query", db_query,
|
|
42
|
+
schema=ToolSchema(required=["q"], types={"q": str, "limit": int}),
|
|
43
|
+
policy=Policy(constraints={"limit": in_range(1, 100)}))
|
|
44
|
+
wall.register("delete_records", delete_records,
|
|
45
|
+
schema=ToolSchema(required=["filter"], types={"filter": dict}),
|
|
46
|
+
policy=Policy(constraints={"filter": not_empty}, require_approval=True))
|
|
47
|
+
wall.budget(max_calls=20)
|
|
48
|
+
|
|
49
|
+
result = wall.call("delete_records", {"filter": {}})
|
|
50
|
+
# result.blocked -> True
|
|
51
|
+
# result.reason -> "policy violation: arg 'filter' rejected by not_empty"
|
|
52
|
+
|
|
53
|
+
results = wall.guard(openai_response) # or gate a raw OpenAI/Anthropic/Gemini response
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Only an `ALLOW` verdict runs the tool. `Gate` is the lower-level primitive underneath.
|
|
57
|
+
|
|
58
|
+
## What it stops
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
wall.call("delete_records", {"filter": {}})
|
|
62
|
+
# BLOCKED: policy violation: arg 'filter' rejected by not_empty
|
|
63
|
+
|
|
64
|
+
wall.call("send_email", {"to": "ops@ourco.com", "body": "aws key AKIA..."})
|
|
65
|
+
# BLOCKED: secret detected (aws-access-key) in arg 'body'
|
|
66
|
+
|
|
67
|
+
wall.call("db_query", {"q": "everything", "limit": 10_000_000})
|
|
68
|
+
# BLOCKED: policy violation: arg 'limit' rejected by in_range(1, 100)
|
|
69
|
+
|
|
70
|
+
wall.call("run_shell", {"cmd": "..."})
|
|
71
|
+
# BLOCKED: unknown tool: 'run_shell'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Everything not explicitly allowed is blocked. That is the whole idea.
|
|
75
|
+
|
|
76
|
+
## What it does
|
|
77
|
+
|
|
78
|
+
- **Fail-closed gate**: unknown tool, schema violation, policy violation, budget hit,
|
|
79
|
+
or unparseable payload all block *before* the tool runs. Registration is the allowlist.
|
|
80
|
+
- **Policy engine**: value constraints (`in_range`, `one_of`, `matches`, `email_domain`…),
|
|
81
|
+
cross-argument rules, human-approval flags, and budget caps (calls / per-tool / USD).
|
|
82
|
+
For recipients use `email_domain("ourco.com")`, not `ends_with`: a comma-separated
|
|
83
|
+
list ends with your domain too.
|
|
84
|
+
- **Shield, both directions**: detects secrets (AWS, OpenAI, GitHub, Stripe, Slack,
|
|
85
|
+
JWT, PEM, and high-entropy strings) in tool arguments *and in tool return values*,
|
|
86
|
+
then blocks or redacts them, including in dict keys, tuples (database rows), sets,
|
|
87
|
+
bytes and dataclasses. Output that cannot be scanned (generators, cursors, file
|
|
88
|
+
objects, XML elements, models, arrays) is withheld, so return plain data;
|
|
89
|
+
redaction never modifies the tool's own objects. Reasons, reports
|
|
90
|
+
and the audit log are scrubbed of detected secrets.
|
|
91
|
+
- **Dry-run**: run your whole agent with `dry_run=True`: nothing executes, and
|
|
92
|
+
`gate.report()` tells you what it *would* have done. `suggest_policies(gate)` drafts
|
|
93
|
+
a starter policy from the calls it observed.
|
|
94
|
+
- **MCP guard**: `MCPGuard` runs the same check and execute path (budget, dry-run,
|
|
95
|
+
receipts, output scanning) in front of a function that forwards to your MCP server.
|
|
96
|
+
- **Audit trail**: every verdict exported to JSON/CSV.
|
|
97
|
+
|
|
98
|
+
## Why not just the guardrails in my agent framework?
|
|
99
|
+
|
|
100
|
+
Use those too. Two things make `toolwall` different from security bundled into one
|
|
101
|
+
workspace or framework:
|
|
102
|
+
|
|
103
|
+
- **It's an allowlist, not a blocklist.** Bundled protections usually ship a list of
|
|
104
|
+
dangerous patterns to deny, so anything the authors did not anticipate gets through.
|
|
105
|
+
Here, registration *is* the allowlist: everything not explicitly allowed is blocked.
|
|
106
|
+
- **It runs inside your agent, not instead of it.** A workspace's built-in security
|
|
107
|
+
protects that workspace. `toolwall` is a zero-dependency library that drops into the
|
|
108
|
+
agent you already have, on any framework, and speaks OpenAI, Anthropic, Gemini, and MCP.
|
|
109
|
+
|
|
110
|
+
It is **not** a sandbox. A sandbox isolates the process; `toolwall` authorizes the call.
|
|
111
|
+
Production setups want both.
|
|
112
|
+
|
|
113
|
+
## Dry-run first
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from toolwall import Gate, Meter, suggest_policies
|
|
117
|
+
|
|
118
|
+
gate = Gate(default="deny", dry_run=True, meter=Meter())
|
|
119
|
+
# ... run your agent; ALLOW calls are simulated, never executed ...
|
|
120
|
+
print(gate.report()) # verdict counts, blocked reasons, secrets caught
|
|
121
|
+
print(suggest_policies(gate)) # a draft policy from observed calls, for you to review
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Guard an MCP server
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from toolwall import Gate, MCPGuard
|
|
128
|
+
|
|
129
|
+
guard = MCPGuard(gate, forward=call_downstream_mcp_server)
|
|
130
|
+
decision = guard.handle(tool_name, args) # only ALLOW is forwarded
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`forward` is your own function that calls the downstream server; no MCP transport
|
|
134
|
+
ships yet. `pip install "toolwall[mcp]"` only installs the `mcp` package.
|
|
135
|
+
|
|
136
|
+
## Examples
|
|
137
|
+
|
|
138
|
+
Runnable scripts live in the [repo `examples/` folder](https://github.com/Dev-Saif-Ops/toolwall/tree/main/toolwall/examples):
|
|
139
|
+
|
|
140
|
+
- **quickstart.py**: six gated scenarios (allow, unknown tool, out-of-range, empty-filter delete, approval hold, secret block). No API key.
|
|
141
|
+
- **dangerous_agent_demo.py**: an off-the-rails agent replayed with vs without the gate. No API key.
|
|
142
|
+
- **mcp_guard_demo.py**: the gate in front of an MCP-style server. No API key.
|
|
143
|
+
- **live_gemini_agent.py**: a real Gemini agent using native function calling, gated by toolwall. Needs `GEMINI_API_KEY`.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
git clone https://github.com/Dev-Saif-Ops/toolwall && cd toolwall/toolwall
|
|
147
|
+
python examples/quickstart.py
|
|
148
|
+
python examples/live_gemini_agent.py # set GEMINI_API_KEY first
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Honest status
|
|
152
|
+
|
|
153
|
+
`toolwall` is alpha. The published failure suite blocks **35 of 35 attack cases across
|
|
154
|
+
11 classes with 0 false blocks on its 14 clean cases**, at sub-millisecond per-call
|
|
155
|
+
overhead for small arguments (about 2 ms at 50 KB). It is a regression suite for one
|
|
156
|
+
reference config, not a coverage measure, and it lists ordinary text the shield is
|
|
157
|
+
known to flag. Secret
|
|
158
|
+
detection is pattern + entropy based and is **never 100%**. Structureless passwords are
|
|
159
|
+
out of scope, and the suite report states exactly what is and is not proven. Every claim
|
|
160
|
+
about toolwall cites that report, nothing broader.
|
|
161
|
+
|
|
162
|
+
**The verdict covers the call, not the state of the world.** An approved
|
|
163
|
+
`delete_records(id=42)` deletes whatever 42 points to at execution time; if the resource
|
|
164
|
+
was swapped after the check, the receipt still matches because the argument never
|
|
165
|
+
changed. Argument integrity is not resource integrity. For resources that can change
|
|
166
|
+
owner or meaning, re-verify inside the tool's own transaction (for example
|
|
167
|
+
compare-and-swap on a version column); the gate cannot see your datastore.
|
|
168
|
+
|
|
169
|
+
## Links
|
|
170
|
+
|
|
171
|
+
- **Source, full docs, and the failure suite:** https://github.com/Dev-Saif-Ops/toolwall
|
|
172
|
+
- **What happened to TOAP** (this project's predecessor, an honest postmortem):
|
|
173
|
+
the [`toap-v0.1-archive`](https://github.com/Dev-Saif-Ops/toolwall/tree/toap-v0.1-archive) branch
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT © Mohammad Safwan Athar
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
# Pin hatchling below the release that emits Metadata-Version 2.4/2.5.
|
|
3
|
-
# PyPI's upload endpoint accepts up to 2.3; newer metadata is rejected with a 400.
|
|
4
|
-
requires = ["hatchling<1.27"]
|
|
5
|
-
build-backend = "hatchling.build"
|
|
6
|
-
|
|
7
|
-
[project]
|
|
8
|
-
name = "toolwall"
|
|
9
|
-
version = "0.4.
|
|
10
|
-
description = "Security gateway for AI agent tool calls: fail-closed schema + policy + secret-detection gate before any tool executes"
|
|
11
|
-
readme = "README.md"
|
|
12
|
-
license = { text = "MIT" }
|
|
13
|
-
requires-python = ">=3.10"
|
|
14
|
-
authors = [{ name = "Mohammad Safwan Athar" }]
|
|
15
|
-
keywords = [
|
|
16
|
-
"ai", "agents", "ai-agents", "llm", "llm-security", "ai-agent-security",
|
|
17
|
-
"agent-security", "tool-calling", "tool-call-validation", "guardrails",
|
|
18
|
-
"security", "ai-safety", "mcp", "mcp-security", "mcp-firewall", "mcp-proxy",
|
|
19
|
-
"prompt-injection", "secret-detection", "agent-sandbox", "firewall",
|
|
20
|
-
]
|
|
21
|
-
classifiers = [
|
|
22
|
-
"Development Status :: 3 - Alpha",
|
|
23
|
-
"Intended Audience :: Developers",
|
|
24
|
-
"License :: OSI Approved :: MIT License",
|
|
25
|
-
"Programming Language :: Python :: 3",
|
|
26
|
-
"Topic :: Software Development :: Libraries",
|
|
27
|
-
"Topic :: Security",
|
|
28
|
-
]
|
|
29
|
-
dependencies = []
|
|
30
|
-
|
|
31
|
-
[project.optional-dependencies]
|
|
32
|
-
mcp = ["mcp>=1.0.0"]
|
|
33
|
-
dev = ["pytest>=8.0", "pytest-cov>=4.0"]
|
|
34
|
-
|
|
35
|
-
[project.scripts]
|
|
36
|
-
toolwall = "toolwall.cli:main"
|
|
37
|
-
|
|
38
|
-
[project.urls]
|
|
39
|
-
Homepage = "https://github.com/Dev-Saif-Ops/toolwall"
|
|
40
|
-
|
|
41
|
-
[tool.hatch.build.targets.wheel]
|
|
42
|
-
packages = ["src/toolwall"]
|
|
43
|
-
|
|
44
|
-
[tool.hatch.build.targets.sdist]
|
|
45
|
-
include = ["src/toolwall", "README.md", "LICENSE"]
|
|
46
|
-
|
|
47
|
-
[tool.pytest.ini_options]
|
|
48
|
-
testpaths = ["tests"]
|
|
49
|
-
pythonpath = ["src"]
|
|
1
|
+
[build-system]
|
|
2
|
+
# Pin hatchling below the release that emits Metadata-Version 2.4/2.5.
|
|
3
|
+
# PyPI's upload endpoint accepts up to 2.3; newer metadata is rejected with a 400.
|
|
4
|
+
requires = ["hatchling<1.27"]
|
|
5
|
+
build-backend = "hatchling.build"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "toolwall"
|
|
9
|
+
version = "0.4.2"
|
|
10
|
+
description = "Security gateway for AI agent tool calls: fail-closed schema + policy + secret-detection gate before any tool executes"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
license = { text = "MIT" }
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
authors = [{ name = "Mohammad Safwan Athar" }]
|
|
15
|
+
keywords = [
|
|
16
|
+
"ai", "agents", "ai-agents", "llm", "llm-security", "ai-agent-security",
|
|
17
|
+
"agent-security", "tool-calling", "tool-call-validation", "guardrails",
|
|
18
|
+
"security", "ai-safety", "mcp", "mcp-security", "mcp-firewall", "mcp-proxy",
|
|
19
|
+
"prompt-injection", "secret-detection", "agent-sandbox", "firewall",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Topic :: Software Development :: Libraries",
|
|
27
|
+
"Topic :: Security",
|
|
28
|
+
]
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
mcp = ["mcp>=1.0.0"]
|
|
33
|
+
dev = ["pytest>=8.0", "pytest-cov>=4.0"]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
toolwall = "toolwall.cli:main"
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/Dev-Saif-Ops/toolwall"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/toolwall"]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.sdist]
|
|
45
|
+
include = ["src/toolwall", "README.md", "LICENSE"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
testpaths = ["tests"]
|
|
49
|
+
pythonpath = ["src"]
|