tollgate 1.0.5__tar.gz → 1.4.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.
- tollgate-1.4.0/.gitignore +35 -0
- tollgate-1.4.0/ARCHITECTURE.md +579 -0
- tollgate-1.4.0/CHANGELOG.md +66 -0
- tollgate-1.4.0/FEATURES.md +837 -0
- tollgate-1.4.0/PKG-INFO +393 -0
- tollgate-1.4.0/QUICKSTART.md +473 -0
- tollgate-1.4.0/README.md +358 -0
- tollgate-1.4.0/SECURITY.md +356 -0
- tollgate-1.4.0/examples/security_hardened/README.md +70 -0
- tollgate-1.4.0/examples/security_hardened/audit.jsonl +34 -0
- tollgate-1.4.0/examples/security_hardened/demo.py +340 -0
- tollgate-1.4.0/examples/security_hardened/manifest.yaml +62 -0
- tollgate-1.4.0/examples/security_hardened/manifest.yaml.sig +1 -0
- tollgate-1.4.0/examples/security_hardened/policy.yaml +63 -0
- tollgate-1.4.0/examples/security_hardened/test_scenarios.yaml +123 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/pyproject.toml +18 -2
- {tollgate-1.0.5 → tollgate-1.4.0}/specs/audit_event.schema.json +2 -1
- tollgate-1.4.0/specs/identity.schema.json +64 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/__init__.py +34 -2
- tollgate-1.4.0/src/tollgate/anomaly_detector.py +396 -0
- tollgate-1.4.0/src/tollgate/audit.py +136 -0
- tollgate-1.4.0/src/tollgate/backends/__init__.py +37 -0
- tollgate-1.4.0/src/tollgate/backends/redis_store.py +411 -0
- tollgate-1.4.0/src/tollgate/backends/sqlite_store.py +458 -0
- tollgate-1.4.0/src/tollgate/circuit_breaker.py +206 -0
- tollgate-1.4.0/src/tollgate/context_monitor.py +292 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/exceptions.py +20 -0
- tollgate-1.4.0/src/tollgate/manifest_signing.py +90 -0
- tollgate-1.4.0/src/tollgate/network_guard.py +114 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/policy.py +37 -0
- tollgate-1.4.0/src/tollgate/policy_testing.py +360 -0
- tollgate-1.4.0/src/tollgate/rate_limiter.py +162 -0
- tollgate-1.4.0/src/tollgate/registry.py +281 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/tower.py +182 -11
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/types.py +21 -1
- tollgate-1.4.0/src/tollgate/verification.py +81 -0
- tollgate-1.4.0/tests/test_defense_in_depth.py +1146 -0
- tollgate-1.4.0/tests/test_resilience_protection.py +1108 -0
- tollgate-1.4.0/tests/test_security_hardening.py +816 -0
- tollgate-1.0.5/.gitignore +0 -12
- tollgate-1.0.5/CHANGELOG.md +0 -27
- tollgate-1.0.5/PKG-INFO +0 -144
- tollgate-1.0.5/QUICKSTART.md +0 -136
- tollgate-1.0.5/README.md +0 -119
- tollgate-1.0.5/SECURITY.md +0 -29
- tollgate-1.0.5/specs/identity.schema.json +0 -12
- tollgate-1.0.5/src/tollgate/audit.py +0 -47
- tollgate-1.0.5/src/tollgate/registry.py +0 -58
- {tollgate-1.0.5 → tollgate-1.4.0}/.claude/settings.local.json +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/COMPARISON.md +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/CONTRIBUTING.md +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/LICENSE +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/Makefile +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mcp_minimal/audit.jsonl +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mcp_minimal/demo.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mcp_minimal/manifest.yaml +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mcp_minimal/policy.yaml +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/README.md +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/agent.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/demo.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/manifest.yaml +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/tickets.json +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/mock_tickets/tools.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/strands_minimal/audit.jsonl +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/strands_minimal/demo.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/strands_minimal/manifest.yaml +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/examples/strands_minimal/policy.yaml +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/policies/default.yaml +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/specs/decision.schema.json +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/specs/intent.schema.json +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/specs/tool_request.schema.json +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/approvals.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/grants.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/helpers.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/integrations/__init__.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/integrations/mcp.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/integrations/strands.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/interceptors/__init__.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/interceptors/base.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/interceptors/langchain.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/src/tollgate/interceptors/openai.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_adapters_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_audit_integrity_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_deferred_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_grants.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_helpers_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_integrations_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_policy_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_registry_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_security_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_tower_v1.py +0 -0
- {tollgate-1.0.5 → tollgate-1.4.0}/tests/test_v1_integrations.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.venv/
|
|
7
|
+
env/
|
|
8
|
+
venv/
|
|
9
|
+
ENV/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
|
|
14
|
+
# Testing
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
|
|
20
|
+
# Environment variables
|
|
21
|
+
.env
|
|
22
|
+
.env.*
|
|
23
|
+
|
|
24
|
+
# Office documents (requested)
|
|
25
|
+
*.docx
|
|
26
|
+
*.pptx
|
|
27
|
+
|
|
28
|
+
# Local demo files
|
|
29
|
+
examples/mock_tickets/audit.jsonl
|
|
30
|
+
examples/mock_tickets/tickets.json.bak
|
|
31
|
+
|
|
32
|
+
# IDEs
|
|
33
|
+
.vscode/
|
|
34
|
+
.idea/
|
|
35
|
+
.cursor/
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
# Tollgate Architecture
|
|
2
|
+
|
|
3
|
+
This document explains Tollgate's architecture — how its components fit together and where they sit in the lifecycle of an AI agent tool call.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## The Problem
|
|
8
|
+
|
|
9
|
+
When an AI agent calls a tool — reading a file, sending an email, querying a database — there is no built-in mechanism to verify **who** is calling, **why** they're calling, or whether the call should be **allowed**. Tollgate is a runtime enforcement layer that sits between the agent and the tool, making every call go through a security checkpoint.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Core Idea: Identity + Intent + Policy
|
|
14
|
+
|
|
15
|
+
Every tool call in Tollgate is evaluated against three questions:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
19
|
+
│ WHO? │ │ WHY? │ │ WHAT? │
|
|
20
|
+
│ │ │ │ │ │
|
|
21
|
+
│ AgentContext │ │ Intent │ │ ToolRequest │
|
|
22
|
+
│ │ │ │ │ │
|
|
23
|
+
│ agent_id │ │ action │ │ tool │
|
|
24
|
+
│ version │ │ reason │ │ action │
|
|
25
|
+
│ owner │ │ confidence │ │ effect │
|
|
26
|
+
│ delegated_by │ │ │ │ params │
|
|
27
|
+
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
|
28
|
+
│ │ │
|
|
29
|
+
└────────────────────┼────────────────────┘
|
|
30
|
+
│
|
|
31
|
+
▼
|
|
32
|
+
┌─────────────────┐
|
|
33
|
+
│ ControlTower │
|
|
34
|
+
│ (Enforcer) │
|
|
35
|
+
└────────┬────────┘
|
|
36
|
+
│
|
|
37
|
+
▼
|
|
38
|
+
┌─────────────────┐
|
|
39
|
+
│ Decision │
|
|
40
|
+
│ │
|
|
41
|
+
│ ALLOW │ ASK │ │
|
|
42
|
+
│ DENY │
|
|
43
|
+
└─────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## The Three Core Entities
|
|
49
|
+
|
|
50
|
+
### AgentContext — "Who is calling?"
|
|
51
|
+
|
|
52
|
+
Represents the agent making the request. Immutable and optionally signed with HMAC-SHA256.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
AgentContext(
|
|
56
|
+
agent_id="weather-bot", # Unique identifier
|
|
57
|
+
version="2.1.0", # Agent version
|
|
58
|
+
owner="platform-team", # Responsible team
|
|
59
|
+
delegated_by=("orchestrator",) # Who delegated to this agent
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Key properties:**
|
|
64
|
+
- `delegation_depth` — How many agents are in the delegation chain
|
|
65
|
+
- `is_delegated` — Whether this agent was called by another agent
|
|
66
|
+
- `root_agent` — The original agent that started the chain
|
|
67
|
+
|
|
68
|
+
### Intent — "Why are they calling?"
|
|
69
|
+
|
|
70
|
+
Describes the agent's stated goal. This is the agent's claim about what it's trying to accomplish.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
Intent(
|
|
74
|
+
action="get_weather", # What the agent wants to do
|
|
75
|
+
reason="User asked about London", # Why
|
|
76
|
+
confidence=0.95, # LLM confidence (optional)
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### ToolRequest — "What exactly are they doing?"
|
|
81
|
+
|
|
82
|
+
The specific tool call being made. The `effect` field is the most important — it comes from the trusted Tool Registry, not the agent.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
ToolRequest(
|
|
86
|
+
tool="api:weather", # Tool identifier
|
|
87
|
+
action="get", # Action on the tool
|
|
88
|
+
effect=Effect.READ, # READ | WRITE | DELETE | NOTIFY | UNKNOWN
|
|
89
|
+
resource_type="weather", # What kind of resource
|
|
90
|
+
params={"city": "London"}, # Parameters
|
|
91
|
+
manifest_version="1.0.0", # Links to trusted registry
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Effects** determine the risk level:
|
|
96
|
+
|
|
97
|
+
| Effect | Risk | Default Policy |
|
|
98
|
+
|--------|------|----------------|
|
|
99
|
+
| `READ` | Low | Typically ALLOW |
|
|
100
|
+
| `WRITE` | Medium | Typically ASK |
|
|
101
|
+
| `DELETE` | High | Typically ASK or DENY |
|
|
102
|
+
| `NOTIFY` | Medium | Typically ALLOW |
|
|
103
|
+
| `UNKNOWN` | Highest | Always DENY |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## ControlTower — The Enforcer
|
|
108
|
+
|
|
109
|
+
The `ControlTower` is the central class. It orchestrates all security checks and makes the final decision. You configure it once with all the security layers you want, then route every tool call through it.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
tower = ControlTower(
|
|
113
|
+
# Required
|
|
114
|
+
policy=YamlPolicyEvaluator("policy.yaml"),
|
|
115
|
+
approver=CliApprover(),
|
|
116
|
+
audit=JsonlAuditSink("audit.jsonl"),
|
|
117
|
+
|
|
118
|
+
# Optional security layers
|
|
119
|
+
registry=ToolRegistry("manifest.yaml"),
|
|
120
|
+
grant_store=InMemoryGrantStore(),
|
|
121
|
+
rate_limiter=InMemoryRateLimiter([...]),
|
|
122
|
+
circuit_breaker=InMemoryCircuitBreaker(...),
|
|
123
|
+
network_guard=NetworkGuard(...),
|
|
124
|
+
verify_fn=make_verifier(secret),
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## The Enforcement Pipeline
|
|
131
|
+
|
|
132
|
+
When `tower.execute_async()` is called, the request goes through these checks **in order**. Any check can halt the pipeline.
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
136
|
+
│ ENFORCEMENT PIPELINE │
|
|
137
|
+
│ │
|
|
138
|
+
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
139
|
+
│ │ 1. IDENTITY VERIFICATION │ │
|
|
140
|
+
│ │ verify_fn(agent_ctx) → signed? not tampered? │ │
|
|
141
|
+
│ │ ✗ → TollgateDenied │ │
|
|
142
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
143
|
+
│ │ ✓ │
|
|
144
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
145
|
+
│ │ 2. CIRCUIT BREAKER │ │
|
|
146
|
+
│ │ Is this tool in OPEN state? (too many recent fails) │ │
|
|
147
|
+
│ │ ✗ → TollgateDenied │ │
|
|
148
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
149
|
+
│ │ ✓ │
|
|
150
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
151
|
+
│ │ 3. RATE LIMITING │ │
|
|
152
|
+
│ │ Sliding window check per agent/tool/effect │ │
|
|
153
|
+
│ │ ✗ → TollgateRateLimited (with retry_after) │ │
|
|
154
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
155
|
+
│ │ ✓ │
|
|
156
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
157
|
+
│ │ 4. POLICY EVALUATION │ │
|
|
158
|
+
│ │ YAML rules: effect, tool, agent, delegation, metadata │ │
|
|
159
|
+
│ │ → ALLOW │ ASK │ DENY │ │
|
|
160
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
161
|
+
│ │ │
|
|
162
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
163
|
+
│ │ 5. NETWORK GUARD (if not DENY) │ │
|
|
164
|
+
│ │ Check all URL params against global allowlist/blocklist│ │
|
|
165
|
+
│ │ ✗ → TollgateConstraintViolation │ │
|
|
166
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
167
|
+
│ │ │
|
|
168
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
169
|
+
│ │ 6. PARAMETER VALIDATION (if not DENY) │ │
|
|
170
|
+
│ │ JSON Schema: type, required, pattern, enum, range │ │
|
|
171
|
+
│ │ ✗ → TollgateDenied │ │
|
|
172
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
173
|
+
│ │ │
|
|
174
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
175
|
+
│ │ 7. PER-TOOL CONSTRAINTS (if not DENY) │ │
|
|
176
|
+
│ │ URL patterns, param value constraints │ │
|
|
177
|
+
│ │ ✗ → TollgateConstraintViolation │ │
|
|
178
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
179
|
+
│ │ │
|
|
180
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
181
|
+
│ │ 8. DECISION ROUTING │ │
|
|
182
|
+
│ │ │ │
|
|
183
|
+
│ │ DENY → Log + TollgateDenied │ │
|
|
184
|
+
│ │ │ │
|
|
185
|
+
│ │ ASK → Check grants first: │ │
|
|
186
|
+
│ │ ├─ Grant found? → Execute (skip approval) │ │
|
|
187
|
+
│ │ └─ No grant? → Request human approval │ │
|
|
188
|
+
│ │ ├─ Approved → Execute │ │
|
|
189
|
+
│ │ ├─ Denied → TollgateApprovalDenied│
|
|
190
|
+
│ │ └─ Deferred → TollgateDeferred│ │
|
|
191
|
+
│ │ │ │
|
|
192
|
+
│ │ ALLOW → Execute │ │
|
|
193
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
194
|
+
│ │ │
|
|
195
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
196
|
+
│ │ 9. TOOL EXECUTION │ │
|
|
197
|
+
│ │ Run the actual tool function │ │
|
|
198
|
+
│ │ ├─ Success → Record in circuit breaker │ │
|
|
199
|
+
│ │ └─ Failure → Record failure in circuit breaker │ │
|
|
200
|
+
│ └─────────────────────┬───────────────────────────────────┘ │
|
|
201
|
+
│ │ │
|
|
202
|
+
│ ┌─────────────────────▼───────────────────────────────────┐ │
|
|
203
|
+
│ │ 10. AUDIT LOGGING │ │
|
|
204
|
+
│ │ Every outcome is logged with full context: │ │
|
|
205
|
+
│ │ correlation_id, request_hash, decision, outcome, │ │
|
|
206
|
+
│ │ agent, intent, tool_request, timestamps │ │
|
|
207
|
+
│ └─────────────────────────────────────────────────────────┘ │
|
|
208
|
+
│ │
|
|
209
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Component Map
|
|
215
|
+
|
|
216
|
+
Here's how every component relates to the pipeline:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
┌────────────────────────────────────────────────────────────────────────┐
|
|
220
|
+
│ CONFIGURATION │
|
|
221
|
+
│ │
|
|
222
|
+
│ manifest.yaml policy.yaml secrets │
|
|
223
|
+
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
|
|
224
|
+
│ │ Tools: │ │ Rules: │ │ Agent signing key │ │
|
|
225
|
+
│ │ effect │ │ effect │ │ Manifest signing key │ │
|
|
226
|
+
│ │ schema │ │ decision │ └──────────────────────┘ │
|
|
227
|
+
│ │ constraints│ │ delegation│ │
|
|
228
|
+
│ └─────┬──────┘ └─────┬──────┘ │
|
|
229
|
+
│ │ │ │
|
|
230
|
+
│ ▼ ▼ │
|
|
231
|
+
│ ┌────────────┐ ┌──────────────────┐ │
|
|
232
|
+
│ │ToolRegistry│ │YamlPolicyEvaluator│ │
|
|
233
|
+
│ └────────────┘ └──────────────────┘ │
|
|
234
|
+
│ │
|
|
235
|
+
├────────────────────────────────────────────────────────────────────────┤
|
|
236
|
+
│ RUNTIME ENFORCEMENT │
|
|
237
|
+
│ │
|
|
238
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
239
|
+
│ │ ControlTower │ │
|
|
240
|
+
│ │ │ │
|
|
241
|
+
│ │ Pre-execution guards: │ │
|
|
242
|
+
│ │ ┌──────────┐ ┌───────────────┐ ┌──────────────┐ │ │
|
|
243
|
+
│ │ │verify_fn │ │CircuitBreaker │ │ RateLimiter │ │ │
|
|
244
|
+
│ │ └──────────┘ └───────────────┘ └──────────────┘ │ │
|
|
245
|
+
│ │ │ │
|
|
246
|
+
│ │ Policy + validation: │ │
|
|
247
|
+
│ │ ┌─────────────┐ ┌────────────┐ ┌──────────────┐ │ │
|
|
248
|
+
│ │ │PolicyEvaluator│ │NetworkGuard│ │ToolRegistry │ │ │
|
|
249
|
+
│ │ └─────────────┘ └────────────┘ └──────────────┘ │ │
|
|
250
|
+
│ │ │ │
|
|
251
|
+
│ │ Approval flow: │ │
|
|
252
|
+
│ │ ┌────────────┐ ┌──────────┐ │ │
|
|
253
|
+
│ │ │ GrantStore │ │ Approver │ │ │
|
|
254
|
+
│ │ └────────────┘ └──────────┘ │ │
|
|
255
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
256
|
+
│ │
|
|
257
|
+
├────────────────────────────────────────────────────────────────────────┤
|
|
258
|
+
│ POST-EXECUTION │
|
|
259
|
+
│ │
|
|
260
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
261
|
+
│ │ Audit Pipeline │ │
|
|
262
|
+
│ │ ┌────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │
|
|
263
|
+
│ │ │ JsonlAuditSink │ │WebhookAudit │ │AnomalyDetector │ │ │
|
|
264
|
+
│ │ │ (file logging) │ │(alerts) │ │(pattern analysis)│ │ │
|
|
265
|
+
│ │ └────────────────┘ └──────────────┘ └─────────────────┘ │ │
|
|
266
|
+
│ │ ↑ ↑ ↑ │ │
|
|
267
|
+
│ │ └──────────────────┼─────────────────┘ │ │
|
|
268
|
+
│ │ ┌────────┴────────┐ │ │
|
|
269
|
+
│ │ │CompositeAuditSink│ │ │
|
|
270
|
+
│ │ └─────────────────┘ │ │
|
|
271
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
272
|
+
│ │
|
|
273
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
274
|
+
│ │ Monitoring (out-of-band) │ │
|
|
275
|
+
│ │ ┌────────────────────────┐ ┌──────────────────────────┐ │ │
|
|
276
|
+
│ │ │ContextIntegrityMonitor │ │ PolicyTestRunner (CI) │ │ │
|
|
277
|
+
│ │ │(memory poisoning check)│ │ (policy regression test) │ │ │
|
|
278
|
+
│ │ └────────────────────────┘ └──────────────────────────┘ │ │
|
|
279
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
280
|
+
│ │
|
|
281
|
+
├────────────────────────────────────────────────────────────────────────┤
|
|
282
|
+
│ PERSISTENT STORAGE │
|
|
283
|
+
│ │
|
|
284
|
+
│ In-Memory (dev/test) SQLite (single-process) │
|
|
285
|
+
│ ┌───────────────────┐ ┌──────────────────┐ │
|
|
286
|
+
│ │InMemoryGrantStore │ │SQLiteGrantStore │ │
|
|
287
|
+
│ │InMemoryApprovalSt.│ │SQLiteApprovalStore│ │
|
|
288
|
+
│ └───────────────────┘ └──────────────────┘ │
|
|
289
|
+
│ │
|
|
290
|
+
│ Redis (distributed) │
|
|
291
|
+
│ ┌──────────────────┐ │
|
|
292
|
+
│ │RedisGrantStore │ │
|
|
293
|
+
│ │RedisApprovalStore│ │
|
|
294
|
+
│ └──────────────────┘ │
|
|
295
|
+
└────────────────────────────────────────────────────────────────────────┘
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Where Tollgate Fits in the Agent Lifecycle
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
304
|
+
│ AI AGENT LIFECYCLE │
|
|
305
|
+
│ │
|
|
306
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
307
|
+
│ │ User │───▶│ LLM │───▶│ Agent │───▶│ Tool │ │
|
|
308
|
+
│ │ Input │ │ Reasoning│ │ Decision │ │ Execution│ │
|
|
309
|
+
│ └──────────┘ └──────────┘ └────┬─────┘ └──────────┘ │
|
|
310
|
+
│ │ │
|
|
311
|
+
│ │ "I need to call │
|
|
312
|
+
│ │ api:weather.get" │
|
|
313
|
+
│ │ │
|
|
314
|
+
│ ┌───────▼───────┐ │
|
|
315
|
+
│ │ │ │
|
|
316
|
+
│ │ TOLLGATE │ │
|
|
317
|
+
│ │ │ │
|
|
318
|
+
│ │ WHO + WHY + │ │
|
|
319
|
+
│ │ WHAT → OK? │ │
|
|
320
|
+
│ │ │ │
|
|
321
|
+
│ └───────┬───────┘ │
|
|
322
|
+
│ │ │
|
|
323
|
+
│ ┌────────┼────────┐ │
|
|
324
|
+
│ │ │ │ │
|
|
325
|
+
│ ALLOW ASK DENY │
|
|
326
|
+
│ │ │ │ │
|
|
327
|
+
│ ▼ ▼ ▼ │
|
|
328
|
+
│ Execute Human Block │
|
|
329
|
+
│ Tool Review + Log │
|
|
330
|
+
│ │
|
|
331
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Tollgate operates at the **tool-call boundary** — the moment an agent decides to call an external tool. It does not interfere with the LLM's reasoning or the agent's planning. It only enforces rules at the point of action.
|
|
335
|
+
|
|
336
|
+
### Multi-Agent Scenarios
|
|
337
|
+
|
|
338
|
+
In multi-agent systems, Tollgate tracks the delegation chain:
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
342
|
+
│ Orchestrator │────▶│ Router │────▶│ Worker │
|
|
343
|
+
│ │ │ │ │ │
|
|
344
|
+
│ agent_id: │ │ agent_id: │ │ agent_id: │
|
|
345
|
+
│ "orch" │ │ "router" │ │ "worker" │
|
|
346
|
+
└──────────────┘ └──────────────┘ │ │
|
|
347
|
+
│ delegated_by:│
|
|
348
|
+
│ ("orch", │
|
|
349
|
+
│ "router") │
|
|
350
|
+
└──────┬───────┘
|
|
351
|
+
│
|
|
352
|
+
┌──────▼───────┐
|
|
353
|
+
│ ControlTower │
|
|
354
|
+
│ │
|
|
355
|
+
│ Checks: │
|
|
356
|
+
│ • depth ≤ 2? │
|
|
357
|
+
│ • "orch" in │
|
|
358
|
+
│ allowed? │
|
|
359
|
+
│ • "router" in │
|
|
360
|
+
│ blocked? │
|
|
361
|
+
└───────────────┘
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Policy System
|
|
367
|
+
|
|
368
|
+
Policies are YAML files with ordered rules. First match wins.
|
|
369
|
+
|
|
370
|
+
```yaml
|
|
371
|
+
rules:
|
|
372
|
+
- id: allow_reads
|
|
373
|
+
effect: read # Match by effect
|
|
374
|
+
decision: ALLOW
|
|
375
|
+
|
|
376
|
+
- id: trusted_delegation
|
|
377
|
+
agent:
|
|
378
|
+
allowed_delegators: # Only these can delegate
|
|
379
|
+
- "orchestrator"
|
|
380
|
+
max_delegation_depth: 2 # Chain length limit
|
|
381
|
+
effect: write
|
|
382
|
+
decision: ALLOW
|
|
383
|
+
|
|
384
|
+
- id: ask_writes
|
|
385
|
+
effect: write
|
|
386
|
+
decision: ASK # Human must approve
|
|
387
|
+
|
|
388
|
+
- id: deny_default
|
|
389
|
+
decision: DENY # Catch-all: fail closed
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Matching capabilities:**
|
|
393
|
+
- `effect` — READ, WRITE, DELETE, NOTIFY
|
|
394
|
+
- `tool`, `action`, `resource_type` — Exact match
|
|
395
|
+
- `agent.agent_id`, `agent.version` — Agent attributes
|
|
396
|
+
- `agent.allowed_delegators` — Delegation trust list
|
|
397
|
+
- `agent.blocked_delegators` — Delegation block list
|
|
398
|
+
- `agent.max_delegation_depth` — Chain depth limit
|
|
399
|
+
- `agent.deny_delegated` — Skip rule for delegated agents
|
|
400
|
+
- `when` — Metadata conditions with operators (`>=`, `<=`, `==`, `!=`)
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Approvals & Grants
|
|
405
|
+
|
|
406
|
+
When a policy returns `ASK`, two mechanisms can resolve it:
|
|
407
|
+
|
|
408
|
+
### Grants (Pre-approvals)
|
|
409
|
+
|
|
410
|
+
Grants are pre-authorized permissions that bypass the human approval step. They support wildcards and expiration.
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
Grant(
|
|
414
|
+
agent_id="batch-agent", # or None for any agent
|
|
415
|
+
effect=Effect.WRITE, # or None for any effect
|
|
416
|
+
tool="api:*", # Prefix matching
|
|
417
|
+
expires_at=time.time()+3600 # 1 hour
|
|
418
|
+
)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Approvers
|
|
422
|
+
|
|
423
|
+
If no grant matches, an `Approver` handles the request:
|
|
424
|
+
|
|
425
|
+
| Approver | Use Case |
|
|
426
|
+
|----------|----------|
|
|
427
|
+
| `AutoApprover` | Testing — auto-approves READs, denies everything else |
|
|
428
|
+
| `CliApprover` | Development — interactive terminal prompt |
|
|
429
|
+
| `AsyncQueueApprover` | Production — external system decides via API |
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Audit Pipeline
|
|
434
|
+
|
|
435
|
+
Every decision is logged as a structured `AuditEvent`:
|
|
436
|
+
|
|
437
|
+
```
|
|
438
|
+
┌─────────────────────────────────────────────┐
|
|
439
|
+
│ AuditEvent │
|
|
440
|
+
│ │
|
|
441
|
+
│ timestamp, correlation_id, request_hash │
|
|
442
|
+
│ agent, intent, tool_request │
|
|
443
|
+
│ decision (ALLOW/ASK/DENY) │
|
|
444
|
+
│ outcome (EXECUTED/BLOCKED/FAILED) │
|
|
445
|
+
│ grant_id (if grant was used) │
|
|
446
|
+
│ approval_id (if approval was requested) │
|
|
447
|
+
│ result_summary (truncated) │
|
|
448
|
+
│ schema_version: "1.0" │
|
|
449
|
+
└──────────────────┬──────────────────────────┘
|
|
450
|
+
│
|
|
451
|
+
┌────────┼────────┐
|
|
452
|
+
▼ ▼ ▼
|
|
453
|
+
┌──────────┐ ┌──────┐ ┌─────────────────┐
|
|
454
|
+
│ JSONL │ │Webhook│ │AnomalyDetector │
|
|
455
|
+
│ file │ │alerts │ │ │
|
|
456
|
+
│ │ │(BLOCK,│ │ rate_spike │
|
|
457
|
+
│ append- │ │ DENY, │ │ error_burst │
|
|
458
|
+
│ only log │ │ FAIL) │ │ deny_surge │
|
|
459
|
+
└──────────┘ └──────┘ │ unusual_tool │
|
|
460
|
+
└─────────────────┘
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Sensitive parameters** (password, token, secret, api_key) are **redacted** before logging, not before enforcement.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Monitoring Components
|
|
468
|
+
|
|
469
|
+
These components operate **outside** the main enforcement pipeline:
|
|
470
|
+
|
|
471
|
+
### ContextIntegrityMonitor
|
|
472
|
+
|
|
473
|
+
Detects unauthorized changes to agent context between turns. Takes SHA-256 snapshots and verifies immutable fields haven't changed.
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
Turn 1: snapshot(context) → { system_prompt: sha256("..."), ... }
|
|
477
|
+
Turn 2: verify(context) → ✓ Valid
|
|
478
|
+
Turn 3: verify(context) → ✗ security_level changed! ALERT
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### AnomalyDetector
|
|
482
|
+
|
|
483
|
+
Plugs into the audit pipeline as an `AuditSink`. Analyzes patterns using z-score statistics over sliding windows.
|
|
484
|
+
|
|
485
|
+
| Alert Type | What It Detects |
|
|
486
|
+
|------------|-----------------|
|
|
487
|
+
| `rate_spike` | Call frequency suddenly increases |
|
|
488
|
+
| `error_burst` | Sudden increase in tool failures |
|
|
489
|
+
| `deny_surge` | Unusual number of policy denials |
|
|
490
|
+
| `unusual_tool` | Agent calling a tool it never used before |
|
|
491
|
+
|
|
492
|
+
### PolicyTestRunner
|
|
493
|
+
|
|
494
|
+
Runs declarative test scenarios against policies in CI. Catches regressions before deployment.
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
tollgate test-policy policy.yaml --scenarios test_scenarios.yaml
|
|
498
|
+
# Exit code 0 = all pass, 1 = failures
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Framework Integrations
|
|
504
|
+
|
|
505
|
+
Tollgate integrates with agent frameworks by wrapping their tool-calling mechanisms:
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
509
|
+
│ FRAMEWORK ADAPTERS │
|
|
510
|
+
│ │
|
|
511
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
512
|
+
│ │ MCP │ │ Strands │ │
|
|
513
|
+
│ │ │ │ │ │
|
|
514
|
+
│ │ TollgateMCPClient│ │ guard_tools() │ │
|
|
515
|
+
│ │ wraps base client│ │ wraps tool list │ │
|
|
516
|
+
│ └────────┬─────────┘ └────────┬─────────┘ │
|
|
517
|
+
│ │ │ │
|
|
518
|
+
│ └───────────┬───────────┘ │
|
|
519
|
+
│ ▼ │
|
|
520
|
+
│ ┌─────────────────┐ │
|
|
521
|
+
│ │ ControlTower │ │
|
|
522
|
+
│ │ .execute_async │ │
|
|
523
|
+
│ └─────────────────┘ │
|
|
524
|
+
│ │
|
|
525
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
526
|
+
│ │ LangChain │ │ OpenAI │ │
|
|
527
|
+
│ │ │ │ │ │
|
|
528
|
+
│ │ guard_tools() │ │ OpenAIToolRunner │ │
|
|
529
|
+
│ │ wraps LC tools │ │ wraps tool_calls│ │
|
|
530
|
+
│ └──────────────────┘ └──────────────────┘ │
|
|
531
|
+
└─────────────────────────────────────────────────────────────┘
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Each adapter:
|
|
535
|
+
1. Intercepts the tool call
|
|
536
|
+
2. Resolves tool name to registry format (e.g., `mcp:server.tool`)
|
|
537
|
+
3. Builds `AgentContext`, `Intent`, `ToolRequest`
|
|
538
|
+
4. Routes through `ControlTower.execute_async()`
|
|
539
|
+
5. Returns result or raises Tollgate exception
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## Exception Hierarchy
|
|
544
|
+
|
|
545
|
+
```
|
|
546
|
+
TollgateError (base)
|
|
547
|
+
├── TollgateDenied — Policy says no
|
|
548
|
+
├── TollgateApprovalDenied — Human said no (or timeout)
|
|
549
|
+
├── TollgateDeferred — Approval pending (async)
|
|
550
|
+
├── TollgateRateLimited — Too many calls (includes retry_after)
|
|
551
|
+
└── TollgateConstraintViolation — Parameter or URL constraint failed
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Every exception carries a descriptive `reason` so the agent (or operator) understands what went wrong.
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Storage Backends
|
|
559
|
+
|
|
560
|
+
| Backend | Best For | Dependencies | Features |
|
|
561
|
+
|---------|----------|-------------|----------|
|
|
562
|
+
| `InMemory*` | Testing, development | None | Fast, no persistence |
|
|
563
|
+
| `SQLite*` | Single-process production | None (stdlib) | WAL mode, persistent |
|
|
564
|
+
| `Redis*` | Distributed production | `redis[hiredis]` | TTL, pub/sub, multi-host |
|
|
565
|
+
|
|
566
|
+
All backends implement the same `GrantStore` and `ApprovalStore` protocols, so you can swap them without changing any other code.
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Design Principles
|
|
571
|
+
|
|
572
|
+
1. **Fail closed** — Unknown effects, missing rules, and unverified agents all result in DENY
|
|
573
|
+
2. **Developer controls metadata** — Tool effects come from the registry, not from agents
|
|
574
|
+
3. **Deterministic decisions** — No LLM in the enforcement loop; policies are pure logic
|
|
575
|
+
4. **Defense in depth** — Multiple independent layers; no single point of failure
|
|
576
|
+
5. **Async-first** — All enforcement is async; sync wrapper available for convenience
|
|
577
|
+
6. **Protocol-based** — Every component is a pluggable protocol; swap implementations freely
|
|
578
|
+
7. **Audit everything** — Every outcome (allow, deny, execute, fail) is logged with full context
|
|
579
|
+
8. **Least agency** — Grant only the minimum autonomy needed for safe, bounded tasks
|