loom-agent 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl
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.
Potentially problematic release.
This version of loom-agent might be problematic. Click here for more details.
- loom/builtin/tools/calculator.py +4 -0
- loom/builtin/tools/document_search.py +5 -0
- loom/builtin/tools/glob.py +4 -0
- loom/builtin/tools/grep.py +4 -0
- loom/builtin/tools/http_request.py +5 -0
- loom/builtin/tools/python_repl.py +5 -0
- loom/builtin/tools/read_file.py +4 -0
- loom/builtin/tools/task.py +5 -0
- loom/builtin/tools/web_search.py +4 -0
- loom/builtin/tools/write_file.py +4 -0
- loom/components/agent.py +121 -5
- loom/core/agent_executor.py +505 -320
- loom/core/compression_manager.py +17 -10
- loom/core/context_assembly.py +329 -0
- loom/core/events.py +414 -0
- loom/core/execution_context.py +119 -0
- loom/core/tool_orchestrator.py +383 -0
- loom/core/turn_state.py +188 -0
- loom/core/types.py +15 -4
- loom/interfaces/event_producer.py +172 -0
- loom/interfaces/tool.py +22 -1
- loom/security/__init__.py +13 -0
- loom/security/models.py +85 -0
- loom/security/path_validator.py +128 -0
- loom/security/validator.py +346 -0
- loom/tasks/PHASE_1_FOUNDATION/task_1.1_agent_events.md +121 -0
- loom/tasks/PHASE_1_FOUNDATION/task_1.2_streaming_api.md +521 -0
- loom/tasks/PHASE_1_FOUNDATION/task_1.3_context_assembler.md +606 -0
- loom/tasks/PHASE_2_CORE_FEATURES/task_2.1_tool_orchestrator.md +743 -0
- loom/tasks/PHASE_2_CORE_FEATURES/task_2.2_security_validator.md +676 -0
- loom/tasks/README.md +109 -0
- loom/tasks/__init__.py +11 -0
- loom/tasks/sql_placeholder.py +100 -0
- loom_agent-0.0.2.dist-info/METADATA +295 -0
- {loom_agent-0.0.1.dist-info → loom_agent-0.0.2.dist-info}/RECORD +37 -19
- loom_agent-0.0.1.dist-info/METADATA +0 -457
- {loom_agent-0.0.1.dist-info → loom_agent-0.0.2.dist-info}/WHEEL +0 -0
- {loom_agent-0.0.1.dist-info → loom_agent-0.0.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
# Task 2.2: Implement SecurityValidator
|
|
2
|
+
|
|
3
|
+
**Status**: 🔄 In Progress
|
|
4
|
+
**Priority**: P1 (High)
|
|
5
|
+
**Estimated Time**: 2 days
|
|
6
|
+
**Started**: 2025-10-25
|
|
7
|
+
**Dependencies**: Task 2.1 (ToolOrchestrator)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 📋 Overview
|
|
12
|
+
|
|
13
|
+
### Objective
|
|
14
|
+
|
|
15
|
+
Implement a multi-layer security validation system that provides comprehensive protection for tool execution through 4 independent security layers.
|
|
16
|
+
|
|
17
|
+
### Current Problem
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# Loom 1.0 - Single layer security (PermissionManager only)
|
|
21
|
+
if permission_manager.check(tool, args) == "allow":
|
|
22
|
+
await tool.run(**args) # Execute
|
|
23
|
+
|
|
24
|
+
# Problems:
|
|
25
|
+
# 1. No category-based validation (destructive tools need extra checks)
|
|
26
|
+
# 2. No path security (tools can access any file)
|
|
27
|
+
# 3. No sandbox awareness
|
|
28
|
+
# 4. Binary allow/deny (no risk assessment)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Real Security Risks**:
|
|
32
|
+
```python
|
|
33
|
+
# Risk 1: Destructive tool without confirmation
|
|
34
|
+
tool_call = ToolCall(name="write_file", arguments={
|
|
35
|
+
"path": "/etc/passwd", # System file!
|
|
36
|
+
"content": "malicious"
|
|
37
|
+
})
|
|
38
|
+
# Loom 1.0: May execute if permission policy allows ❌
|
|
39
|
+
|
|
40
|
+
# Risk 2: Path traversal attack
|
|
41
|
+
tool_call = ToolCall(name="read_file", arguments={
|
|
42
|
+
"path": "../../../etc/passwd" # Path traversal!
|
|
43
|
+
})
|
|
44
|
+
# Loom 1.0: No path validation ❌
|
|
45
|
+
|
|
46
|
+
# Risk 3: Network tool abuse
|
|
47
|
+
tool_call = ToolCall(name="http_request", arguments={
|
|
48
|
+
"url": "http://internal-service/admin", # Internal endpoint!
|
|
49
|
+
"method": "DELETE"
|
|
50
|
+
})
|
|
51
|
+
# Loom 1.0: No network filtering ❌
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Solution
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# Loom 2.0 - 4-Layer Security Validation
|
|
58
|
+
class SecurityValidator:
|
|
59
|
+
async def validate(self, tool_call, tool, context):
|
|
60
|
+
# Layer 1: Permission Rules
|
|
61
|
+
decision = await self.layer1_permission_check(tool_call, tool)
|
|
62
|
+
|
|
63
|
+
# Layer 2: Tool Category Validation
|
|
64
|
+
decision = await self.layer2_category_check(tool, context)
|
|
65
|
+
|
|
66
|
+
# Layer 3: Path Security
|
|
67
|
+
decision = await self.layer3_path_security(tool_call, tool)
|
|
68
|
+
|
|
69
|
+
# Layer 4: Sandbox Support
|
|
70
|
+
decision = await self.layer4_sandbox_check(tool)
|
|
71
|
+
|
|
72
|
+
return SecurityDecision(allow=True/False, risk_level=..., reason=...)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 🎯 Goals
|
|
78
|
+
|
|
79
|
+
1. **Defense in Depth**: Multiple independent security layers
|
|
80
|
+
2. **Risk Assessment**: Not just allow/deny, but risk scoring
|
|
81
|
+
3. **Path Protection**: Prevent path traversal and unauthorized access
|
|
82
|
+
4. **Category-Based**: Different rules for different tool categories
|
|
83
|
+
5. **Auditability**: Log all security decisions
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🏗️ Architecture
|
|
88
|
+
|
|
89
|
+
### 4-Layer Security Model
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
┌────────────────────────────────────────────────────────┐
|
|
93
|
+
│ SecurityValidator │
|
|
94
|
+
│ │
|
|
95
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
96
|
+
│ │ Layer 1: Permission Rules │ │
|
|
97
|
+
│ │ - Policy-based access control │ │
|
|
98
|
+
│ │ - User confirmation for sensitive ops │ │
|
|
99
|
+
│ │ - Integration with PermissionManager │ │
|
|
100
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
101
|
+
│ ↓ │
|
|
102
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
103
|
+
│ │ Layer 2: Tool Category Validation │ │
|
|
104
|
+
│ │ - Destructive tools require confirmation │ │
|
|
105
|
+
│ │ - Network tools checked against whitelist │ │
|
|
106
|
+
│ │ - High-risk categories flagged │ │
|
|
107
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
108
|
+
│ ↓ │
|
|
109
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
110
|
+
│ │ Layer 3: Path Security │ │
|
|
111
|
+
│ │ - Path traversal detection │ │
|
|
112
|
+
│ │ - Working directory enforcement │ │
|
|
113
|
+
│ │ - System path protection │ │
|
|
114
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
115
|
+
│ ↓ │
|
|
116
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
117
|
+
│ │ Layer 4: Sandbox Support │ │
|
|
118
|
+
│ │ - Sandbox capability detection │ │
|
|
119
|
+
│ │ - Auto-sandbox for safe operations │ │
|
|
120
|
+
│ │ - Sandbox escape prevention │ │
|
|
121
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
122
|
+
│ ↓ │
|
|
123
|
+
│ SecurityDecision(allow, risk, reason) │
|
|
124
|
+
└────────────────────────────────────────────────────────┘
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Class Design
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# loom/security/validator.py
|
|
131
|
+
|
|
132
|
+
from enum import Enum
|
|
133
|
+
from dataclasses import dataclass
|
|
134
|
+
from typing import Optional, List, Dict
|
|
135
|
+
from pathlib import Path
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class RiskLevel(str, Enum):
|
|
139
|
+
"""Security risk levels."""
|
|
140
|
+
LOW = "low"
|
|
141
|
+
MEDIUM = "medium"
|
|
142
|
+
HIGH = "high"
|
|
143
|
+
CRITICAL = "critical"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class SecurityDecision:
|
|
148
|
+
"""Result of security validation."""
|
|
149
|
+
allow: bool
|
|
150
|
+
risk_level: RiskLevel
|
|
151
|
+
reason: str
|
|
152
|
+
failed_layers: List[str] = None
|
|
153
|
+
warnings: List[str] = None
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def is_safe(self) -> bool:
|
|
157
|
+
"""Check if decision is safe to execute."""
|
|
158
|
+
return self.allow and self.risk_level in [RiskLevel.LOW, RiskLevel.MEDIUM]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class SecurityValidator:
|
|
162
|
+
"""
|
|
163
|
+
Multi-layer security validator for tool execution.
|
|
164
|
+
|
|
165
|
+
Provides 4 layers of independent security checks:
|
|
166
|
+
1. Permission rules (policy-based access control)
|
|
167
|
+
2. Tool category validation (destructive/network/general)
|
|
168
|
+
3. Path security (traversal detection, working dir enforcement)
|
|
169
|
+
4. Sandbox support (automatic sandboxing for safe ops)
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
```python
|
|
173
|
+
validator = SecurityValidator(
|
|
174
|
+
working_dir="/Users/project",
|
|
175
|
+
allowed_categories=["general", "network"],
|
|
176
|
+
require_confirmation_for=["destructive"]
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
decision = await validator.validate(
|
|
180
|
+
tool_call=ToolCall(name="write_file", arguments={...}),
|
|
181
|
+
tool=WriteFileTool(),
|
|
182
|
+
context={"user_approved": False}
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if decision.allow:
|
|
186
|
+
await tool.run(**tool_call.arguments)
|
|
187
|
+
else:
|
|
188
|
+
print(f"Blocked: {decision.reason}")
|
|
189
|
+
```
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
def __init__(
|
|
193
|
+
self,
|
|
194
|
+
working_dir: Optional[Path] = None,
|
|
195
|
+
allowed_categories: Optional[List[str]] = None,
|
|
196
|
+
require_confirmation_for: Optional[List[str]] = None,
|
|
197
|
+
permission_manager: Optional[PermissionManager] = None,
|
|
198
|
+
enable_sandbox: bool = True
|
|
199
|
+
):
|
|
200
|
+
self.working_dir = working_dir or Path.cwd()
|
|
201
|
+
self.allowed_categories = allowed_categories or ["general", "network", "destructive"]
|
|
202
|
+
self.require_confirmation_for = require_confirmation_for or ["destructive"]
|
|
203
|
+
self.permission_manager = permission_manager
|
|
204
|
+
self.enable_sandbox = enable_sandbox
|
|
205
|
+
self.audit_log: List[Dict] = []
|
|
206
|
+
|
|
207
|
+
async def validate(
|
|
208
|
+
self,
|
|
209
|
+
tool_call: ToolCall,
|
|
210
|
+
tool: BaseTool,
|
|
211
|
+
context: Optional[Dict] = None
|
|
212
|
+
) -> SecurityDecision:
|
|
213
|
+
"""
|
|
214
|
+
Validate tool execution through all 4 security layers.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
tool_call: Tool call to validate
|
|
218
|
+
tool: Tool instance
|
|
219
|
+
context: Additional context (user_approved, etc.)
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
SecurityDecision with allow/deny and risk assessment
|
|
223
|
+
"""
|
|
224
|
+
context = context or {}
|
|
225
|
+
failed_layers = []
|
|
226
|
+
warnings = []
|
|
227
|
+
max_risk = RiskLevel.LOW
|
|
228
|
+
|
|
229
|
+
# Layer 1: Permission Rules
|
|
230
|
+
layer1_result = await self.layer1_permission_check(tool_call, tool, context)
|
|
231
|
+
if not layer1_result.allow:
|
|
232
|
+
failed_layers.append("permission")
|
|
233
|
+
max_risk = max(max_risk, layer1_result.risk_level)
|
|
234
|
+
|
|
235
|
+
# Layer 2: Tool Category Validation
|
|
236
|
+
layer2_result = await self.layer2_category_check(tool, context)
|
|
237
|
+
if not layer2_result.allow:
|
|
238
|
+
failed_layers.append("category")
|
|
239
|
+
max_risk = max(max_risk, layer2_result.risk_level)
|
|
240
|
+
|
|
241
|
+
# Layer 3: Path Security
|
|
242
|
+
layer3_result = await self.layer3_path_security(tool_call, tool)
|
|
243
|
+
if not layer3_result.allow:
|
|
244
|
+
failed_layers.append("path_security")
|
|
245
|
+
max_risk = max(max_risk, layer3_result.risk_level)
|
|
246
|
+
|
|
247
|
+
# Layer 4: Sandbox Support
|
|
248
|
+
layer4_result = await self.layer4_sandbox_check(tool, context)
|
|
249
|
+
if layer4_result.warnings:
|
|
250
|
+
warnings.extend(layer4_result.warnings)
|
|
251
|
+
|
|
252
|
+
# Aggregate decision
|
|
253
|
+
allow = len(failed_layers) == 0
|
|
254
|
+
reason = self._build_reason(failed_layers, warnings)
|
|
255
|
+
|
|
256
|
+
decision = SecurityDecision(
|
|
257
|
+
allow=allow,
|
|
258
|
+
risk_level=max_risk,
|
|
259
|
+
reason=reason,
|
|
260
|
+
failed_layers=failed_layers,
|
|
261
|
+
warnings=warnings
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Audit log
|
|
265
|
+
self._log_decision(tool_call, tool, decision)
|
|
266
|
+
|
|
267
|
+
return decision
|
|
268
|
+
|
|
269
|
+
async def layer1_permission_check(
|
|
270
|
+
self,
|
|
271
|
+
tool_call: ToolCall,
|
|
272
|
+
tool: BaseTool,
|
|
273
|
+
context: Dict
|
|
274
|
+
) -> SecurityDecision:
|
|
275
|
+
"""
|
|
276
|
+
Layer 1: Check permission policy.
|
|
277
|
+
|
|
278
|
+
Integrates with existing PermissionManager.
|
|
279
|
+
"""
|
|
280
|
+
...
|
|
281
|
+
|
|
282
|
+
async def layer2_category_check(
|
|
283
|
+
self,
|
|
284
|
+
tool: BaseTool,
|
|
285
|
+
context: Dict
|
|
286
|
+
) -> SecurityDecision:
|
|
287
|
+
"""
|
|
288
|
+
Layer 2: Validate tool category.
|
|
289
|
+
|
|
290
|
+
- Destructive tools require confirmation
|
|
291
|
+
- Network tools checked against whitelist
|
|
292
|
+
- Unknown categories treated as high-risk
|
|
293
|
+
"""
|
|
294
|
+
...
|
|
295
|
+
|
|
296
|
+
async def layer3_path_security(
|
|
297
|
+
self,
|
|
298
|
+
tool_call: ToolCall,
|
|
299
|
+
tool: BaseTool
|
|
300
|
+
) -> SecurityDecision:
|
|
301
|
+
"""
|
|
302
|
+
Layer 3: Validate file paths.
|
|
303
|
+
|
|
304
|
+
- Detect path traversal attempts (../)
|
|
305
|
+
- Enforce working directory boundaries
|
|
306
|
+
- Block system paths (/etc, /sys, etc.)
|
|
307
|
+
"""
|
|
308
|
+
...
|
|
309
|
+
|
|
310
|
+
async def layer4_sandbox_check(
|
|
311
|
+
self,
|
|
312
|
+
tool: BaseTool,
|
|
313
|
+
context: Dict
|
|
314
|
+
) -> SecurityDecision:
|
|
315
|
+
"""
|
|
316
|
+
Layer 4: Check sandbox support.
|
|
317
|
+
|
|
318
|
+
- Recommend sandbox for safe operations
|
|
319
|
+
- Warn if sandbox unavailable for risky ops
|
|
320
|
+
"""
|
|
321
|
+
...
|
|
322
|
+
|
|
323
|
+
def _build_reason(self, failed_layers: List[str], warnings: List[str]) -> str:
|
|
324
|
+
"""Build human-readable reason for decision."""
|
|
325
|
+
...
|
|
326
|
+
|
|
327
|
+
def _log_decision(
|
|
328
|
+
self,
|
|
329
|
+
tool_call: ToolCall,
|
|
330
|
+
tool: BaseTool,
|
|
331
|
+
decision: SecurityDecision
|
|
332
|
+
):
|
|
333
|
+
"""Log security decision for audit trail."""
|
|
334
|
+
...
|
|
335
|
+
|
|
336
|
+
def get_audit_log(self) -> List[Dict]:
|
|
337
|
+
"""Get security audit log."""
|
|
338
|
+
return self.audit_log
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## 📝 Implementation Steps
|
|
344
|
+
|
|
345
|
+
### Step 1: Create Security Models
|
|
346
|
+
|
|
347
|
+
**File**: `loom/security/models.py` (new)
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
from enum import Enum
|
|
351
|
+
from dataclasses import dataclass
|
|
352
|
+
from typing import List, Optional
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class RiskLevel(str, Enum):
|
|
356
|
+
LOW = "low"
|
|
357
|
+
MEDIUM = "medium"
|
|
358
|
+
HIGH = "high"
|
|
359
|
+
CRITICAL = "critical"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@dataclass
|
|
363
|
+
class SecurityDecision:
|
|
364
|
+
allow: bool
|
|
365
|
+
risk_level: RiskLevel
|
|
366
|
+
reason: str
|
|
367
|
+
failed_layers: List[str] = None
|
|
368
|
+
warnings: List[str] = None
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def is_safe(self) -> bool:
|
|
372
|
+
return self.allow and self.risk_level in [RiskLevel.LOW, RiskLevel.MEDIUM]
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@dataclass
|
|
376
|
+
class PathSecurityResult:
|
|
377
|
+
is_safe: bool
|
|
378
|
+
normalized_path: str
|
|
379
|
+
warnings: List[str]
|
|
380
|
+
violations: List[str]
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Step 2: Implement Path Security Validator
|
|
384
|
+
|
|
385
|
+
**File**: `loom/security/path_validator.py` (new)
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from pathlib import Path
|
|
389
|
+
from typing import List, Optional
|
|
390
|
+
|
|
391
|
+
SYSTEM_PATHS = [
|
|
392
|
+
"/etc",
|
|
393
|
+
"/sys",
|
|
394
|
+
"/proc",
|
|
395
|
+
"/dev",
|
|
396
|
+
"/boot",
|
|
397
|
+
"/root",
|
|
398
|
+
"/var/log",
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class PathSecurityValidator:
|
|
403
|
+
"""Validate file paths for security."""
|
|
404
|
+
|
|
405
|
+
def __init__(self, working_dir: Path):
|
|
406
|
+
self.working_dir = working_dir.resolve()
|
|
407
|
+
|
|
408
|
+
def validate_path(self, path: str) -> PathSecurityResult:
|
|
409
|
+
"""
|
|
410
|
+
Validate a file path for security issues.
|
|
411
|
+
|
|
412
|
+
Checks:
|
|
413
|
+
1. Path traversal (../)
|
|
414
|
+
2. Absolute path outside working dir
|
|
415
|
+
3. System paths
|
|
416
|
+
4. Symlink attacks (future)
|
|
417
|
+
"""
|
|
418
|
+
violations = []
|
|
419
|
+
warnings = []
|
|
420
|
+
|
|
421
|
+
# Detect path traversal
|
|
422
|
+
if ".." in path:
|
|
423
|
+
violations.append("Path traversal detected")
|
|
424
|
+
|
|
425
|
+
# Resolve and check boundaries
|
|
426
|
+
try:
|
|
427
|
+
resolved = (self.working_dir / path).resolve()
|
|
428
|
+
|
|
429
|
+
# Check if within working dir
|
|
430
|
+
if not str(resolved).startswith(str(self.working_dir)):
|
|
431
|
+
violations.append(f"Path outside working directory: {resolved}")
|
|
432
|
+
|
|
433
|
+
# Check system paths
|
|
434
|
+
for sys_path in SYSTEM_PATHS:
|
|
435
|
+
if str(resolved).startswith(sys_path):
|
|
436
|
+
violations.append(f"System path access denied: {sys_path}")
|
|
437
|
+
|
|
438
|
+
except Exception as e:
|
|
439
|
+
violations.append(f"Path resolution failed: {e}")
|
|
440
|
+
|
|
441
|
+
is_safe = len(violations) == 0
|
|
442
|
+
|
|
443
|
+
return PathSecurityResult(
|
|
444
|
+
is_safe=is_safe,
|
|
445
|
+
normalized_path=str(resolved) if is_safe else path,
|
|
446
|
+
warnings=warnings,
|
|
447
|
+
violations=violations
|
|
448
|
+
)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Step 3: Implement SecurityValidator
|
|
452
|
+
|
|
453
|
+
**File**: `loom/security/validator.py` (new)
|
|
454
|
+
|
|
455
|
+
Main implementation with all 4 layers.
|
|
456
|
+
|
|
457
|
+
### Step 4: Integrate into ToolOrchestrator
|
|
458
|
+
|
|
459
|
+
**File**: `loom/core/tool_orchestrator.py` (modify)
|
|
460
|
+
|
|
461
|
+
```python
|
|
462
|
+
# Add SecurityValidator support
|
|
463
|
+
class ToolOrchestrator:
|
|
464
|
+
def __init__(
|
|
465
|
+
self,
|
|
466
|
+
tools: Dict[str, BaseTool],
|
|
467
|
+
permission_manager: Optional[PermissionManager] = None,
|
|
468
|
+
security_validator: Optional[SecurityValidator] = None, # 🆕
|
|
469
|
+
max_parallel: int = 5
|
|
470
|
+
):
|
|
471
|
+
self.tools = tools
|
|
472
|
+
self.permission_manager = permission_manager
|
|
473
|
+
self.security_validator = security_validator # 🆕
|
|
474
|
+
self.max_parallel = max_parallel
|
|
475
|
+
|
|
476
|
+
async def execute_one(self, tool_call: ToolCall):
|
|
477
|
+
# ... existing code ...
|
|
478
|
+
|
|
479
|
+
# 🆕 Security validation
|
|
480
|
+
if self.security_validator:
|
|
481
|
+
decision = await self.security_validator.validate(
|
|
482
|
+
tool_call=tool_call,
|
|
483
|
+
tool=tool,
|
|
484
|
+
context={}
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
if not decision.allow:
|
|
488
|
+
# Emit security error event
|
|
489
|
+
yield AgentEvent(
|
|
490
|
+
type=AgentEventType.TOOL_ERROR,
|
|
491
|
+
tool_result=ToolResult(
|
|
492
|
+
tool_call_id=tool_call.id,
|
|
493
|
+
tool_name=tool_call.name,
|
|
494
|
+
content=f"Security check failed: {decision.reason}",
|
|
495
|
+
is_error=True
|
|
496
|
+
),
|
|
497
|
+
error=SecurityError(decision.reason)
|
|
498
|
+
)
|
|
499
|
+
return
|
|
500
|
+
|
|
501
|
+
# ... continue with execution ...
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## 🧪 Testing Requirements
|
|
507
|
+
|
|
508
|
+
### Unit Tests
|
|
509
|
+
|
|
510
|
+
**File**: `tests/unit/test_security_validator.py`
|
|
511
|
+
|
|
512
|
+
**Test cases** (target 20-25 tests):
|
|
513
|
+
|
|
514
|
+
```python
|
|
515
|
+
class TestSecurityDecision:
|
|
516
|
+
def test_decision_creation(self):
|
|
517
|
+
"""Test SecurityDecision creation."""
|
|
518
|
+
...
|
|
519
|
+
|
|
520
|
+
def test_is_safe_property(self):
|
|
521
|
+
"""Test is_safe property logic."""
|
|
522
|
+
...
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class TestPathSecurityValidator:
|
|
526
|
+
def test_path_traversal_detection(self):
|
|
527
|
+
"""Test ../ detection."""
|
|
528
|
+
...
|
|
529
|
+
|
|
530
|
+
def test_absolute_path_outside_workdir(self):
|
|
531
|
+
"""Test absolute path restrictions."""
|
|
532
|
+
...
|
|
533
|
+
|
|
534
|
+
def test_system_path_blocking(self):
|
|
535
|
+
"""Test system path protection."""
|
|
536
|
+
...
|
|
537
|
+
|
|
538
|
+
def test_safe_relative_path(self):
|
|
539
|
+
"""Test safe relative paths."""
|
|
540
|
+
...
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
class TestLayer1PermissionCheck:
|
|
544
|
+
async def test_permission_allow(self):
|
|
545
|
+
"""Test permission layer allows valid tools."""
|
|
546
|
+
...
|
|
547
|
+
|
|
548
|
+
async def test_permission_deny(self):
|
|
549
|
+
"""Test permission layer blocks denied tools."""
|
|
550
|
+
...
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
class TestLayer2CategoryCheck:
|
|
554
|
+
async def test_general_category_allowed(self):
|
|
555
|
+
"""Test general category tools allowed."""
|
|
556
|
+
...
|
|
557
|
+
|
|
558
|
+
async def test_destructive_requires_confirmation(self):
|
|
559
|
+
"""Test destructive tools require confirmation."""
|
|
560
|
+
...
|
|
561
|
+
|
|
562
|
+
async def test_unknown_category_high_risk(self):
|
|
563
|
+
"""Test unknown categories flagged as high risk."""
|
|
564
|
+
...
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
class TestLayer3PathSecurity:
|
|
568
|
+
async def test_blocks_path_traversal(self):
|
|
569
|
+
"""Test path traversal blocked."""
|
|
570
|
+
...
|
|
571
|
+
|
|
572
|
+
async def test_blocks_system_paths(self):
|
|
573
|
+
"""Test system path access blocked."""
|
|
574
|
+
...
|
|
575
|
+
|
|
576
|
+
async def test_allows_safe_paths(self):
|
|
577
|
+
"""Test safe paths allowed."""
|
|
578
|
+
...
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
class TestLayer4SandboxCheck:
|
|
582
|
+
async def test_sandbox_recommendation(self):
|
|
583
|
+
"""Test sandbox recommended for safe ops."""
|
|
584
|
+
...
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
class TestSecurityValidatorIntegration:
|
|
588
|
+
async def test_all_layers_pass(self):
|
|
589
|
+
"""Test all layers passing."""
|
|
590
|
+
...
|
|
591
|
+
|
|
592
|
+
async def test_single_layer_failure(self):
|
|
593
|
+
"""Test single layer failure blocks execution."""
|
|
594
|
+
...
|
|
595
|
+
|
|
596
|
+
async def test_multiple_layer_failures(self):
|
|
597
|
+
"""Test multiple layer failures."""
|
|
598
|
+
...
|
|
599
|
+
|
|
600
|
+
async def test_audit_logging(self):
|
|
601
|
+
"""Test audit log captures decisions."""
|
|
602
|
+
...
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class TestToolOrchestrationWithSecurity:
|
|
606
|
+
async def test_security_blocks_dangerous_tool(self):
|
|
607
|
+
"""Test security validator blocks dangerous tools."""
|
|
608
|
+
...
|
|
609
|
+
|
|
610
|
+
async def test_security_allows_safe_tool(self):
|
|
611
|
+
"""Test security validator allows safe tools."""
|
|
612
|
+
...
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## ✅ Acceptance Criteria
|
|
618
|
+
|
|
619
|
+
- [ ] SecurityDecision model implemented
|
|
620
|
+
- [ ] PathSecurityValidator implemented
|
|
621
|
+
- [ ] SecurityValidator implemented with 4 layers
|
|
622
|
+
- [ ] Layer 1: Permission check
|
|
623
|
+
- [ ] Layer 2: Category validation
|
|
624
|
+
- [ ] Layer 3: Path security
|
|
625
|
+
- [ ] Layer 4: Sandbox support
|
|
626
|
+
- [ ] Integrated into ToolOrchestrator
|
|
627
|
+
- [ ] Test coverage ≥ 80%
|
|
628
|
+
- [ ] 20-25 unit tests
|
|
629
|
+
- [ ] All tests pass
|
|
630
|
+
- [ ] Audit logging works
|
|
631
|
+
- [ ] Path traversal prevented
|
|
632
|
+
- [ ] System paths protected
|
|
633
|
+
- [ ] Backward compatible
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## 📦 Deliverables
|
|
638
|
+
|
|
639
|
+
1. **Core Implementation**
|
|
640
|
+
- [ ] `loom/security/models.py` (~100 lines)
|
|
641
|
+
- [ ] `loom/security/path_validator.py` (~150 lines)
|
|
642
|
+
- [ ] `loom/security/validator.py` (~400 lines)
|
|
643
|
+
|
|
644
|
+
2. **Integration**
|
|
645
|
+
- [ ] Modified `loom/core/tool_orchestrator.py`
|
|
646
|
+
- [ ] Modified `loom/core/agent_executor.py`
|
|
647
|
+
|
|
648
|
+
3. **Tests**
|
|
649
|
+
- [ ] `tests/unit/test_security_validator.py` (20-25 tests)
|
|
650
|
+
- [ ] All tests passing
|
|
651
|
+
|
|
652
|
+
4. **Documentation**
|
|
653
|
+
- [ ] Code docstrings complete
|
|
654
|
+
- [ ] Example usage
|
|
655
|
+
- [ ] `docs/TASK_2.2_COMPLETION_SUMMARY.md`
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## 🔍 Testing Checklist
|
|
660
|
+
|
|
661
|
+
Before marking as complete:
|
|
662
|
+
|
|
663
|
+
- [ ] All unit tests pass
|
|
664
|
+
- [ ] Path traversal attacks blocked
|
|
665
|
+
- [ ] System paths protected
|
|
666
|
+
- [ ] Audit log captures decisions
|
|
667
|
+
- [ ] Integration with orchestrator works
|
|
668
|
+
- [ ] No regressions in existing tests
|
|
669
|
+
- [ ] Code coverage ≥ 80%
|
|
670
|
+
- [ ] Documentation complete
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
|
|
674
|
+
**Created**: 2025-10-25
|
|
675
|
+
**Last Updated**: 2025-10-25
|
|
676
|
+
**Status**: 🔄 In Progress
|