composio-tealtiger 0.1.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.
- composio_tealtiger-0.1.0/PKG-INFO +188 -0
- composio_tealtiger-0.1.0/README.md +160 -0
- composio_tealtiger-0.1.0/composio_tealtiger/__init__.py +6 -0
- composio_tealtiger-0.1.0/composio_tealtiger/middleware.py +264 -0
- composio_tealtiger-0.1.0/composio_tealtiger.egg-info/PKG-INFO +188 -0
- composio_tealtiger-0.1.0/composio_tealtiger.egg-info/SOURCES.txt +10 -0
- composio_tealtiger-0.1.0/composio_tealtiger.egg-info/dependency_links.txt +1 -0
- composio_tealtiger-0.1.0/composio_tealtiger.egg-info/requires.txt +5 -0
- composio_tealtiger-0.1.0/composio_tealtiger.egg-info/top_level.txt +1 -0
- composio_tealtiger-0.1.0/pyproject.toml +45 -0
- composio_tealtiger-0.1.0/setup.cfg +4 -0
- composio_tealtiger-0.1.0/tests/test_governance.py +134 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: composio-tealtiger
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic governance middleware for Composio tool calls — policy enforcement, PII detection, cost tracking, and audit evidence.
|
|
5
|
+
Author-email: TealTiger <feedback@tealtiger.ai>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/agentguard-ai/composio-tealtiger
|
|
8
|
+
Project-URL: Documentation, https://docs.tealtiger.ai/integrations/composio
|
|
9
|
+
Project-URL: Repository, https://github.com/agentguard-ai/composio-tealtiger
|
|
10
|
+
Project-URL: Issues, https://github.com/agentguard-ai/composio-tealtiger/issues
|
|
11
|
+
Keywords: composio,tealtiger,governance,ai-agents,security,pii,guardrails
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: tealtiger>=1.1.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# composio-tealtiger
|
|
30
|
+
|
|
31
|
+
Deterministic governance middleware for [Composio](https://composio.dev) tool calls — policy enforcement, PII detection, cost tracking, and structured audit evidence using [TealTiger](https://github.com/agentguard-ai/tealtiger).
|
|
32
|
+
|
|
33
|
+
No LLM in the governance path. All policy evaluation is deterministic, adding <5ms latency.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install composio-tealtiger
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Zero-Config (Observe Mode)
|
|
44
|
+
|
|
45
|
+
Add governance to any Composio tool call with zero configuration. TealTiger observes all traffic, tracks costs, detects PII, and allows everything through — producing structured audit entries.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from composio import Composio
|
|
49
|
+
from composio_tealtiger import governance_modifiers
|
|
50
|
+
|
|
51
|
+
composio = Composio()
|
|
52
|
+
|
|
53
|
+
# Get tools with TealTiger governance (observe mode — zero config)
|
|
54
|
+
tools = composio.tools.get(
|
|
55
|
+
user_id="user_123",
|
|
56
|
+
toolkits=["github", "slack"],
|
|
57
|
+
**governance_modifiers()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Every tool call is now tracked: cost, PII detection, audit trail
|
|
61
|
+
# Nothing is blocked in observe mode — just visibility
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Enforce Mode (Policy Enforcement)
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from composio import Composio
|
|
68
|
+
from composio_tealtiger import governance_modifiers
|
|
69
|
+
from tealtiger import TealEngine
|
|
70
|
+
|
|
71
|
+
# Define governance policies
|
|
72
|
+
engine = TealEngine(policies=[
|
|
73
|
+
{"type": "tool_allowlist", "agent": "coder", "allowed": ["GITHUB_*", "HACKERNEWS_*"]},
|
|
74
|
+
{"type": "pii_block", "categories": ["ssn", "credit_card"]},
|
|
75
|
+
{"type": "cost_limit", "max_per_session": 5.00},
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
composio = Composio()
|
|
79
|
+
|
|
80
|
+
# Get tools with enforcing governance
|
|
81
|
+
tools = composio.tools.get(
|
|
82
|
+
user_id="user_123",
|
|
83
|
+
toolkits=["github", "gmail", "slack"],
|
|
84
|
+
**governance_modifiers(engine=engine, mode="ENFORCE")
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Agent can use GITHUB_* tools → ALLOWED
|
|
88
|
+
# Agent tries GMAIL_SEND_EMAIL → DENIED (not in allowlist)
|
|
89
|
+
# Agent passes SSN in tool args → DENIED (PII blocked)
|
|
90
|
+
# Agent exceeds $5 budget → DENIED (cost limit)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### With Kill Switch
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from tealtiger import freeze, unfreeze
|
|
97
|
+
|
|
98
|
+
# Emergency: stop all tool execution for an agent
|
|
99
|
+
freeze("coder-agent")
|
|
100
|
+
|
|
101
|
+
# After investigation, restore access
|
|
102
|
+
unfreeze("coder-agent")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## How It Works
|
|
106
|
+
|
|
107
|
+
`composio-tealtiger` uses Composio's native modifier hooks:
|
|
108
|
+
|
|
109
|
+
- **`beforeExecute`** — Evaluates governance policies before any tool executes. If the policy denies the action, execution is blocked before reaching the external service.
|
|
110
|
+
- **`afterExecute`** — Records the execution result in the audit trail and updates cost tracking.
|
|
111
|
+
- **`modifySchema`** — Optionally adds governance metadata (allowed/denied status) to tool schemas for AI context.
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
Agent → Composio → beforeExecute (TealTiger evaluates) → Tool Execution → afterExecute (audit)
|
|
115
|
+
↓ (if DENY)
|
|
116
|
+
GovernanceDenyError (tool never executes)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Features
|
|
120
|
+
|
|
121
|
+
| Feature | Observe | Enforce |
|
|
122
|
+
|---------|---------|---------|
|
|
123
|
+
| PII detection (email, SSN, credit card, phone) | ✅ report | ✅ block |
|
|
124
|
+
| Tool allowlisting per agent/role | — | ✅ |
|
|
125
|
+
| Cost tracking per tool call | ✅ | ✅ |
|
|
126
|
+
| Per-session budget limits | — | ✅ |
|
|
127
|
+
| Structured audit trail | ✅ | ✅ |
|
|
128
|
+
| Correlation IDs (UUID v4) | ✅ | ✅ |
|
|
129
|
+
| Kill switch (freeze/unfreeze) | ✅ | ✅ |
|
|
130
|
+
| Risk scoring | ✅ | ✅ |
|
|
131
|
+
|
|
132
|
+
## API Reference
|
|
133
|
+
|
|
134
|
+
### `governance_modifiers(engine=None, mode="OBSERVE", agent_id=None)`
|
|
135
|
+
|
|
136
|
+
Returns a dict of Composio modifier hooks configured for TealTiger governance.
|
|
137
|
+
|
|
138
|
+
**Parameters:**
|
|
139
|
+
- `engine` (TealEngine, optional) — Policy engine. If None, uses observe mode.
|
|
140
|
+
- `mode` (str) — `"OBSERVE"`, `"MONITOR"`, or `"ENFORCE"`. Default: `"OBSERVE"`.
|
|
141
|
+
- `agent_id` (str, optional) — Agent identifier. Auto-generated if not provided.
|
|
142
|
+
|
|
143
|
+
**Returns:** Dict with `beforeExecute`, `afterExecute`, and optionally `modifySchema` keys.
|
|
144
|
+
|
|
145
|
+
### `GovernanceDenyError`
|
|
146
|
+
|
|
147
|
+
Raised when a tool call is denied in ENFORCE mode.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from composio_tealtiger import GovernanceDenyError
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
result = composio.tools.execute("GMAIL_SEND_EMAIL", params, **governance_modifiers(engine=engine, mode="ENFORCE"))
|
|
154
|
+
except GovernanceDenyError as e:
|
|
155
|
+
print(f"Blocked: {e.decision['reason']}")
|
|
156
|
+
print(f"Codes: {e.decision['reason_codes']}")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Audit Trail
|
|
160
|
+
|
|
161
|
+
Every governance evaluation produces a structured audit entry:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
166
|
+
"timestamp_ms": 1720000000000,
|
|
167
|
+
"action": "DENY",
|
|
168
|
+
"mode": "ENFORCE",
|
|
169
|
+
"tool_slug": "GMAIL_SEND_EMAIL",
|
|
170
|
+
"toolkit_slug": "gmail",
|
|
171
|
+
"agent_id": "coder-agent",
|
|
172
|
+
"reason": "Tool not in allowlist for role 'coder'",
|
|
173
|
+
"reason_codes": ["TOOL_NOT_ALLOWED"],
|
|
174
|
+
"pii_detected": [],
|
|
175
|
+
"cost_tracked": 0.0,
|
|
176
|
+
"evaluation_time_ms": 0.42
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Requirements
|
|
181
|
+
|
|
182
|
+
- Python 3.9+
|
|
183
|
+
- `composio` >= 0.7.0
|
|
184
|
+
- `tealtiger` >= 1.1.0
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
Apache-2.0
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# composio-tealtiger
|
|
2
|
+
|
|
3
|
+
Deterministic governance middleware for [Composio](https://composio.dev) tool calls — policy enforcement, PII detection, cost tracking, and structured audit evidence using [TealTiger](https://github.com/agentguard-ai/tealtiger).
|
|
4
|
+
|
|
5
|
+
No LLM in the governance path. All policy evaluation is deterministic, adding <5ms latency.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install composio-tealtiger
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Zero-Config (Observe Mode)
|
|
16
|
+
|
|
17
|
+
Add governance to any Composio tool call with zero configuration. TealTiger observes all traffic, tracks costs, detects PII, and allows everything through — producing structured audit entries.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from composio import Composio
|
|
21
|
+
from composio_tealtiger import governance_modifiers
|
|
22
|
+
|
|
23
|
+
composio = Composio()
|
|
24
|
+
|
|
25
|
+
# Get tools with TealTiger governance (observe mode — zero config)
|
|
26
|
+
tools = composio.tools.get(
|
|
27
|
+
user_id="user_123",
|
|
28
|
+
toolkits=["github", "slack"],
|
|
29
|
+
**governance_modifiers()
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Every tool call is now tracked: cost, PII detection, audit trail
|
|
33
|
+
# Nothing is blocked in observe mode — just visibility
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Enforce Mode (Policy Enforcement)
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from composio import Composio
|
|
40
|
+
from composio_tealtiger import governance_modifiers
|
|
41
|
+
from tealtiger import TealEngine
|
|
42
|
+
|
|
43
|
+
# Define governance policies
|
|
44
|
+
engine = TealEngine(policies=[
|
|
45
|
+
{"type": "tool_allowlist", "agent": "coder", "allowed": ["GITHUB_*", "HACKERNEWS_*"]},
|
|
46
|
+
{"type": "pii_block", "categories": ["ssn", "credit_card"]},
|
|
47
|
+
{"type": "cost_limit", "max_per_session": 5.00},
|
|
48
|
+
])
|
|
49
|
+
|
|
50
|
+
composio = Composio()
|
|
51
|
+
|
|
52
|
+
# Get tools with enforcing governance
|
|
53
|
+
tools = composio.tools.get(
|
|
54
|
+
user_id="user_123",
|
|
55
|
+
toolkits=["github", "gmail", "slack"],
|
|
56
|
+
**governance_modifiers(engine=engine, mode="ENFORCE")
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Agent can use GITHUB_* tools → ALLOWED
|
|
60
|
+
# Agent tries GMAIL_SEND_EMAIL → DENIED (not in allowlist)
|
|
61
|
+
# Agent passes SSN in tool args → DENIED (PII blocked)
|
|
62
|
+
# Agent exceeds $5 budget → DENIED (cost limit)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### With Kill Switch
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from tealtiger import freeze, unfreeze
|
|
69
|
+
|
|
70
|
+
# Emergency: stop all tool execution for an agent
|
|
71
|
+
freeze("coder-agent")
|
|
72
|
+
|
|
73
|
+
# After investigation, restore access
|
|
74
|
+
unfreeze("coder-agent")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
`composio-tealtiger` uses Composio's native modifier hooks:
|
|
80
|
+
|
|
81
|
+
- **`beforeExecute`** — Evaluates governance policies before any tool executes. If the policy denies the action, execution is blocked before reaching the external service.
|
|
82
|
+
- **`afterExecute`** — Records the execution result in the audit trail and updates cost tracking.
|
|
83
|
+
- **`modifySchema`** — Optionally adds governance metadata (allowed/denied status) to tool schemas for AI context.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Agent → Composio → beforeExecute (TealTiger evaluates) → Tool Execution → afterExecute (audit)
|
|
87
|
+
↓ (if DENY)
|
|
88
|
+
GovernanceDenyError (tool never executes)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Features
|
|
92
|
+
|
|
93
|
+
| Feature | Observe | Enforce |
|
|
94
|
+
|---------|---------|---------|
|
|
95
|
+
| PII detection (email, SSN, credit card, phone) | ✅ report | ✅ block |
|
|
96
|
+
| Tool allowlisting per agent/role | — | ✅ |
|
|
97
|
+
| Cost tracking per tool call | ✅ | ✅ |
|
|
98
|
+
| Per-session budget limits | — | ✅ |
|
|
99
|
+
| Structured audit trail | ✅ | ✅ |
|
|
100
|
+
| Correlation IDs (UUID v4) | ✅ | ✅ |
|
|
101
|
+
| Kill switch (freeze/unfreeze) | ✅ | ✅ |
|
|
102
|
+
| Risk scoring | ✅ | ✅ |
|
|
103
|
+
|
|
104
|
+
## API Reference
|
|
105
|
+
|
|
106
|
+
### `governance_modifiers(engine=None, mode="OBSERVE", agent_id=None)`
|
|
107
|
+
|
|
108
|
+
Returns a dict of Composio modifier hooks configured for TealTiger governance.
|
|
109
|
+
|
|
110
|
+
**Parameters:**
|
|
111
|
+
- `engine` (TealEngine, optional) — Policy engine. If None, uses observe mode.
|
|
112
|
+
- `mode` (str) — `"OBSERVE"`, `"MONITOR"`, or `"ENFORCE"`. Default: `"OBSERVE"`.
|
|
113
|
+
- `agent_id` (str, optional) — Agent identifier. Auto-generated if not provided.
|
|
114
|
+
|
|
115
|
+
**Returns:** Dict with `beforeExecute`, `afterExecute`, and optionally `modifySchema` keys.
|
|
116
|
+
|
|
117
|
+
### `GovernanceDenyError`
|
|
118
|
+
|
|
119
|
+
Raised when a tool call is denied in ENFORCE mode.
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from composio_tealtiger import GovernanceDenyError
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
result = composio.tools.execute("GMAIL_SEND_EMAIL", params, **governance_modifiers(engine=engine, mode="ENFORCE"))
|
|
126
|
+
except GovernanceDenyError as e:
|
|
127
|
+
print(f"Blocked: {e.decision['reason']}")
|
|
128
|
+
print(f"Codes: {e.decision['reason_codes']}")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Audit Trail
|
|
132
|
+
|
|
133
|
+
Every governance evaluation produces a structured audit entry:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
138
|
+
"timestamp_ms": 1720000000000,
|
|
139
|
+
"action": "DENY",
|
|
140
|
+
"mode": "ENFORCE",
|
|
141
|
+
"tool_slug": "GMAIL_SEND_EMAIL",
|
|
142
|
+
"toolkit_slug": "gmail",
|
|
143
|
+
"agent_id": "coder-agent",
|
|
144
|
+
"reason": "Tool not in allowlist for role 'coder'",
|
|
145
|
+
"reason_codes": ["TOOL_NOT_ALLOWED"],
|
|
146
|
+
"pii_detected": [],
|
|
147
|
+
"cost_tracked": 0.0,
|
|
148
|
+
"evaluation_time_ms": 0.42
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Requirements
|
|
153
|
+
|
|
154
|
+
- Python 3.9+
|
|
155
|
+
- `composio` >= 0.7.0
|
|
156
|
+
- `tealtiger` >= 1.1.0
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
Apache-2.0
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""TealTiger governance middleware for Composio tool calls.
|
|
2
|
+
|
|
3
|
+
Uses Composio's native beforeExecute/afterExecute modifier hooks to evaluate
|
|
4
|
+
governance policies before tools execute against external services.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
import time
|
|
9
|
+
import re
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GovernanceDenyError(Exception):
|
|
14
|
+
"""Raised when a tool call is denied by governance policy."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, decision: Dict[str, Any]):
|
|
17
|
+
self.decision = decision
|
|
18
|
+
super().__init__(f"Governance DENY: {decision.get('reason', 'Policy violation')}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# PII patterns for detection
|
|
22
|
+
_PII_PATTERNS = {
|
|
23
|
+
"ssn": re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
|
|
24
|
+
"credit_card": re.compile(r"\b(?:\d{4}[-\s]?){3}\d{4}\b"),
|
|
25
|
+
"email": re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"),
|
|
26
|
+
"phone": re.compile(r"\b(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b"),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _GovernanceState:
|
|
31
|
+
"""Internal state for governance tracking across tool calls."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, agent_id: str):
|
|
34
|
+
self.agent_id = agent_id
|
|
35
|
+
self.session_id = str(uuid.uuid4())
|
|
36
|
+
self.cumulative_cost = 0.0
|
|
37
|
+
self.evaluation_count = 0
|
|
38
|
+
self.decisions: List[Dict[str, Any]] = []
|
|
39
|
+
self._frozen_agents: set = set()
|
|
40
|
+
|
|
41
|
+
def is_frozen(self, agent_id: str) -> bool:
|
|
42
|
+
return "*" in self._frozen_agents or agent_id in self._frozen_agents
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Global freeze registry (in-process, shared across all governance states)
|
|
46
|
+
_freeze_registry: set = set()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _detect_pii(text: str) -> List[Dict[str, Any]]:
|
|
50
|
+
"""Detect PII patterns in text."""
|
|
51
|
+
findings = []
|
|
52
|
+
for pii_type, pattern in _PII_PATTERNS.items():
|
|
53
|
+
matches = pattern.finditer(text)
|
|
54
|
+
for match in matches:
|
|
55
|
+
findings.append({
|
|
56
|
+
"type": pii_type,
|
|
57
|
+
"start": match.start(),
|
|
58
|
+
"end": match.end(),
|
|
59
|
+
})
|
|
60
|
+
return findings
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _check_tool_allowlist(
|
|
64
|
+
tool_slug: str, policies: List[Dict[str, Any]], agent_id: str
|
|
65
|
+
) -> Optional[str]:
|
|
66
|
+
"""Check if tool is in the allowlist. Returns denial reason or None."""
|
|
67
|
+
for policy in policies:
|
|
68
|
+
if policy.get("type") != "tool_allowlist":
|
|
69
|
+
continue
|
|
70
|
+
policy_agent = policy.get("agent", "*")
|
|
71
|
+
if policy_agent != "*" and policy_agent != agent_id:
|
|
72
|
+
continue
|
|
73
|
+
allowed_patterns = policy.get("allowed", [])
|
|
74
|
+
for pattern in allowed_patterns:
|
|
75
|
+
if pattern.endswith("*"):
|
|
76
|
+
if tool_slug.startswith(pattern[:-1]):
|
|
77
|
+
return None
|
|
78
|
+
elif tool_slug == pattern:
|
|
79
|
+
return None
|
|
80
|
+
return f"Tool '{tool_slug}' not in allowlist for agent '{agent_id}'"
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _check_pii_block(
|
|
85
|
+
text: str, policies: List[Dict[str, Any]]
|
|
86
|
+
) -> tuple:
|
|
87
|
+
"""Check for PII violations. Returns (denial_reason, findings) or (None, findings)."""
|
|
88
|
+
findings = _detect_pii(text)
|
|
89
|
+
if not findings:
|
|
90
|
+
return None, []
|
|
91
|
+
|
|
92
|
+
for policy in policies:
|
|
93
|
+
if policy.get("type") != "pii_block":
|
|
94
|
+
continue
|
|
95
|
+
blocked_categories = policy.get("categories", [])
|
|
96
|
+
for finding in findings:
|
|
97
|
+
if finding["type"] in blocked_categories:
|
|
98
|
+
return (
|
|
99
|
+
f"PII detected: {finding['type']} in tool arguments",
|
|
100
|
+
findings,
|
|
101
|
+
)
|
|
102
|
+
return None, findings
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _check_cost_limit(
|
|
106
|
+
cumulative_cost: float, policies: List[Dict[str, Any]]
|
|
107
|
+
) -> Optional[str]:
|
|
108
|
+
"""Check if cost limit exceeded. Returns denial reason or None."""
|
|
109
|
+
for policy in policies:
|
|
110
|
+
if policy.get("type") != "cost_limit":
|
|
111
|
+
continue
|
|
112
|
+
max_cost = policy.get("max_per_session", float("inf"))
|
|
113
|
+
if cumulative_cost >= max_cost:
|
|
114
|
+
return f"Session cost limit exceeded: ${cumulative_cost:.4f} >= ${max_cost:.2f}"
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _estimate_cost(tool_slug: str) -> float:
|
|
119
|
+
"""Estimate cost of a tool call (simplified)."""
|
|
120
|
+
# Base cost estimate per tool call category
|
|
121
|
+
if "SEARCH" in tool_slug or "GET" in tool_slug or "LIST" in tool_slug:
|
|
122
|
+
return 0.001 # Read operations
|
|
123
|
+
elif "CREATE" in tool_slug or "SEND" in tool_slug or "POST" in tool_slug:
|
|
124
|
+
return 0.005 # Write operations
|
|
125
|
+
else:
|
|
126
|
+
return 0.002 # Default
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def governance_modifiers(
|
|
130
|
+
engine=None,
|
|
131
|
+
mode: str = "OBSERVE",
|
|
132
|
+
agent_id: Optional[str] = None,
|
|
133
|
+
cost_per_call: Optional[float] = None,
|
|
134
|
+
) -> Dict[str, Any]:
|
|
135
|
+
"""Create Composio modifier hooks for TealTiger governance.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
engine: TealEngine instance with policies. If None, uses observe mode.
|
|
139
|
+
mode: "OBSERVE", "MONITOR", or "ENFORCE".
|
|
140
|
+
agent_id: Agent identifier. Auto-generated if not provided.
|
|
141
|
+
cost_per_call: Override cost estimate per call. If None, uses heuristic.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dict with beforeExecute and afterExecute keys for Composio modifiers.
|
|
145
|
+
"""
|
|
146
|
+
_agent_id = agent_id or f"composio-agent-{str(uuid.uuid4())[:8]}"
|
|
147
|
+
state = _GovernanceState(_agent_id)
|
|
148
|
+
policies = []
|
|
149
|
+
|
|
150
|
+
# Extract policies from TealEngine if provided
|
|
151
|
+
if engine is not None:
|
|
152
|
+
if hasattr(engine, "policies"):
|
|
153
|
+
policies = engine.policies
|
|
154
|
+
elif hasattr(engine, "_policies"):
|
|
155
|
+
policies = engine._policies
|
|
156
|
+
|
|
157
|
+
async def before_execute(tool_slug: str, toolkit_slug: str, params: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
|
158
|
+
"""Evaluate governance before tool execution."""
|
|
159
|
+
start_time = time.perf_counter()
|
|
160
|
+
state.evaluation_count += 1
|
|
161
|
+
correlation_id = str(uuid.uuid4())
|
|
162
|
+
|
|
163
|
+
# Check freeze registry
|
|
164
|
+
if _agent_id in _freeze_registry or "*" in _freeze_registry:
|
|
165
|
+
decision = {
|
|
166
|
+
"correlation_id": correlation_id,
|
|
167
|
+
"timestamp_ms": time.time() * 1000,
|
|
168
|
+
"action": "DENY",
|
|
169
|
+
"mode": mode,
|
|
170
|
+
"tool_slug": tool_slug,
|
|
171
|
+
"toolkit_slug": toolkit_slug,
|
|
172
|
+
"agent_id": _agent_id,
|
|
173
|
+
"reason": f"Agent '{_agent_id}' is frozen",
|
|
174
|
+
"reason_codes": ["AGENT_FROZEN"],
|
|
175
|
+
"pii_detected": [],
|
|
176
|
+
"cost_tracked": 0.0,
|
|
177
|
+
"cumulative_cost": state.cumulative_cost,
|
|
178
|
+
"evaluation_time_ms": (time.perf_counter() - start_time) * 1000,
|
|
179
|
+
}
|
|
180
|
+
state.decisions.append(decision)
|
|
181
|
+
if mode == "ENFORCE":
|
|
182
|
+
raise GovernanceDenyError(decision)
|
|
183
|
+
return params
|
|
184
|
+
|
|
185
|
+
# Serialize arguments for scanning
|
|
186
|
+
args_text = str(params.get("arguments", {}))
|
|
187
|
+
|
|
188
|
+
# Check tool allowlist
|
|
189
|
+
denial_reason = _check_tool_allowlist(tool_slug, policies, _agent_id)
|
|
190
|
+
|
|
191
|
+
# Check PII
|
|
192
|
+
pii_denial, pii_findings = None, []
|
|
193
|
+
if not denial_reason:
|
|
194
|
+
pii_denial, pii_findings = _check_pii_block(args_text, policies)
|
|
195
|
+
if pii_denial:
|
|
196
|
+
denial_reason = pii_denial
|
|
197
|
+
|
|
198
|
+
# Check cost limit
|
|
199
|
+
if not denial_reason:
|
|
200
|
+
cost_denial = _check_cost_limit(state.cumulative_cost, policies)
|
|
201
|
+
if cost_denial:
|
|
202
|
+
denial_reason = cost_denial
|
|
203
|
+
|
|
204
|
+
# Determine action
|
|
205
|
+
action = "DENY" if denial_reason else "ALLOW"
|
|
206
|
+
reason_codes = []
|
|
207
|
+
if denial_reason:
|
|
208
|
+
if "allowlist" in (denial_reason or "").lower():
|
|
209
|
+
reason_codes.append("TOOL_NOT_ALLOWED")
|
|
210
|
+
elif "pii" in (denial_reason or "").lower():
|
|
211
|
+
reason_codes.append("PII_BLOCKED")
|
|
212
|
+
elif "cost" in (denial_reason or "").lower():
|
|
213
|
+
reason_codes.append("COST_LIMIT_EXCEEDED")
|
|
214
|
+
elif "frozen" in (denial_reason or "").lower():
|
|
215
|
+
reason_codes.append("AGENT_FROZEN")
|
|
216
|
+
|
|
217
|
+
# Estimate cost for this call
|
|
218
|
+
call_cost = cost_per_call if cost_per_call is not None else _estimate_cost(tool_slug)
|
|
219
|
+
if action == "ALLOW":
|
|
220
|
+
state.cumulative_cost += call_cost
|
|
221
|
+
|
|
222
|
+
eval_time = (time.perf_counter() - start_time) * 1000
|
|
223
|
+
|
|
224
|
+
decision = {
|
|
225
|
+
"correlation_id": correlation_id,
|
|
226
|
+
"timestamp_ms": time.time() * 1000,
|
|
227
|
+
"action": action,
|
|
228
|
+
"mode": mode,
|
|
229
|
+
"tool_slug": tool_slug,
|
|
230
|
+
"toolkit_slug": toolkit_slug,
|
|
231
|
+
"agent_id": _agent_id,
|
|
232
|
+
"reason": denial_reason or f"Allowed: {mode.lower()} mode",
|
|
233
|
+
"reason_codes": reason_codes or (["OBSERVE_PASSTHROUGH"] if mode == "OBSERVE" else ["POLICY_ALLOW"]),
|
|
234
|
+
"pii_detected": pii_findings,
|
|
235
|
+
"cost_tracked": call_cost if action == "ALLOW" else 0.0,
|
|
236
|
+
"cumulative_cost": state.cumulative_cost,
|
|
237
|
+
"evaluation_time_ms": eval_time,
|
|
238
|
+
}
|
|
239
|
+
state.decisions.append(decision)
|
|
240
|
+
|
|
241
|
+
# Enforce denial
|
|
242
|
+
if action == "DENY" and mode == "ENFORCE":
|
|
243
|
+
raise GovernanceDenyError(decision)
|
|
244
|
+
|
|
245
|
+
return params
|
|
246
|
+
|
|
247
|
+
async def after_execute(result: Dict[str, Any], tool_slug: str, toolkit_slug: str, **kwargs) -> Dict[str, Any]:
|
|
248
|
+
"""Record execution result in audit trail."""
|
|
249
|
+
# Update the last decision with execution outcome
|
|
250
|
+
if state.decisions:
|
|
251
|
+
last_decision = state.decisions[-1]
|
|
252
|
+
last_decision["execution_successful"] = result.get("successful", False)
|
|
253
|
+
|
|
254
|
+
return result
|
|
255
|
+
|
|
256
|
+
modifiers = {
|
|
257
|
+
"beforeExecute": before_execute,
|
|
258
|
+
"afterExecute": after_execute,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Attach state accessor for testing/inspection
|
|
262
|
+
modifiers["_governance_state"] = state
|
|
263
|
+
|
|
264
|
+
return modifiers
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: composio-tealtiger
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic governance middleware for Composio tool calls — policy enforcement, PII detection, cost tracking, and audit evidence.
|
|
5
|
+
Author-email: TealTiger <feedback@tealtiger.ai>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/agentguard-ai/composio-tealtiger
|
|
8
|
+
Project-URL: Documentation, https://docs.tealtiger.ai/integrations/composio
|
|
9
|
+
Project-URL: Repository, https://github.com/agentguard-ai/composio-tealtiger
|
|
10
|
+
Project-URL: Issues, https://github.com/agentguard-ai/composio-tealtiger/issues
|
|
11
|
+
Keywords: composio,tealtiger,governance,ai-agents,security,pii,guardrails
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: tealtiger>=1.1.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# composio-tealtiger
|
|
30
|
+
|
|
31
|
+
Deterministic governance middleware for [Composio](https://composio.dev) tool calls — policy enforcement, PII detection, cost tracking, and structured audit evidence using [TealTiger](https://github.com/agentguard-ai/tealtiger).
|
|
32
|
+
|
|
33
|
+
No LLM in the governance path. All policy evaluation is deterministic, adding <5ms latency.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install composio-tealtiger
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Zero-Config (Observe Mode)
|
|
44
|
+
|
|
45
|
+
Add governance to any Composio tool call with zero configuration. TealTiger observes all traffic, tracks costs, detects PII, and allows everything through — producing structured audit entries.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from composio import Composio
|
|
49
|
+
from composio_tealtiger import governance_modifiers
|
|
50
|
+
|
|
51
|
+
composio = Composio()
|
|
52
|
+
|
|
53
|
+
# Get tools with TealTiger governance (observe mode — zero config)
|
|
54
|
+
tools = composio.tools.get(
|
|
55
|
+
user_id="user_123",
|
|
56
|
+
toolkits=["github", "slack"],
|
|
57
|
+
**governance_modifiers()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Every tool call is now tracked: cost, PII detection, audit trail
|
|
61
|
+
# Nothing is blocked in observe mode — just visibility
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Enforce Mode (Policy Enforcement)
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from composio import Composio
|
|
68
|
+
from composio_tealtiger import governance_modifiers
|
|
69
|
+
from tealtiger import TealEngine
|
|
70
|
+
|
|
71
|
+
# Define governance policies
|
|
72
|
+
engine = TealEngine(policies=[
|
|
73
|
+
{"type": "tool_allowlist", "agent": "coder", "allowed": ["GITHUB_*", "HACKERNEWS_*"]},
|
|
74
|
+
{"type": "pii_block", "categories": ["ssn", "credit_card"]},
|
|
75
|
+
{"type": "cost_limit", "max_per_session": 5.00},
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
composio = Composio()
|
|
79
|
+
|
|
80
|
+
# Get tools with enforcing governance
|
|
81
|
+
tools = composio.tools.get(
|
|
82
|
+
user_id="user_123",
|
|
83
|
+
toolkits=["github", "gmail", "slack"],
|
|
84
|
+
**governance_modifiers(engine=engine, mode="ENFORCE")
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Agent can use GITHUB_* tools → ALLOWED
|
|
88
|
+
# Agent tries GMAIL_SEND_EMAIL → DENIED (not in allowlist)
|
|
89
|
+
# Agent passes SSN in tool args → DENIED (PII blocked)
|
|
90
|
+
# Agent exceeds $5 budget → DENIED (cost limit)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### With Kill Switch
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from tealtiger import freeze, unfreeze
|
|
97
|
+
|
|
98
|
+
# Emergency: stop all tool execution for an agent
|
|
99
|
+
freeze("coder-agent")
|
|
100
|
+
|
|
101
|
+
# After investigation, restore access
|
|
102
|
+
unfreeze("coder-agent")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## How It Works
|
|
106
|
+
|
|
107
|
+
`composio-tealtiger` uses Composio's native modifier hooks:
|
|
108
|
+
|
|
109
|
+
- **`beforeExecute`** — Evaluates governance policies before any tool executes. If the policy denies the action, execution is blocked before reaching the external service.
|
|
110
|
+
- **`afterExecute`** — Records the execution result in the audit trail and updates cost tracking.
|
|
111
|
+
- **`modifySchema`** — Optionally adds governance metadata (allowed/denied status) to tool schemas for AI context.
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
Agent → Composio → beforeExecute (TealTiger evaluates) → Tool Execution → afterExecute (audit)
|
|
115
|
+
↓ (if DENY)
|
|
116
|
+
GovernanceDenyError (tool never executes)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Features
|
|
120
|
+
|
|
121
|
+
| Feature | Observe | Enforce |
|
|
122
|
+
|---------|---------|---------|
|
|
123
|
+
| PII detection (email, SSN, credit card, phone) | ✅ report | ✅ block |
|
|
124
|
+
| Tool allowlisting per agent/role | — | ✅ |
|
|
125
|
+
| Cost tracking per tool call | ✅ | ✅ |
|
|
126
|
+
| Per-session budget limits | — | ✅ |
|
|
127
|
+
| Structured audit trail | ✅ | ✅ |
|
|
128
|
+
| Correlation IDs (UUID v4) | ✅ | ✅ |
|
|
129
|
+
| Kill switch (freeze/unfreeze) | ✅ | ✅ |
|
|
130
|
+
| Risk scoring | ✅ | ✅ |
|
|
131
|
+
|
|
132
|
+
## API Reference
|
|
133
|
+
|
|
134
|
+
### `governance_modifiers(engine=None, mode="OBSERVE", agent_id=None)`
|
|
135
|
+
|
|
136
|
+
Returns a dict of Composio modifier hooks configured for TealTiger governance.
|
|
137
|
+
|
|
138
|
+
**Parameters:**
|
|
139
|
+
- `engine` (TealEngine, optional) — Policy engine. If None, uses observe mode.
|
|
140
|
+
- `mode` (str) — `"OBSERVE"`, `"MONITOR"`, or `"ENFORCE"`. Default: `"OBSERVE"`.
|
|
141
|
+
- `agent_id` (str, optional) — Agent identifier. Auto-generated if not provided.
|
|
142
|
+
|
|
143
|
+
**Returns:** Dict with `beforeExecute`, `afterExecute`, and optionally `modifySchema` keys.
|
|
144
|
+
|
|
145
|
+
### `GovernanceDenyError`
|
|
146
|
+
|
|
147
|
+
Raised when a tool call is denied in ENFORCE mode.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from composio_tealtiger import GovernanceDenyError
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
result = composio.tools.execute("GMAIL_SEND_EMAIL", params, **governance_modifiers(engine=engine, mode="ENFORCE"))
|
|
154
|
+
except GovernanceDenyError as e:
|
|
155
|
+
print(f"Blocked: {e.decision['reason']}")
|
|
156
|
+
print(f"Codes: {e.decision['reason_codes']}")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Audit Trail
|
|
160
|
+
|
|
161
|
+
Every governance evaluation produces a structured audit entry:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
166
|
+
"timestamp_ms": 1720000000000,
|
|
167
|
+
"action": "DENY",
|
|
168
|
+
"mode": "ENFORCE",
|
|
169
|
+
"tool_slug": "GMAIL_SEND_EMAIL",
|
|
170
|
+
"toolkit_slug": "gmail",
|
|
171
|
+
"agent_id": "coder-agent",
|
|
172
|
+
"reason": "Tool not in allowlist for role 'coder'",
|
|
173
|
+
"reason_codes": ["TOOL_NOT_ALLOWED"],
|
|
174
|
+
"pii_detected": [],
|
|
175
|
+
"cost_tracked": 0.0,
|
|
176
|
+
"evaluation_time_ms": 0.42
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Requirements
|
|
181
|
+
|
|
182
|
+
- Python 3.9+
|
|
183
|
+
- `composio` >= 0.7.0
|
|
184
|
+
- `tealtiger` >= 1.1.0
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
Apache-2.0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
composio_tealtiger/__init__.py
|
|
4
|
+
composio_tealtiger/middleware.py
|
|
5
|
+
composio_tealtiger.egg-info/PKG-INFO
|
|
6
|
+
composio_tealtiger.egg-info/SOURCES.txt
|
|
7
|
+
composio_tealtiger.egg-info/dependency_links.txt
|
|
8
|
+
composio_tealtiger.egg-info/requires.txt
|
|
9
|
+
composio_tealtiger.egg-info/top_level.txt
|
|
10
|
+
tests/test_governance.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
composio_tealtiger
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "composio-tealtiger"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Deterministic governance middleware for Composio tool calls — policy enforcement, PII detection, cost tracking, and audit evidence."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "Apache-2.0"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "TealTiger", email = "feedback@tealtiger.ai"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["composio", "tealtiger", "governance", "ai-agents", "security", "pii", "guardrails"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: Apache Software License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Security",
|
|
26
|
+
"Topic :: Software Development :: Libraries",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"tealtiger>=1.1.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.0",
|
|
35
|
+
"pytest-asyncio>=0.21",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/agentguard-ai/composio-tealtiger"
|
|
40
|
+
Documentation = "https://docs.tealtiger.ai/integrations/composio"
|
|
41
|
+
Repository = "https://github.com/agentguard-ai/composio-tealtiger"
|
|
42
|
+
Issues = "https://github.com/agentguard-ai/composio-tealtiger/issues"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
include = ["composio_tealtiger*"]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Tests for composio-tealtiger governance middleware."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import asyncio
|
|
5
|
+
from composio_tealtiger import governance_modifiers, GovernanceDenyError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def observe_modifiers():
|
|
10
|
+
return governance_modifiers(agent_id="test-agent")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def enforce_modifiers():
|
|
15
|
+
class MockEngine:
|
|
16
|
+
policies = [
|
|
17
|
+
{"type": "tool_allowlist", "agent": "test-agent", "allowed": ["GITHUB_*", "HACKERNEWS_*"]},
|
|
18
|
+
{"type": "pii_block", "categories": ["ssn", "credit_card"]},
|
|
19
|
+
{"type": "cost_limit", "max_per_session": 0.01},
|
|
20
|
+
]
|
|
21
|
+
return governance_modifiers(engine=MockEngine(), mode="ENFORCE", agent_id="test-agent")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.asyncio
|
|
25
|
+
async def test_observe_mode_allows_everything(observe_modifiers):
|
|
26
|
+
"""Observe mode should allow all tool calls and track them."""
|
|
27
|
+
before = observe_modifiers["beforeExecute"]
|
|
28
|
+
result = await before(tool_slug="GMAIL_SEND_EMAIL", toolkit_slug="gmail", params={"arguments": {"to": "test@example.com"}})
|
|
29
|
+
assert result is not None # Not blocked
|
|
30
|
+
|
|
31
|
+
state = observe_modifiers["_governance_state"]
|
|
32
|
+
assert len(state.decisions) == 1
|
|
33
|
+
assert state.decisions[0]["action"] == "ALLOW"
|
|
34
|
+
assert state.decisions[0]["tool_slug"] == "GMAIL_SEND_EMAIL"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
async def test_enforce_mode_blocks_unauthorized_tool(enforce_modifiers):
|
|
39
|
+
"""Enforce mode should block tools not in the allowlist."""
|
|
40
|
+
before = enforce_modifiers["beforeExecute"]
|
|
41
|
+
|
|
42
|
+
with pytest.raises(GovernanceDenyError) as exc_info:
|
|
43
|
+
await before(tool_slug="GMAIL_SEND_EMAIL", toolkit_slug="gmail", params={"arguments": {}})
|
|
44
|
+
|
|
45
|
+
assert "TOOL_NOT_ALLOWED" in exc_info.value.decision["reason_codes"]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.asyncio
|
|
49
|
+
async def test_enforce_mode_allows_authorized_tool(enforce_modifiers):
|
|
50
|
+
"""Enforce mode should allow tools in the allowlist."""
|
|
51
|
+
before = enforce_modifiers["beforeExecute"]
|
|
52
|
+
result = await before(tool_slug="GITHUB_GET_REPOS", toolkit_slug="github", params={"arguments": {"owner": "composio"}})
|
|
53
|
+
assert result is not None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.mark.asyncio
|
|
57
|
+
async def test_pii_detection_blocks_ssn(enforce_modifiers):
|
|
58
|
+
"""Enforce mode should block tool calls containing SSN."""
|
|
59
|
+
before = enforce_modifiers["beforeExecute"]
|
|
60
|
+
|
|
61
|
+
with pytest.raises(GovernanceDenyError) as exc_info:
|
|
62
|
+
await before(
|
|
63
|
+
tool_slug="GITHUB_CREATE_ISSUE",
|
|
64
|
+
toolkit_slug="github",
|
|
65
|
+
params={"arguments": {"body": "Customer SSN: 000-00-0000"}}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
assert "PII_BLOCKED" in exc_info.value.decision["reason_codes"]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
async def test_observe_mode_detects_pii_without_blocking(observe_modifiers):
|
|
73
|
+
"""Observe mode should detect PII but not block."""
|
|
74
|
+
before = observe_modifiers["beforeExecute"]
|
|
75
|
+
result = await before(
|
|
76
|
+
tool_slug="SLACK_SEND_MESSAGE",
|
|
77
|
+
toolkit_slug="slack",
|
|
78
|
+
params={"arguments": {"text": "Customer SSN: 000-00-0000"}}
|
|
79
|
+
)
|
|
80
|
+
assert result is not None # Not blocked
|
|
81
|
+
|
|
82
|
+
state = observe_modifiers["_governance_state"]
|
|
83
|
+
assert len(state.decisions[0]["pii_detected"]) > 0
|
|
84
|
+
assert state.decisions[0]["pii_detected"][0]["type"] == "ssn"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_cost_limit_enforcement(enforce_modifiers):
|
|
89
|
+
"""Should block when cumulative cost exceeds limit."""
|
|
90
|
+
before = enforce_modifiers["beforeExecute"]
|
|
91
|
+
|
|
92
|
+
# Make several calls to exhaust the $0.01 budget
|
|
93
|
+
for i in range(5):
|
|
94
|
+
try:
|
|
95
|
+
await before(
|
|
96
|
+
tool_slug="GITHUB_GET_REPOS",
|
|
97
|
+
toolkit_slug="github",
|
|
98
|
+
params={"arguments": {"owner": "test"}}
|
|
99
|
+
)
|
|
100
|
+
except GovernanceDenyError as e:
|
|
101
|
+
assert "COST_LIMIT_EXCEEDED" in e.decision["reason_codes"]
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
pytest.fail("Cost limit should have been exceeded")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@pytest.mark.asyncio
|
|
108
|
+
async def test_audit_trail_produced(observe_modifiers):
|
|
109
|
+
"""Every call should produce an audit entry."""
|
|
110
|
+
before = observe_modifiers["beforeExecute"]
|
|
111
|
+
|
|
112
|
+
await before(tool_slug="GITHUB_GET_REPOS", toolkit_slug="github", params={"arguments": {}})
|
|
113
|
+
await before(tool_slug="SLACK_SEND_MESSAGE", toolkit_slug="slack", params={"arguments": {}})
|
|
114
|
+
|
|
115
|
+
state = observe_modifiers["_governance_state"]
|
|
116
|
+
assert len(state.decisions) == 2
|
|
117
|
+
assert all("correlation_id" in d for d in state.decisions)
|
|
118
|
+
assert all("timestamp_ms" in d for d in state.decisions)
|
|
119
|
+
assert all("evaluation_time_ms" in d for d in state.decisions)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@pytest.mark.asyncio
|
|
123
|
+
async def test_evaluation_time_under_5ms(observe_modifiers):
|
|
124
|
+
"""Governance evaluation should complete in under 5ms."""
|
|
125
|
+
before = observe_modifiers["beforeExecute"]
|
|
126
|
+
|
|
127
|
+
await before(
|
|
128
|
+
tool_slug="GITHUB_GET_REPOS",
|
|
129
|
+
toolkit_slug="github",
|
|
130
|
+
params={"arguments": {"owner": "composio", "description": "A" * 1000}}
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
state = observe_modifiers["_governance_state"]
|
|
134
|
+
assert state.decisions[0]["evaluation_time_ms"] < 5.0
|