@jaguilar87/gaia-ops 2.2.1 → 2.2.2
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.
- package/CHANGELOG.md +74 -0
- package/config/embeddings_info.json +14 -0
- package/config/intent_embeddings.json +2002 -0
- package/config/intent_embeddings.npy +0 -0
- package/package.json +2 -1
- package/templates/CLAUDE.template.md +3 -11
- package/tests/README.en.md +224 -0
- package/tests/README.md +338 -0
- package/tests/fixtures/project-context.aws.json +53 -0
- package/tests/fixtures/project-context.gcp.json +53 -0
- package/tests/integration/RUN_TESTS.md +185 -0
- package/tests/integration/__init__.py +0 -0
- package/tests/integration/test_hooks_integration.py +473 -0
- package/tests/integration/test_hooks_workflow.py +397 -0
- package/tests/permissions-validation/MANUAL_VALIDATION.md +434 -0
- package/tests/permissions-validation/test_permissions_validation.py +527 -0
- package/tests/system/__init__.py +0 -0
- package/tests/system/permissions_helpers.py +318 -0
- package/tests/system/test_agent_definitions.py +166 -0
- package/tests/system/test_configuration_files.py +121 -0
- package/tests/system/test_directory_structure.py +231 -0
- package/tests/system/test_permissions_system.py +1006 -0
- package/tests/tools/__init__.py +0 -0
- package/tests/tools/test_agent_router.py +266 -0
- package/tests/tools/test_clarify_engine.py +413 -0
- package/tests/tools/test_context_provider.py +157 -0
- package/tests/validators/__init__.py +0 -0
- package/tests/validators/test_approval_gate.py +415 -0
- package/tests/validators/test_commit_validator.py +446 -0
- package/tools/context_provider.py +4 -4
- package/tools/generate_embeddings.py +3 -3
- package/tools/semantic_matcher.py +2 -2
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integration tests for hooks and permissions system.
|
|
3
|
+
|
|
4
|
+
Tests the integration between:
|
|
5
|
+
- pre_tool_use hook and PolicyEngine
|
|
6
|
+
- post_tool_use hook and AuditLogger
|
|
7
|
+
- Settings permissions and pattern matching
|
|
8
|
+
- GitOps security validation
|
|
9
|
+
- Tier-based command classification
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
import sys
|
|
14
|
+
import json
|
|
15
|
+
import tempfile
|
|
16
|
+
import shutil
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Dict, Any
|
|
19
|
+
|
|
20
|
+
# Add parent directories to path for imports
|
|
21
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "hooks"))
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "tests" / "system"))
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from pre_tool_use import PolicyEngine, SecurityTier, pre_tool_use_hook
|
|
26
|
+
PRE_HOOK_AVAILABLE = True
|
|
27
|
+
except ImportError as e:
|
|
28
|
+
print(f"⚠️ pre_tool_use hook not available: {e}")
|
|
29
|
+
PRE_HOOK_AVAILABLE = False
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
from post_tool_use import post_tool_use_hook, AuditLogger, MetricsCollector
|
|
33
|
+
POST_HOOK_AVAILABLE = True
|
|
34
|
+
except ImportError as e:
|
|
35
|
+
print(f"⚠️ post_tool_use hook not available: {e}")
|
|
36
|
+
POST_HOOK_AVAILABLE = False
|
|
37
|
+
|
|
38
|
+
from permissions_helpers import (
|
|
39
|
+
get_permission_decision,
|
|
40
|
+
matches_any_pattern,
|
|
41
|
+
get_permission_level,
|
|
42
|
+
merge_settings,
|
|
43
|
+
load_merged_settings
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.skipif(not PRE_HOOK_AVAILABLE, reason="pre_tool_use hook not available")
|
|
48
|
+
class TestPreToolUseHook:
|
|
49
|
+
"""Test pre_tool_use hook integration"""
|
|
50
|
+
|
|
51
|
+
def test_hook_allows_read_operations(self):
|
|
52
|
+
"""Test that read operations are allowed"""
|
|
53
|
+
result = pre_tool_use_hook("bash", {"command": "kubectl get pods"})
|
|
54
|
+
assert result is None, "Read operations should be allowed"
|
|
55
|
+
|
|
56
|
+
def test_hook_blocks_write_operations(self):
|
|
57
|
+
"""Test that write operations are blocked"""
|
|
58
|
+
result = pre_tool_use_hook("bash", {"command": "kubectl apply -f manifest.yaml"})
|
|
59
|
+
assert result is not None, "Write operations should be blocked"
|
|
60
|
+
assert "blocked" in result.lower()
|
|
61
|
+
|
|
62
|
+
def test_hook_allows_dry_run_operations(self):
|
|
63
|
+
"""Test that dry-run operations are allowed"""
|
|
64
|
+
result = pre_tool_use_hook("bash", {"command": "kubectl apply -f manifest.yaml --dry-run=client"})
|
|
65
|
+
assert result is None, "Dry-run operations should be allowed"
|
|
66
|
+
|
|
67
|
+
def test_hook_blocks_terraform_apply(self):
|
|
68
|
+
"""Test that terraform apply is blocked"""
|
|
69
|
+
result = pre_tool_use_hook("bash", {"command": "terraform apply"})
|
|
70
|
+
assert result is not None
|
|
71
|
+
assert "blocked" in result.lower()
|
|
72
|
+
|
|
73
|
+
def test_hook_allows_terraform_plan(self):
|
|
74
|
+
"""Test that terraform plan is allowed"""
|
|
75
|
+
result = pre_tool_use_hook("bash", {"command": "terraform plan"})
|
|
76
|
+
assert result is None
|
|
77
|
+
|
|
78
|
+
def test_hook_handles_empty_command(self):
|
|
79
|
+
"""Test that empty commands are rejected"""
|
|
80
|
+
result = pre_tool_use_hook("bash", {"command": ""})
|
|
81
|
+
assert result is not None
|
|
82
|
+
assert "empty" in result.lower() or "error" in result.lower()
|
|
83
|
+
|
|
84
|
+
def test_hook_allows_non_bash_tools(self):
|
|
85
|
+
"""Test that non-bash tools are allowed"""
|
|
86
|
+
result = pre_tool_use_hook("read", {"file_path": "/tmp/test.txt"})
|
|
87
|
+
assert result is None, "Non-bash tools should be allowed"
|
|
88
|
+
|
|
89
|
+
def test_hook_blocks_git_push(self):
|
|
90
|
+
"""Test that git push is blocked"""
|
|
91
|
+
result = pre_tool_use_hook("bash", {"command": "git push origin main"})
|
|
92
|
+
assert result is not None
|
|
93
|
+
assert "blocked" in result.lower()
|
|
94
|
+
|
|
95
|
+
def test_hook_blocks_git_status(self):
|
|
96
|
+
"""Test that git status is blocked (not in allowed patterns)"""
|
|
97
|
+
result = pre_tool_use_hook("bash", {"command": "git status"})
|
|
98
|
+
# git status is not in allowed_read_operations, so it's blocked by default
|
|
99
|
+
assert result is not None
|
|
100
|
+
|
|
101
|
+
def test_hook_blocks_helm_install(self):
|
|
102
|
+
"""Test that helm install is blocked"""
|
|
103
|
+
result = pre_tool_use_hook("bash", {"command": "helm install myapp ./chart"})
|
|
104
|
+
assert result is not None
|
|
105
|
+
|
|
106
|
+
def test_hook_allows_helm_template(self):
|
|
107
|
+
"""Test that helm template is allowed"""
|
|
108
|
+
result = pre_tool_use_hook("bash", {"command": "helm template myapp ./chart"})
|
|
109
|
+
assert result is None
|
|
110
|
+
|
|
111
|
+
def test_hook_blocks_flux_reconcile(self):
|
|
112
|
+
"""Test that flux reconcile is blocked"""
|
|
113
|
+
result = pre_tool_use_hook("bash", {"command": "flux reconcile kustomization flux-system"})
|
|
114
|
+
assert result is not None
|
|
115
|
+
|
|
116
|
+
def test_hook_allows_flux_get(self):
|
|
117
|
+
"""Test that flux get is allowed"""
|
|
118
|
+
result = pre_tool_use_hook("bash", {"command": "flux get kustomizations"})
|
|
119
|
+
assert result is None
|
|
120
|
+
|
|
121
|
+
def test_hook_blocks_gcloud_create(self):
|
|
122
|
+
"""Test that gcloud create operations are blocked"""
|
|
123
|
+
result = pre_tool_use_hook("bash", {"command": "gcloud compute instances create test-vm"})
|
|
124
|
+
assert result is not None
|
|
125
|
+
|
|
126
|
+
def test_hook_allows_gcloud_describe(self):
|
|
127
|
+
"""Test that gcloud describe operations are allowed"""
|
|
128
|
+
result = pre_tool_use_hook("bash", {"command": "gcloud compute instances describe test-vm"})
|
|
129
|
+
assert result is None
|
|
130
|
+
|
|
131
|
+
def test_hook_blocks_docker_build(self):
|
|
132
|
+
"""Test that docker build is blocked"""
|
|
133
|
+
result = pre_tool_use_hook("bash", {"command": "docker build -t myapp:latest ."})
|
|
134
|
+
assert result is not None
|
|
135
|
+
|
|
136
|
+
def test_hook_blocks_docker_ps(self):
|
|
137
|
+
"""Test that docker ps is blocked (not in allowed patterns)"""
|
|
138
|
+
result = pre_tool_use_hook("bash", {"command": "docker ps"})
|
|
139
|
+
# docker ps is not in allowed_read_operations, so it's blocked by default
|
|
140
|
+
assert result is not None
|
|
141
|
+
|
|
142
|
+
def test_hook_provides_helpful_error_messages(self):
|
|
143
|
+
"""Test that blocked commands get helpful error messages"""
|
|
144
|
+
result = pre_tool_use_hook("bash", {"command": "kubectl delete pod test-pod"})
|
|
145
|
+
assert result is not None
|
|
146
|
+
assert ("alternative" in result.lower() or "instead" in result.lower()), \
|
|
147
|
+
"Error message should suggest alternatives"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@pytest.mark.skipif(not PRE_HOOK_AVAILABLE, reason="PolicyEngine not available")
|
|
151
|
+
class TestPolicyEngine:
|
|
152
|
+
"""Test PolicyEngine command classification"""
|
|
153
|
+
|
|
154
|
+
@pytest.fixture
|
|
155
|
+
def policy_engine(self):
|
|
156
|
+
"""Create a PolicyEngine instance"""
|
|
157
|
+
return PolicyEngine()
|
|
158
|
+
|
|
159
|
+
def test_classify_read_operations(self, policy_engine):
|
|
160
|
+
"""Test classification of read operations"""
|
|
161
|
+
tier = policy_engine.classify_command_tier("kubectl get pods")
|
|
162
|
+
assert tier == SecurityTier.T0_READ_ONLY
|
|
163
|
+
|
|
164
|
+
def test_classify_validation_operations(self, policy_engine):
|
|
165
|
+
"""Test classification of validation operations"""
|
|
166
|
+
tier = policy_engine.classify_command_tier("terraform plan")
|
|
167
|
+
assert tier == SecurityTier.T1_VALIDATION
|
|
168
|
+
|
|
169
|
+
def test_classify_dry_run_operations(self, policy_engine):
|
|
170
|
+
"""Test classification of dry-run operations"""
|
|
171
|
+
tier = policy_engine.classify_command_tier("kubectl apply -f test.yaml --dry-run=client")
|
|
172
|
+
assert tier == SecurityTier.T2_DRY_RUN
|
|
173
|
+
|
|
174
|
+
def test_classify_blocked_operations(self, policy_engine):
|
|
175
|
+
"""Test classification of blocked operations"""
|
|
176
|
+
tier = policy_engine.classify_command_tier("terraform apply")
|
|
177
|
+
assert tier == SecurityTier.T3_BLOCKED
|
|
178
|
+
|
|
179
|
+
def test_validate_command_returns_tuple(self, policy_engine):
|
|
180
|
+
"""Test that validate_command returns proper tuple"""
|
|
181
|
+
is_allowed, tier, reason = policy_engine.validate_command("bash", "kubectl get pods")
|
|
182
|
+
assert isinstance(is_allowed, bool)
|
|
183
|
+
assert isinstance(tier, str)
|
|
184
|
+
assert isinstance(reason, str)
|
|
185
|
+
|
|
186
|
+
def test_validate_allows_safe_commands(self, policy_engine):
|
|
187
|
+
"""Test that safe commands are allowed"""
|
|
188
|
+
is_allowed, tier, reason = policy_engine.validate_command("bash", "ls -la")
|
|
189
|
+
assert is_allowed is True
|
|
190
|
+
|
|
191
|
+
def test_validate_blocks_dangerous_commands(self, policy_engine):
|
|
192
|
+
"""Test that dangerous commands are blocked"""
|
|
193
|
+
is_allowed, tier, reason = policy_engine.validate_command("bash", "kubectl delete namespace production")
|
|
194
|
+
assert is_allowed is False
|
|
195
|
+
assert tier == SecurityTier.T3_BLOCKED
|
|
196
|
+
|
|
197
|
+
def test_validate_handles_invalid_tool_name(self, policy_engine):
|
|
198
|
+
"""Test handling of invalid tool names"""
|
|
199
|
+
is_allowed, tier, reason = policy_engine.validate_command(123, "test")
|
|
200
|
+
assert is_allowed is False
|
|
201
|
+
assert "invalid" in reason.lower()
|
|
202
|
+
|
|
203
|
+
def test_validate_handles_invalid_command(self, policy_engine):
|
|
204
|
+
"""Test handling of invalid commands"""
|
|
205
|
+
is_allowed, tier, reason = policy_engine.validate_command("bash", None)
|
|
206
|
+
assert is_allowed is False
|
|
207
|
+
|
|
208
|
+
def test_check_credentials_required(self, policy_engine):
|
|
209
|
+
"""Test credential requirement detection"""
|
|
210
|
+
requires, warning = policy_engine.check_credentials_required("kubectl get pods")
|
|
211
|
+
assert isinstance(requires, bool)
|
|
212
|
+
assert isinstance(warning, str)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@pytest.mark.skipif(not PRE_HOOK_AVAILABLE, reason="PolicyEngine not available")
|
|
216
|
+
class TestGitOpsSecurityValidation:
|
|
217
|
+
"""Test GitOps-specific security validation"""
|
|
218
|
+
|
|
219
|
+
@pytest.fixture
|
|
220
|
+
def policy_engine(self):
|
|
221
|
+
return PolicyEngine()
|
|
222
|
+
|
|
223
|
+
def test_kubectl_write_blocked(self, policy_engine):
|
|
224
|
+
"""Test that kubectl write operations are blocked"""
|
|
225
|
+
is_allowed, tier, _ = policy_engine.validate_command("bash", "kubectl apply -f deployment.yaml")
|
|
226
|
+
assert is_allowed is False
|
|
227
|
+
|
|
228
|
+
def test_kubectl_read_allowed(self, policy_engine):
|
|
229
|
+
"""Test that kubectl read operations are allowed"""
|
|
230
|
+
is_allowed, tier, _ = policy_engine.validate_command("bash", "kubectl get deployments")
|
|
231
|
+
assert is_allowed is True
|
|
232
|
+
|
|
233
|
+
def test_helm_upgrade_blocked(self, policy_engine):
|
|
234
|
+
"""Test that helm upgrade is blocked"""
|
|
235
|
+
is_allowed, tier, _ = policy_engine.validate_command("bash", "helm upgrade myapp ./chart")
|
|
236
|
+
assert is_allowed is False
|
|
237
|
+
|
|
238
|
+
def test_helm_template_allowed(self, policy_engine):
|
|
239
|
+
"""Test that helm template is allowed"""
|
|
240
|
+
is_allowed, tier, _ = policy_engine.validate_command("bash", "helm template myapp ./chart")
|
|
241
|
+
assert is_allowed is True
|
|
242
|
+
|
|
243
|
+
def test_flux_reconcile_blocked(self, policy_engine):
|
|
244
|
+
"""Test that flux reconcile is blocked"""
|
|
245
|
+
is_allowed, tier, _ = policy_engine.validate_command("bash", "flux reconcile helmrelease myapp")
|
|
246
|
+
assert is_allowed is False
|
|
247
|
+
|
|
248
|
+
def test_flux_check_allowed(self, policy_engine):
|
|
249
|
+
"""Test that flux check is allowed"""
|
|
250
|
+
is_allowed, tier, _ = policy_engine.validate_command("bash", "flux check")
|
|
251
|
+
assert is_allowed is True
|
|
252
|
+
|
|
253
|
+
def test_dry_run_kubectl_allowed(self, policy_engine):
|
|
254
|
+
"""Test that kubectl --dry-run is allowed"""
|
|
255
|
+
is_allowed, tier, _ = policy_engine.validate_command(
|
|
256
|
+
"bash",
|
|
257
|
+
"kubectl apply -f deployment.yaml --dry-run=client"
|
|
258
|
+
)
|
|
259
|
+
assert is_allowed is True
|
|
260
|
+
assert tier == SecurityTier.T2_DRY_RUN
|
|
261
|
+
|
|
262
|
+
def test_dry_run_helm_allowed(self, policy_engine):
|
|
263
|
+
"""Test that helm --dry-run is allowed"""
|
|
264
|
+
is_allowed, tier, _ = policy_engine.validate_command(
|
|
265
|
+
"bash",
|
|
266
|
+
"helm install myapp ./chart --dry-run"
|
|
267
|
+
)
|
|
268
|
+
assert is_allowed is True
|
|
269
|
+
|
|
270
|
+
def test_namespace_delete_blocked(self, policy_engine):
|
|
271
|
+
"""Test that namespace deletion is blocked"""
|
|
272
|
+
is_allowed, tier, _ = policy_engine.validate_command(
|
|
273
|
+
"bash",
|
|
274
|
+
"kubectl delete namespace production"
|
|
275
|
+
)
|
|
276
|
+
assert is_allowed is False
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class TestSettingsPermissionMatching:
|
|
280
|
+
"""Test settings-based permission matching"""
|
|
281
|
+
|
|
282
|
+
@pytest.fixture
|
|
283
|
+
def sample_settings(self):
|
|
284
|
+
"""Create sample settings for testing"""
|
|
285
|
+
return {
|
|
286
|
+
"permissions": {
|
|
287
|
+
"bash": {
|
|
288
|
+
"deny": [
|
|
289
|
+
"rm -rf",
|
|
290
|
+
"terraform apply",
|
|
291
|
+
"git push"
|
|
292
|
+
],
|
|
293
|
+
"ask": {
|
|
294
|
+
"terraform plan": "Confirm terraform plan execution?",
|
|
295
|
+
"kubectl apply.*--dry-run": "Confirm dry-run execution?"
|
|
296
|
+
},
|
|
297
|
+
"allow": [
|
|
298
|
+
"kubectl get",
|
|
299
|
+
"kubectl describe",
|
|
300
|
+
"terraform validate",
|
|
301
|
+
"ls",
|
|
302
|
+
"cat"
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
def test_deny_priority_highest(self, sample_settings):
|
|
309
|
+
"""Test that deny has highest priority"""
|
|
310
|
+
decision = get_permission_decision("rm -rf /tmp", "bash", sample_settings)
|
|
311
|
+
assert decision == "deny"
|
|
312
|
+
|
|
313
|
+
def test_ask_priority_over_allow(self, sample_settings):
|
|
314
|
+
"""Test that ask has priority over allow"""
|
|
315
|
+
# Even if "terraform" might match allow patterns, specific ask should win
|
|
316
|
+
decision = get_permission_decision("terraform plan", "bash", sample_settings)
|
|
317
|
+
assert decision == "ask"
|
|
318
|
+
|
|
319
|
+
def test_allow_works_when_no_higher_priority(self, sample_settings):
|
|
320
|
+
"""Test that allow works when no deny/ask matches"""
|
|
321
|
+
decision = get_permission_decision("kubectl get pods", "bash", sample_settings)
|
|
322
|
+
assert decision == "allow"
|
|
323
|
+
|
|
324
|
+
def test_default_deny_when_no_match(self, sample_settings):
|
|
325
|
+
"""Test default deny when no patterns match"""
|
|
326
|
+
decision = get_permission_decision("unknown-command", "bash", sample_settings)
|
|
327
|
+
assert decision == "default_deny"
|
|
328
|
+
|
|
329
|
+
def test_pattern_matching_with_wildcards(self):
|
|
330
|
+
"""Test pattern matching with wildcards"""
|
|
331
|
+
patterns = ["kubectl get*", "helm template*"]
|
|
332
|
+
assert matches_any_pattern("kubectl get pods", patterns) is True
|
|
333
|
+
assert matches_any_pattern("helm template mychart", patterns) is True
|
|
334
|
+
assert matches_any_pattern("kubectl apply", patterns) is False
|
|
335
|
+
|
|
336
|
+
def test_pattern_matching_with_regex(self):
|
|
337
|
+
"""Test pattern matching with regex patterns"""
|
|
338
|
+
patterns = [r"kubectl\s+apply.*--dry-run"]
|
|
339
|
+
assert matches_any_pattern("kubectl apply -f test.yaml --dry-run=client", patterns) is True
|
|
340
|
+
assert matches_any_pattern("kubectl apply -f test.yaml", patterns) is False
|
|
341
|
+
|
|
342
|
+
def test_settings_without_permissions(self):
|
|
343
|
+
"""Test handling of settings without permissions section"""
|
|
344
|
+
settings = {"other": "config"}
|
|
345
|
+
decision = get_permission_decision("any command", "bash", settings)
|
|
346
|
+
assert decision == "default_deny"
|
|
347
|
+
|
|
348
|
+
def test_settings_without_tool(self):
|
|
349
|
+
"""Test handling when tool not in permissions"""
|
|
350
|
+
settings = {"permissions": {"other_tool": {}}}
|
|
351
|
+
decision = get_permission_decision("any command", "bash", settings)
|
|
352
|
+
assert decision == "default_deny"
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class TestAskPermissionTriggers:
|
|
356
|
+
"""Test that 'ask' permissions are properly triggered"""
|
|
357
|
+
|
|
358
|
+
@pytest.fixture
|
|
359
|
+
def ask_settings(self):
|
|
360
|
+
return {
|
|
361
|
+
"permissions": {
|
|
362
|
+
"bash": {
|
|
363
|
+
"ask": {
|
|
364
|
+
"terraform apply": "Confirm terraform apply?",
|
|
365
|
+
"git push": "Confirm git push?",
|
|
366
|
+
"kubectl apply -f": "Confirm kubectl apply?"
|
|
367
|
+
},
|
|
368
|
+
"allow": []
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
def test_terraform_apply_triggers_ask(self, ask_settings):
|
|
374
|
+
"""Test that terraform apply triggers ask"""
|
|
375
|
+
decision = get_permission_decision("terraform apply", "bash", ask_settings)
|
|
376
|
+
assert decision == "ask"
|
|
377
|
+
|
|
378
|
+
def test_git_push_triggers_ask(self, ask_settings):
|
|
379
|
+
"""Test that git push triggers ask"""
|
|
380
|
+
decision = get_permission_decision("git push origin main", "bash", ask_settings)
|
|
381
|
+
assert decision == "ask"
|
|
382
|
+
|
|
383
|
+
def test_kubectl_apply_triggers_ask(self, ask_settings):
|
|
384
|
+
"""Test that kubectl apply triggers ask"""
|
|
385
|
+
decision = get_permission_decision("kubectl apply -f deployment.yaml", "bash", ask_settings)
|
|
386
|
+
assert decision == "ask"
|
|
387
|
+
|
|
388
|
+
def test_other_commands_default_deny(self, ask_settings):
|
|
389
|
+
"""Test that non-ask commands get default deny"""
|
|
390
|
+
decision = get_permission_decision("ls -la", "bash", ask_settings)
|
|
391
|
+
assert decision == "default_deny"
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class TestPermissionWorkflow:
|
|
395
|
+
"""Test complete permission workflow scenarios"""
|
|
396
|
+
|
|
397
|
+
@pytest.fixture
|
|
398
|
+
def complex_settings(self):
|
|
399
|
+
"""Settings with all permission types"""
|
|
400
|
+
return {
|
|
401
|
+
"permissions": {
|
|
402
|
+
"bash": {
|
|
403
|
+
"deny": ["rm -rf", "terraform destroy"],
|
|
404
|
+
"ask": {
|
|
405
|
+
"terraform apply": "Confirm?",
|
|
406
|
+
"git push": "Confirm?"
|
|
407
|
+
},
|
|
408
|
+
"allow": ["terraform plan", "kubectl get", "ls"]
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
def test_workflow_deny_blocks_immediately(self, complex_settings):
|
|
414
|
+
"""Test that deny blocks without asking"""
|
|
415
|
+
decision = get_permission_decision("rm -rf /tmp", "bash", complex_settings)
|
|
416
|
+
assert decision == "deny"
|
|
417
|
+
|
|
418
|
+
def test_workflow_ask_prompts_user(self, complex_settings):
|
|
419
|
+
"""Test that ask returns ask decision"""
|
|
420
|
+
decision = get_permission_decision("terraform apply", "bash", complex_settings)
|
|
421
|
+
assert decision == "ask"
|
|
422
|
+
|
|
423
|
+
def test_workflow_allow_permits_immediately(self, complex_settings):
|
|
424
|
+
"""Test that allow permits without asking"""
|
|
425
|
+
decision = get_permission_decision("terraform plan", "bash", complex_settings)
|
|
426
|
+
assert decision == "allow"
|
|
427
|
+
|
|
428
|
+
def test_workflow_default_deny_for_unknown(self, complex_settings):
|
|
429
|
+
"""Test that unknown commands get default deny"""
|
|
430
|
+
decision = get_permission_decision("unknown-tool --do-something", "bash", complex_settings)
|
|
431
|
+
assert decision == "default_deny"
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@pytest.mark.skipif(not POST_HOOK_AVAILABLE, reason="post_tool_use hook not available")
|
|
435
|
+
class TestPostToolUseHook:
|
|
436
|
+
"""Test post_tool_use hook integration"""
|
|
437
|
+
|
|
438
|
+
def test_hook_logs_execution(self):
|
|
439
|
+
"""Test that post hook logs execution"""
|
|
440
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
441
|
+
# Test logging functionality
|
|
442
|
+
audit_logger = AuditLogger(log_dir=tmpdir)
|
|
443
|
+
audit_logger.log_execution(
|
|
444
|
+
"bash",
|
|
445
|
+
{"command": "kubectl get pods"},
|
|
446
|
+
"pod/test-pod 1/1 Running",
|
|
447
|
+
0.5,
|
|
448
|
+
0
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
# Check that log file was created
|
|
452
|
+
log_files = list(Path(tmpdir).glob("*.jsonl"))
|
|
453
|
+
assert len(log_files) > 0, "Log files should be created"
|
|
454
|
+
|
|
455
|
+
def test_hook_records_metrics(self):
|
|
456
|
+
"""Test that post hook records metrics"""
|
|
457
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
458
|
+
metrics_collector = MetricsCollector(metrics_dir=tmpdir)
|
|
459
|
+
metrics_collector.record_execution(
|
|
460
|
+
"bash",
|
|
461
|
+
"kubectl get pods",
|
|
462
|
+
0.5,
|
|
463
|
+
True,
|
|
464
|
+
"T0"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Check that metrics file was created
|
|
468
|
+
metrics_files = list(Path(tmpdir).glob("*.jsonl"))
|
|
469
|
+
assert len(metrics_files) > 0, "Metrics files should be created"
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
if __name__ == "__main__":
|
|
473
|
+
pytest.main([__file__, "-v", "--tb=short"])
|