agent-intent-guard 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.
Files changed (47) hide show
  1. agent_intent_guard-0.1.0/PKG-INFO +485 -0
  2. agent_intent_guard-0.1.0/README.md +469 -0
  3. agent_intent_guard-0.1.0/agent_intent_guard.egg-info/PKG-INFO +485 -0
  4. agent_intent_guard-0.1.0/agent_intent_guard.egg-info/SOURCES.txt +45 -0
  5. agent_intent_guard-0.1.0/agent_intent_guard.egg-info/dependency_links.txt +1 -0
  6. agent_intent_guard-0.1.0/agent_intent_guard.egg-info/entry_points.txt +3 -0
  7. agent_intent_guard-0.1.0/agent_intent_guard.egg-info/requires.txt +6 -0
  8. agent_intent_guard-0.1.0/agent_intent_guard.egg-info/top_level.txt +1 -0
  9. agent_intent_guard-0.1.0/intent_guard/__init__.py +3 -0
  10. agent_intent_guard-0.1.0/intent_guard/cli.py +188 -0
  11. agent_intent_guard-0.1.0/intent_guard/proxy.py +159 -0
  12. agent_intent_guard-0.1.0/intent_guard/sdk/__init__.py +39 -0
  13. agent_intent_guard-0.1.0/intent_guard/sdk/decision_cache.py +50 -0
  14. agent_intent_guard-0.1.0/intent_guard/sdk/engine.py +611 -0
  15. agent_intent_guard-0.1.0/intent_guard/sdk/log_redactor.py +47 -0
  16. agent_intent_guard-0.1.0/intent_guard/sdk/mcp_proxy.py +352 -0
  17. agent_intent_guard-0.1.0/intent_guard/sdk/providers.py +382 -0
  18. agent_intent_guard-0.1.0/intent_guard/sdk/rate_limiter.py +122 -0
  19. agent_intent_guard-0.1.0/intent_guard/sdk/response_guard.py +147 -0
  20. agent_intent_guard-0.1.0/intent_guard/sdk/semantic_eval.py +92 -0
  21. agent_intent_guard-0.1.0/intent_guard/sdk/tool_snapshot.py +48 -0
  22. agent_intent_guard-0.1.0/intent_guard/sdk/validator.py +290 -0
  23. agent_intent_guard-0.1.0/intent_guard/sdk/watcher.py +63 -0
  24. agent_intent_guard-0.1.0/pyproject.toml +36 -0
  25. agent_intent_guard-0.1.0/setup.cfg +4 -0
  26. agent_intent_guard-0.1.0/setup.py +3 -0
  27. agent_intent_guard-0.1.0/tests/test_advisory_mode.py +100 -0
  28. agent_intent_guard-0.1.0/tests/test_claude_pretooluse_hook_integration.py +108 -0
  29. agent_intent_guard-0.1.0/tests/test_decision_cache.py +81 -0
  30. agent_intent_guard-0.1.0/tests/test_encoded_payloads.py +59 -0
  31. agent_intent_guard-0.1.0/tests/test_hook_integration.py +142 -0
  32. agent_intent_guard-0.1.0/tests/test_hot_reload.py +132 -0
  33. agent_intent_guard-0.1.0/tests/test_injection_detection.py +104 -0
  34. agent_intent_guard-0.1.0/tests/test_integration_phases.py +393 -0
  35. agent_intent_guard-0.1.0/tests/test_log_redaction.py +177 -0
  36. agent_intent_guard-0.1.0/tests/test_ollama_provider_live_semantic.py +381 -0
  37. agent_intent_guard-0.1.0/tests/test_path_traversal.py +47 -0
  38. agent_intent_guard-0.1.0/tests/test_policy_validation.py +193 -0
  39. agent_intent_guard-0.1.0/tests/test_proxy_stdio.py +147 -0
  40. agent_intent_guard-0.1.0/tests/test_rate_limiting.py +300 -0
  41. agent_intent_guard-0.1.0/tests/test_response_inspection.py +64 -0
  42. agent_intent_guard-0.1.0/tests/test_rubric_scoring.py +415 -0
  43. agent_intent_guard-0.1.0/tests/test_secret_pii_scanning.py +119 -0
  44. agent_intent_guard-0.1.0/tests/test_semantic_eval.py +54 -0
  45. agent_intent_guard-0.1.0/tests/test_starter_templates.py +73 -0
  46. agent_intent_guard-0.1.0/tests/test_system_policy_webhook.py +189 -0
  47. agent_intent_guard-0.1.0/tests/test_tool_snapshot_detection.py +63 -0
@@ -0,0 +1,485 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-intent-guard
3
+ Version: 0.1.0
4
+ Summary: MCP tool-call guardrails with static and semantic policy checks
5
+ License-Expression: MIT
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3 :: Only
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: PyYAML>=6.0.0
12
+ Requires-Dist: requests>=2.31.0
13
+ Requires-Dist: litellm>=1.82.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
16
+
17
+ # IntentGuard
18
+
19
+ IntentGuard is a Python guardrail layer for MCP tool calls. It runs as a proxy between an agent client and an MCP server, enforcing both static policy checks and optional semantic intent checks before a tool call is forwarded.
20
+
21
+ ## What is implemented (MVP)
22
+
23
+ The current implementation covers all 4 roadmap phases from `agent.md`:
24
+
25
+ 1. **CLI Interceptor (Phase 1)**
26
+ `intent_guard/proxy.py` + `intent_guard/sdk/mcp_proxy.py` implement a stdio proxy that intercepts `tools/call` JSON-RPC requests and can block/allow calls.
27
+ 2. **Static Engine (Phase 2)**
28
+ `intent_guard/sdk/engine.py` loads YAML policy and enforces:
29
+ - `forbidden_tools`
30
+ - `protected_paths` (glob/fnmatch style)
31
+ - `max_tokens_per_call`
32
+ - `custom_policies` (tool-specific argument requirements/forbidden arguments)
33
+ 3. **Semantic Guardrail Providers (Phase 3)**
34
+ `intent_guard/sdk/providers.py` supports:
35
+ - `OllamaProvider` (`POST /api/generate`)
36
+ - `LiteLLMProvider` (`litellm.completion`) using `LLM_MODEL` and `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` from env
37
+ Both providers include retries (exponential backoff + jitter) and a circuit breaker.
38
+ 4. **Pause & Resume Feedback Loop (Phase 4)**
39
+ `terminal_approval_prompt` provides interactive approval for flagged calls (`Allow? [y/N]`).
40
+
41
+ ## Repository layout
42
+
43
+ ```text
44
+ intent_guard/
45
+ ├── __init__.py
46
+ ├── proxy.py
47
+ └── sdk/
48
+ ├── __init__.py
49
+ ├── engine.py
50
+ ├── mcp_proxy.py
51
+ └── providers.py
52
+ schema/
53
+ └── policy.yaml
54
+ tests/
55
+ ├── conftest.py
56
+ └── test_integration_phases.py
57
+ ```
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ python3 -m venv .venv
63
+ .venv/bin/pip install -r requirements.txt
64
+ ```
65
+
66
+ ## Run tests
67
+
68
+ ```bash
69
+ .venv/bin/pytest -q
70
+ ```
71
+
72
+ Run live Ollama semantic tests only (requires local Ollama + `llama3.1:8b` available):
73
+
74
+ ```bash
75
+ .venv/bin/pytest -q -m runOllamaProvider
76
+ ```
77
+
78
+ If local model responses are slow, increase timeout (seconds):
79
+
80
+ ```bash
81
+ OLLAMA_TIMEOUT_SECONDS=120 .venv/bin/pytest -q -m runOllamaProvider
82
+ ```
83
+
84
+ The live semantic suite defaults to `OLLAMA_RAW=false` and bounded generation tuned for `llama3.1:8b`. You can tune:
85
+
86
+ ```bash
87
+ OLLAMA_TIMEOUT_SECONDS=60 OLLAMA_NUM_PREDICT=256 OLLAMA_RAW=false \
88
+ .venv/bin/pytest -q -m runOllamaProvider
89
+ ```
90
+
91
+ Integration tests cover all phases:
92
+ - phase 1: interception and logging behavior
93
+ - phase 2: static policy blocking
94
+ - phase 3: semantic provider flow (mocked Ollama HTTP call)
95
+ - phase 4: approval allow/deny behavior
96
+
97
+ ## Policy file
98
+
99
+ Use `schema/policy.yaml` as a starting point:
100
+
101
+ ```yaml
102
+ static_rules:
103
+ forbidden_tools: ["delete_database", "purge_all"]
104
+ protected_paths: ["/etc/*", ".env", "src/auth/*"]
105
+ max_tokens_per_call: 4000
106
+ rate_limits:
107
+ enabled: 1 # required to turn rate limiting on; 0/false bypasses checks
108
+ default:
109
+ max_calls: 60
110
+ window_seconds: 60
111
+ by_tool:
112
+ write_file:
113
+ max_calls: 10
114
+ window_seconds: 60
115
+
116
+ custom_policies:
117
+ - tool_name: write_file
118
+ args:
119
+ all_present: ["path", "content"]
120
+ should_not_present: ["sudo"]
121
+
122
+ semantic_rules:
123
+ provider: ollama # or litellm
124
+ mode: enforce # off | enforce | advisory
125
+ prompt_version: "v2"
126
+ guardrail_model: llama3.1:8b
127
+ critical_intent_threshold: 0.85
128
+ retry_attempts: 2
129
+ retry_base_delay_seconds: 0.25
130
+ retry_max_delay_seconds: 2.0
131
+ retry_jitter_ratio: 0.2
132
+ circuit_breaker_failures: 3
133
+ circuit_breaker_reset_seconds: 30
134
+ provider_fail_mode:
135
+ default: advisory # fail-open
136
+ by_tool:
137
+ delete_database: enforce # fail-closed
138
+ constraints:
139
+ - intent: modify_source_code
140
+ allowed_scope: Actions must only affect UI components or styles.
141
+ forbidden_scope: Should not modify database schemas or auth logic.
142
+ ```
143
+
144
+ ### Rubric scoring (v2)
145
+
146
+ Set `prompt_version: "v2"` to switch from opaque LLM-assigned scores to
147
+ multi-signal rubric scoring. Instead of asking the LLM for a single confidence
148
+ number, the engine asks concrete yes/no questions across multiple dimensions
149
+ and computes the score deterministically from the answers.
150
+
151
+ ```yaml
152
+ semantic_rules:
153
+ prompt_version: "v2"
154
+ critical_intent_threshold: 0.85
155
+ scoring:
156
+ dimensions:
157
+ tool_task_alignment:
158
+ weight: 0.25
159
+ argument_scope_compliance:
160
+ weight: 0.30
161
+ no_forbidden_scope_violation:
162
+ weight: 0.30
163
+ no_side_effect_risk:
164
+ weight: 0.15
165
+ ```
166
+
167
+ Default dimensions (used when `scoring` is omitted):
168
+
169
+ | Dimension | Question | Default weight |
170
+ |---|---|---|
171
+ | `tool_task_alignment` | Is this tool appropriate for the stated task? | 0.25 |
172
+ | `argument_scope_compliance` | Are arguments within the allowed scope? | 0.30 |
173
+ | `no_forbidden_scope_violation` | Do arguments avoid the forbidden scope? | 0.30 |
174
+ | `no_side_effect_risk` | Is the call free of destructive/exfil risk? | 0.15 |
175
+
176
+ Score formula: `Σ(weight × pass) / Σ(weight)`. With 4 equal-pass dimensions
177
+ the score is 1.0; any single failure drops below the 0.85 threshold.
178
+
179
+ Decisions include `dimension_scores` with per-dimension `passed` and
180
+ `evidence` for full auditability.
181
+
182
+ ## CLI usage
183
+
184
+ ```bash
185
+ INTENT_GUARD_TASK="Only update frontend styles" \
186
+ python -m intent_guard.proxy \
187
+ --policy schema/policy.yaml \
188
+ --target "npx @modelcontextprotocol/server-filesystem /path/to/repo" \
189
+ --model llama3.1:8b \
190
+ --approval-webhook "https://approval.internal/intent-guard" \
191
+ --approval-timeout 10 \
192
+ --approval-default-action deny
193
+ ```
194
+
195
+ ### Flags
196
+ - `--policy`: YAML policy path
197
+ - `--target`: target MCP server command
198
+ - `--model`: optional Ollama model name for semantic checks
199
+ - `--task`: optional task context (or set `INTENT_GUARD_TASK`)
200
+ - `--ask-approval`: prompt user before allowing flagged calls
201
+ - `--approval-webhook`: call this webhook for non-interactive approval decisions
202
+ - `--approval-timeout`: timeout (seconds) for webhook approvals
203
+ - `--approval-default-action`: `allow` or `deny` when webhook approval times out/fails
204
+
205
+ ## Native hook integration
206
+
207
+ IntentGuard can run as the policy engine behind native hooks in Claude Code, Copilot, and Cursor.
208
+
209
+ ### Evaluate command
210
+
211
+ Use the unified command:
212
+
213
+ ```bash
214
+ intent-guard evaluate --policy schema/policy.yaml
215
+ ```
216
+
217
+ Input:
218
+ - Reads a hook payload JSON object from stdin
219
+ - Supports generic keys like `tool_name`, `arguments`, `task_context`
220
+ - Also supports nested payloads (`params.name`, `params.arguments`) and common aliases (`tool_input`, `args`, `prompt`)
221
+
222
+ Output:
223
+ - Prints a `GuardDecision` JSON object to stdout
224
+ - Exit code `0` for allow, `1` for block, `2` for invalid input
225
+
226
+ ### Hook config templates
227
+
228
+ Template files are shipped under `hooks/`:
229
+ - `hooks/claude-code/settings.json`
230
+ - `hooks/copilot/hooks.json`
231
+ - `hooks/cursor/hooks.json`
232
+
233
+ Each template invokes:
234
+
235
+ ```bash
236
+ cat | intent-guard evaluate --policy schema/policy.yaml
237
+ ```
238
+
239
+ This lets platform-native hooks call IntentGuard directly instead of wrapping only MCP servers.
240
+
241
+ ## Encoded payload detection
242
+
243
+ Static checks can decode and normalize argument payloads before matching:
244
+ - URL decoding
245
+ - Unicode normalization (NFKC)
246
+ - Base64 decoding (when valid)
247
+
248
+ Enable or disable via:
249
+
250
+ ```yaml
251
+ static_rules:
252
+ decode_arguments: true
253
+ ```
254
+
255
+ When enabled, injection, sensitive-data, and protected-path checks run against decoded variants to catch obfuscated bypasses.
256
+
257
+ ## Response-side inspection
258
+
259
+ IntentGuard can inspect MCP server responses before forwarding them to the client.
260
+
261
+ Configure `response_rules` in policy:
262
+
263
+ ```yaml
264
+ response_rules:
265
+ action: block # block | warn | redact
266
+ detect_base64: true
267
+ patterns:
268
+ - name: "GitHub Token"
269
+ pattern: "gh[ps]_[A-Za-z0-9_]{36,}"
270
+ ```
271
+
272
+ Behavior:
273
+ - `block`: return JSON-RPC error and suppress original response
274
+ - `warn`: forward response and log warning decision
275
+ - `redact`: redact matched text and forward sanitized response
276
+
277
+ ## Tool description change detection (rug-pull protection)
278
+
279
+ IntentGuard can snapshot MCP `tools/list` metadata and detect changes over time.
280
+
281
+ Configure:
282
+
283
+ ```yaml
284
+ tool_change_rules:
285
+ enabled: true
286
+ action: warn # warn | block
287
+ ```
288
+
289
+ Behavior:
290
+ - On first `tools/list`, stores snapshot in `.intent-guard/tool-snapshots/<server-hash>.json`
291
+ - On subsequent `tools/list`, compares `name`, `description`, and `inputSchema`
292
+ - `warn`: log warning and continue
293
+ - `block`: block response when drift is detected
294
+
295
+ ### Semantic mode and provider failure behavior
296
+
297
+ `semantic_rules.mode` controls normal semantic enforcement:
298
+ - `off`: semantic check disabled
299
+ - `enforce`: semantic failures block tool calls
300
+ - `advisory`: semantic failures are logged as warnings but calls are allowed
301
+
302
+ `semantic_rules.provider_fail_mode` controls behavior when semantic provider is unavailable:
303
+ - supports `default` and per-tool `by_tool` override
304
+ - values use the same mode set: `off|enforce|advisory`
305
+
306
+ Behavior matrix for tool criticality tiers (example mapping):
307
+
308
+ | Tool tier | `provider_fail_mode` | Outcome on provider outage |
309
+ |---|---|---|
310
+ | Critical tools | `enforce` | Fail-closed (block + approval required) |
311
+ | Standard tools | `advisory` | Fail-open with warning decision |
312
+ | Low-risk tools | `off` | Fail-open without warning severity |
313
+
314
+ Define tiers by assigning tools in `provider_fail_mode.by_tool`.
315
+
316
+ `semantic_rules.prompt_version` is copied into every semantic decision and log entry as `semantic_prompt_version` so prompt changes are auditable.
317
+
318
+ ### Semantic decision caching
319
+
320
+ To reduce repeated provider calls for identical semantic evaluations:
321
+
322
+ ```yaml
323
+ semantic_rules:
324
+ decision_cache:
325
+ enabled: true
326
+ max_size: 256
327
+ ttl_seconds: 300
328
+ ```
329
+
330
+ Cache key uses `(tool_name, arguments, task_context)`. Static checks always run; only semantic verdicts are cached.
331
+
332
+ ### LiteLLM provider
333
+
334
+ To use the API provider, set in `.env` (or process env):
335
+
336
+ ```bash
337
+ LLM_MODEL=claude-3-5-sonnet-20241022
338
+ ANTHROPIC_API_KEY=...
339
+ # or OPENAI_API_KEY=...
340
+ ```
341
+
342
+ Then set `semantic_rules.provider: litellm` (or just set `LLM_MODEL` and omit explicit provider).
343
+
344
+ ### CI break-glass options
345
+
346
+ - `INTENT_GUARD_BREAK_GLASS_TOKEN`: if set, flagged calls are auto-approved with override metadata.
347
+ - `INTENT_GUARD_BREAK_GLASS_SIGNED_TOKEN` + `INTENT_GUARD_BREAK_GLASS_SIGNING_KEY`: optional HMAC-signed break-glass token for CI. Token format is `<base64url(json payload)>.<base64url(signature)>` where signature is `HMAC-SHA256(payload_part, signing_key)` and payload contains future `exp` (unix timestamp), for example `{"exp": 4102444800}`.
348
+ - `INTENT_GUARD_APPROVAL_AUTH_TOKEN`: bearer token added to webhook approval requests.
349
+
350
+ ## SDK usage (Python)
351
+
352
+ ```python
353
+ from intent_guard import IntentGuardSDK
354
+
355
+ guard = IntentGuardSDK(
356
+ policy_path="schema/policy.yaml",
357
+ local_model="llama3.1:8b",
358
+ task_context="Only modify UI components"
359
+ )
360
+
361
+ decision = guard.evaluate("write_file", {"path": "src/auth/config.py"})
362
+ print(decision.allowed, decision.reason)
363
+ ```
364
+
365
+ ## GuardDecision contract (stable)
366
+
367
+ `GuardDecision` now includes machine-readable metadata for enforcement and analytics:
368
+
369
+ - `decision_id` (UUID)
370
+ - `code`
371
+ - `severity`
372
+ - `policy_name`
373
+ - `policy_version`
374
+ - `rule_id`
375
+ - `timestamp` (UTC ISO-8601)
376
+ - `override` (`who`/`why`/`ttl`, when manually approved)
377
+ - `semantic_prompt_version` (when semantic checks are applied)
378
+
379
+ Backward compatibility:
380
+ - Existing fields `allowed`, `reason`, `requires_approval`, `semantic_score` are unchanged.
381
+ - New fields are always present with safe defaults, so existing consumers can ignore them.
382
+
383
+ ## Semantic eval harness
384
+
385
+ IntentGuard ships a lightweight semantic eval harness used in tests to measure model behavior on known-safe and known-unsafe tool calls.
386
+
387
+ - Dataset fixtures: `tests/fixtures/semantic_eval_dataset.json`
388
+ - Replay verdicts: `tests/fixtures/semantic_eval_verdicts.json`
389
+ - Metrics computed: precision, recall, accuracy
390
+
391
+ This enables reproducible regression checks for semantic policy quality.
392
+
393
+ Versioning/migration strategy:
394
+ - Keep parsing logic tolerant of unknown fields.
395
+ - Use `policy_version` + `code` + `rule_id` for downstream contract evolution and dashboards.
396
+ - Prefer adding new fields over changing/removing existing field semantics.
397
+
398
+ ## Usage examples with popular tools
399
+
400
+ ### 1) Claude Code (MCP server proxy)
401
+
402
+ Configure the MCP server command to run through IntentGuard:
403
+
404
+ ```json
405
+ {
406
+ "mcpServers": {
407
+ "filesystem": {
408
+ "command": "python",
409
+ "args": [
410
+ "-m",
411
+ "intent_guard.proxy",
412
+ "--policy",
413
+ "schema/policy.yaml",
414
+ "--target",
415
+ "npx @modelcontextprotocol/server-filesystem /path/to/repo",
416
+ "--ask-approval"
417
+ ],
418
+ "env": {
419
+ "INTENT_GUARD_TASK": "Refactor UI only; do not touch auth or database"
420
+ }
421
+ }
422
+ }
423
+ }
424
+ ```
425
+
426
+ ### 2) Codex (MCP command wrapping)
427
+
428
+ For Codex setups that support MCP server command configuration, point the server command to IntentGuard first, then to your real MCP server as `--target`:
429
+
430
+ ```bash
431
+ python -m intent_guard.proxy \
432
+ --policy schema/policy.yaml \
433
+ --target "npx @modelcontextprotocol/server-filesystem /path/to/repo" \
434
+ --ask-approval
435
+ ```
436
+
437
+ Use that command as the configured MCP server entry in your Codex environment.
438
+
439
+ ### 3) LangSmith / LangChain workflows
440
+
441
+ Use IntentGuard before each tool execution and keep normal LangSmith tracing:
442
+
443
+ ```python
444
+ from langsmith import traceable
445
+ from intent_guard import IntentGuardSDK
446
+
447
+ guard = IntentGuardSDK(
448
+ policy_path="schema/policy.yaml",
449
+ task_context="Only update docs and UI text"
450
+ )
451
+
452
+ @traceable(name="guarded_tool_call")
453
+ def guarded_call(tool_name: str, args: dict, tool_callable):
454
+ decision = guard.evaluate(tool_name, args)
455
+ if not decision.allowed:
456
+ raise PermissionError(f"IntentGuard blocked: {decision.reason}")
457
+ return tool_callable(**args)
458
+ ```
459
+
460
+ This keeps execution decisions visible in traces while enforcing IntentGuard policy at runtime.
461
+
462
+ ## Build and publish (pip / Artifactory)
463
+
464
+ Build source and wheel distributions:
465
+
466
+ ```bash
467
+ python3 -m venv .venv
468
+ .venv/bin/pip install -U pip build twine
469
+ .venv/bin/python -m build
470
+ ```
471
+
472
+ Publish to your Artifactory PyPI repository:
473
+
474
+ ```bash
475
+ export TWINE_USERNAME="<artifactory-username>"
476
+ export TWINE_PASSWORD="<artifactory-password-or-token>"
477
+ .venv/bin/python -m twine upload \
478
+ --repository-url "https://<artifactory-host>/artifactory/api/pypi/<pypi-repo>/local" \
479
+ dist/*
480
+ ```
481
+
482
+ ## Integration testing and Docker
483
+
484
+ Current integration tests are in-process (`tests/test_integration_phases.py`) and do not require a database or cache service.
485
+ If a future change adds external DB/cache dependencies, run those services in Docker for tests (same pattern as `temp-noob/rule-engine`) so test setup remains reproducible.