deepintshield 2.0.0__tar.gz → 2.2.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 (86) hide show
  1. {deepintshield-2.0.0/src/deepintshield.egg-info → deepintshield-2.2.0}/PKG-INFO +132 -14
  2. {deepintshield-2.0.0 → deepintshield-2.2.0}/README.md +129 -13
  3. {deepintshield-2.0.0 → deepintshield-2.2.0}/pyproject.toml +3 -1
  4. deepintshield-2.2.0/src/deepintshield/agentic/enforcement.py +63 -0
  5. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/engine.py +46 -2
  6. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/gate.py +17 -1
  7. deepintshield-2.2.0/src/deepintshield/agentic/integrations/_common.py +190 -0
  8. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/autogen.py +22 -1
  9. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/crewai.py +22 -1
  10. deepintshield-2.2.0/src/deepintshield/agentic/integrations/langchain.py +91 -0
  11. deepintshield-2.2.0/src/deepintshield/agentic/integrations/langgraph.py +189 -0
  12. deepintshield-2.2.0/src/deepintshield/agentic/integrations/litellm.py +104 -0
  13. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/llamaindex.py +26 -1
  14. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/openai_agents.py +43 -0
  15. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/pydanticai.py +21 -1
  16. deepintshield-2.2.0/src/deepintshield/agentic/manifest.py +174 -0
  17. deepintshield-2.2.0/src/deepintshield/agentic/surface.py +227 -0
  18. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/types.py +27 -0
  19. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/client.py +18 -0
  20. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/types.py +6 -0
  21. deepintshield-2.2.0/src/deepintshield/version.py +1 -0
  22. {deepintshield-2.0.0 → deepintshield-2.2.0/src/deepintshield.egg-info}/PKG-INFO +132 -14
  23. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield.egg-info/SOURCES.txt +5 -0
  24. deepintshield-2.2.0/tests/test_agentic_langchain.py +62 -0
  25. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_types.py +9 -0
  26. deepintshield-2.0.0/src/deepintshield/agentic/integrations/_common.py +0 -84
  27. deepintshield-2.0.0/src/deepintshield/agentic/integrations/langgraph.py +0 -62
  28. deepintshield-2.0.0/src/deepintshield/agentic/surface.py +0 -118
  29. deepintshield-2.0.0/src/deepintshield/version.py +0 -1
  30. {deepintshield-2.0.0 → deepintshield-2.2.0}/LICENSE +0 -0
  31. {deepintshield-2.0.0 → deepintshield-2.2.0}/setup.cfg +0 -0
  32. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/__init__.py +0 -0
  33. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/_gemini_cache.py +0 -0
  34. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/_prompt_cache.py +0 -0
  35. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agent.py +0 -0
  36. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/__init__.py +0 -0
  37. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/__init__.py +0 -0
  38. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/base.py +0 -0
  39. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/entra.py +0 -0
  40. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/oidc.py +0 -0
  41. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/zeroid.py +0 -0
  42. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/decorators.py +0 -0
  43. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/errors.py +0 -0
  44. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/__init__.py +0 -0
  45. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/obligations.py +0 -0
  46. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/config.py +0 -0
  47. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/errors.py +0 -0
  48. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/__init__.py +0 -0
  49. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/autogen.py +0 -0
  50. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/crewai.py +0 -0
  51. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/langgraph.py +0 -0
  52. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/llamaindex.py +0 -0
  53. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/openai_agents.py +0 -0
  54. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/pydanticai.py +0 -0
  55. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/__init__.py +0 -0
  56. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/adapters/__init__.py +0 -0
  57. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/adapters/anthropic.py +0 -0
  58. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/adapters/langchain.py +0 -0
  59. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/adapters/openai.py +0 -0
  60. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/client.py +0 -0
  61. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/tool.py +0 -0
  62. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/__init__.py +0 -0
  63. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/anthropic.py +0 -0
  64. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/bedrock.py +0 -0
  65. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/genai.py +0 -0
  66. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/langchain.py +0 -0
  67. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/langgraph.py +0 -0
  68. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/litellm.py +0 -0
  69. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/openai.py +0 -0
  70. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/pydanticai.py +0 -0
  71. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/rag.py +0 -0
  72. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/transport.py +0 -0
  73. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield.egg-info/dependency_links.txt +0 -0
  74. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield.egg-info/requires.txt +0 -0
  75. {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield.egg-info/top_level.txt +0 -0
  76. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_agent.py +0 -0
  77. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_agentic.py +0 -0
  78. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_client.py +0 -0
  79. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_config.py +0 -0
  80. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_errors.py +0 -0
  81. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_gemini_cache.py +0 -0
  82. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_prompt_cache.py +0 -0
  83. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_providers.py +0 -0
  84. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_rag.py +0 -0
  85. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_rag_guard.py +0 -0
  86. {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_transport.py +0 -0
@@ -1,11 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepintshield
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: Unified Python SDK for routing chat, RAG, agentic tool-gating, identity, and MCP traffic through DeepintShield — drop-in across the top agentic frameworks.
5
5
  Author: DeepintShield
6
6
  License: Apache-2.0
7
7
  Project-URL: Homepage, https://app.deepintshield.com
8
8
  Project-URL: Documentation, https://app.deepintshield.com/docs
9
+ Project-URL: Examples, https://github.com/deepintai/DeepintShieldFull/tree/develop/deepintshield/examples
10
+ Project-URL: Source, https://github.com/deepintai/DeepintShieldFull/tree/develop/deepintshield
9
11
  Keywords: llm,agent,security,guardrails,pep,pdp,agentic,deepintshield,entra,langgraph,crewai
10
12
  Requires-Python: >=3.10
11
13
  Description-Content-Type: text/markdown
@@ -311,24 +313,89 @@ runs `decide()` first and the verdict maps to a Python outcome — `ALLOW` runs
311
313
  the body, `MASK` redacts PII kwargs, `REQUIRE_APPROVAL` blocks for a human, and
312
314
  `DENY` raises `GuardrailDenied`.
313
315
 
316
+ ### Zero extra code — enforcement is automatic and non-bypassable
317
+
318
+ The moment you construct `DeepintShield(...)`, the SDK installs guards for every
319
+ agent framework you've imported (LangGraph, CrewAI, LlamaIndex, AutoGen,
320
+ PydanticAI, the OpenAI Agents SDK, LiteLLM). After that, **compiling / building
321
+ an agent yields an already-governed object** — you can't forget to gate it and
322
+ you can't bypass it by invoking the un-governed one (there isn't one).
323
+
314
324
  ```python
315
- @shield.agentic.tool("db.write", recovery_cost="high")
316
- def write_ledger(row: dict) -> dict:
317
- return db.execute("INSERT INTO ledger …", row)
325
+ from langgraph.graph import StateGraph, START, END
326
+ from deepintshield import DeepintShield, GuardrailDenied
318
327
 
319
- # Or a direct decision probe:
320
- decision = shield.agentic.decide(tool="db.write", args={"amount": 12})
328
+ shield = DeepintShield.from_env() # ← guards install here; that's the only line
329
+
330
+ def crm_read(s): ... # your plain tools/nodes, unchanged
331
+ def admin_grant(s): ...
332
+
333
+ g = StateGraph(State)
334
+ g.add_node("read_step", crm_read)
335
+ g.add_node("admin_step", admin_grant)
336
+ ...
337
+ app = g.compile() # auto-governed — every node now gated by the PDP
338
+
339
+ app.invoke({...}) # a DENY raises GuardrailDenied before the node runs
340
+ ```
341
+
342
+ If you import a framework *after* building the client, call
343
+ `shield.agentic.enforce()` once to (re)install the guards.
344
+
345
+ > **Security follows the implementation, not the label.** Each node/tool is
346
+ > governed by its **function name** (`crm_read`), not the node label
347
+ > (`read_step`), and the decision is bound to a fingerprint of the function's
348
+ > **source** — so editing the body is detected and policies target `crm_read`.
349
+ >
350
+ > **Trust boundary:** these client guards are cooperative defense-in-depth. A
351
+ > determined process can un-patch them or call a tool's raw function, so the
352
+ > gateway (MCP / LLM in the call path) remains the authoritative boundary.
353
+
354
+ ### `govern()` — register + threat-scan + instrument (explicit, idempotent)
355
+
356
+ `govern()` does everything the auto-guard does, explicitly: it **describes** the
357
+ agent's declared tool surface, **registers** that blueprint with the server
358
+ (which **threat-scans each tool's source** for RCE / shell-out / exfiltration —
359
+ OWASP Agentic **T11 / T17** — server-side, ZDR), and **instruments** every call.
360
+
361
+ ```python
362
+ app = shield.agentic.govern(app) # idempotent — safe alongside the auto-guard
363
+ ```
364
+
365
+ A tool whose source scans malicious is flagged (Agentic → Findings) and, when
366
+ the workspace enables **Enforce code threat** (Rollout), denied — even if a
367
+ policy would otherwise allow it. A tool called but never declared shows up as
368
+ **ASI04 drift** under Agentic → Discovery.
369
+
370
+ ### `guard()` — LangChain callback / in-place instrument
371
+
372
+ `shield.agentic.guard()` (no argument) returns a native LangChain callback
373
+ handler; attaching it once gates *every* tool the agent calls — the framework
374
+ supplies the tool name and the **gateway resolves the tier, policy, recovery
375
+ cost and identity server-side**.
376
+
377
+ ```python
378
+ guard = shield.agentic.guard()
379
+ agent_executor.invoke({"input": "…"}, config={"callbacks": [guard]})
321
380
  ```
322
381
 
323
- Wrap a whole framework's tools in one line:
382
+ `guard(target)` auto-detects and instruments a framework object in place:
324
383
 
325
384
  ```python
326
- shield.agentic.langgraph(compiled_graph) # gate every tool node
327
- shield.agentic.crewai(tools) # gate CrewAI BaseTools
328
- shield.agentic.openai_agents(agent) # gate FunctionTools on an Agent
329
- shield.agentic.llamaindex(tools)
330
- shield.agentic.autogen(agent)
331
- shield.agentic.pydanticai(agent)
385
+ shield.agentic.guard(compiled_graph) # LangGraph — gate every tool node
386
+ shield.agentic.guard(crewai_tools) # CrewAI BaseTools
387
+ shield.agentic.guard(openai_agent) # OpenAI Agents FunctionTools
388
+ shield.agentic.guard(pydantic_agent) # PydanticAI agent
389
+ ```
390
+
391
+ ### Explicit decorator / decision probe
392
+
393
+ ```python
394
+ @shield.agentic.tool("db.write")
395
+ def write_ledger(row: dict) -> dict:
396
+ return db.execute("INSERT INTO ledger …", row)
397
+
398
+ decision = shield.agentic.decide(tool="db.write", args={"amount": 12})
332
399
  ```
333
400
 
334
401
  ```python
@@ -340,6 +407,28 @@ except GuardrailDenied as e:
340
407
  log.warning("denied: %s (decision_id=%s)", e.reason, e.decision_id)
341
408
  ```
342
409
 
410
+ ### Optional risk signals (OWASP Agentic gap operands)
411
+
412
+ For threats only your app can observe, pass an ABAC signal on a `decide()` and
413
+ author a policy on it (one-click templates ship under **Agentic → Templates**):
414
+
415
+ | Signal | Threat | Policy operand |
416
+ | --- | --- | --- |
417
+ | `memory_integrity` | T1 Memory Poisoning | `memory_integrity eq true` |
418
+ | `hallucination_risk` | T5 Cascading Hallucination | `hallucination_risk gte 0.8` |
419
+ | `goal_drift` | T7 Misaligned & Deceptive | `goal_drift eq true` |
420
+ | `comm_integrity` | T12 Agent Comm Poisoning | `comm_integrity eq true` |
421
+ | `delegation_depth` | T14 Human Attacks on MAS | `delegation_depth gt 4` (server-computed) |
422
+
423
+ ```python
424
+ from deepintshield import ContextBag, DelegationContext
425
+
426
+ shield.agentic.decide(DelegationContext(
427
+ tool="ledger.post", virtual_key=shield.virtual_key,
428
+ context=ContextBag(hallucination_risk=0.91, goal_drift=True),
429
+ ))
430
+ ```
431
+
343
432
  ### Agent identity (zero config)
344
433
 
345
434
  When the virtual key is bound to an identity provider, the SDK auto-discovers
@@ -381,6 +470,35 @@ app = graph.compile()
381
470
 
382
471
  ---
383
472
 
473
+ ## Multimodal guardrails (transparent)
474
+
475
+ Image generation, image edits, audio (TTS / transcription), video, embedding and
476
+ rerank requests are guarded **at the gateway** — no SDK changes and no extra code.
477
+ Keep using the native provider SDKs through DeepIntShield; when the operator
478
+ enables `GUARDRAILS_MULTIMODAL`, the gateway evaluates the text these requests
479
+ already carry (image/TTS/video prompts, transcripts) and the binary artifacts
480
+ themselves, blocking or flagging per your policies.
481
+
482
+ ```python
483
+ client = shield.openai()
484
+
485
+ # Guarded automatically — the image prompt is evaluated before generation.
486
+ img = client.images.generate(model="gpt-image-1", prompt="a serene mountain lake")
487
+
488
+ # A blocked prompt surfaces as the provider SDK's normal HTTP error:
489
+ from deepintshield import DeepintShieldError
490
+ try:
491
+ client.audio.speech.create(model="tts-1", voice="alloy", input="<disallowed text>")
492
+ except DeepintShieldError as exc:
493
+ print(exc.status_code, exc.payload) # 403 guardrail_blocked
494
+ ```
495
+
496
+ For an explicit verdict (rather than transparent enforcement), `evaluate_guardrail`
497
+ returns a `GuardrailResult`; `result.mode` reports whether the verdict was
498
+ enforcing (`sync`) or observe-only (`shadow`).
499
+
500
+ ---
501
+
384
502
  ## MCP
385
503
 
386
504
  Generic MCP support — works with any server connected to your DeepintShield
@@ -537,5 +655,5 @@ the client.
537
655
 
538
656
  ## More examples
539
657
 
540
- See [examples/](examples/) for runnable per-provider chat, RAG, agent, and MCP
658
+ See [examples/](https://github.com/deepintai/DeepintShieldFull/tree/develop/deepintshield/examples) for runnable per-provider chat, RAG, agent, and MCP
541
659
  scripts.
@@ -242,24 +242,89 @@ runs `decide()` first and the verdict maps to a Python outcome — `ALLOW` runs
242
242
  the body, `MASK` redacts PII kwargs, `REQUIRE_APPROVAL` blocks for a human, and
243
243
  `DENY` raises `GuardrailDenied`.
244
244
 
245
+ ### Zero extra code — enforcement is automatic and non-bypassable
246
+
247
+ The moment you construct `DeepintShield(...)`, the SDK installs guards for every
248
+ agent framework you've imported (LangGraph, CrewAI, LlamaIndex, AutoGen,
249
+ PydanticAI, the OpenAI Agents SDK, LiteLLM). After that, **compiling / building
250
+ an agent yields an already-governed object** — you can't forget to gate it and
251
+ you can't bypass it by invoking the un-governed one (there isn't one).
252
+
245
253
  ```python
246
- @shield.agentic.tool("db.write", recovery_cost="high")
247
- def write_ledger(row: dict) -> dict:
248
- return db.execute("INSERT INTO ledger …", row)
254
+ from langgraph.graph import StateGraph, START, END
255
+ from deepintshield import DeepintShield, GuardrailDenied
249
256
 
250
- # Or a direct decision probe:
251
- decision = shield.agentic.decide(tool="db.write", args={"amount": 12})
257
+ shield = DeepintShield.from_env() # ← guards install here; that's the only line
258
+
259
+ def crm_read(s): ... # your plain tools/nodes, unchanged
260
+ def admin_grant(s): ...
261
+
262
+ g = StateGraph(State)
263
+ g.add_node("read_step", crm_read)
264
+ g.add_node("admin_step", admin_grant)
265
+ ...
266
+ app = g.compile() # auto-governed — every node now gated by the PDP
267
+
268
+ app.invoke({...}) # a DENY raises GuardrailDenied before the node runs
269
+ ```
270
+
271
+ If you import a framework *after* building the client, call
272
+ `shield.agentic.enforce()` once to (re)install the guards.
273
+
274
+ > **Security follows the implementation, not the label.** Each node/tool is
275
+ > governed by its **function name** (`crm_read`), not the node label
276
+ > (`read_step`), and the decision is bound to a fingerprint of the function's
277
+ > **source** — so editing the body is detected and policies target `crm_read`.
278
+ >
279
+ > **Trust boundary:** these client guards are cooperative defense-in-depth. A
280
+ > determined process can un-patch them or call a tool's raw function, so the
281
+ > gateway (MCP / LLM in the call path) remains the authoritative boundary.
282
+
283
+ ### `govern()` — register + threat-scan + instrument (explicit, idempotent)
284
+
285
+ `govern()` does everything the auto-guard does, explicitly: it **describes** the
286
+ agent's declared tool surface, **registers** that blueprint with the server
287
+ (which **threat-scans each tool's source** for RCE / shell-out / exfiltration —
288
+ OWASP Agentic **T11 / T17** — server-side, ZDR), and **instruments** every call.
289
+
290
+ ```python
291
+ app = shield.agentic.govern(app) # idempotent — safe alongside the auto-guard
292
+ ```
293
+
294
+ A tool whose source scans malicious is flagged (Agentic → Findings) and, when
295
+ the workspace enables **Enforce code threat** (Rollout), denied — even if a
296
+ policy would otherwise allow it. A tool called but never declared shows up as
297
+ **ASI04 drift** under Agentic → Discovery.
298
+
299
+ ### `guard()` — LangChain callback / in-place instrument
300
+
301
+ `shield.agentic.guard()` (no argument) returns a native LangChain callback
302
+ handler; attaching it once gates *every* tool the agent calls — the framework
303
+ supplies the tool name and the **gateway resolves the tier, policy, recovery
304
+ cost and identity server-side**.
305
+
306
+ ```python
307
+ guard = shield.agentic.guard()
308
+ agent_executor.invoke({"input": "…"}, config={"callbacks": [guard]})
252
309
  ```
253
310
 
254
- Wrap a whole framework's tools in one line:
311
+ `guard(target)` auto-detects and instruments a framework object in place:
255
312
 
256
313
  ```python
257
- shield.agentic.langgraph(compiled_graph) # gate every tool node
258
- shield.agentic.crewai(tools) # gate CrewAI BaseTools
259
- shield.agentic.openai_agents(agent) # gate FunctionTools on an Agent
260
- shield.agentic.llamaindex(tools)
261
- shield.agentic.autogen(agent)
262
- shield.agentic.pydanticai(agent)
314
+ shield.agentic.guard(compiled_graph) # LangGraph — gate every tool node
315
+ shield.agentic.guard(crewai_tools) # CrewAI BaseTools
316
+ shield.agentic.guard(openai_agent) # OpenAI Agents FunctionTools
317
+ shield.agentic.guard(pydantic_agent) # PydanticAI agent
318
+ ```
319
+
320
+ ### Explicit decorator / decision probe
321
+
322
+ ```python
323
+ @shield.agentic.tool("db.write")
324
+ def write_ledger(row: dict) -> dict:
325
+ return db.execute("INSERT INTO ledger …", row)
326
+
327
+ decision = shield.agentic.decide(tool="db.write", args={"amount": 12})
263
328
  ```
264
329
 
265
330
  ```python
@@ -271,6 +336,28 @@ except GuardrailDenied as e:
271
336
  log.warning("denied: %s (decision_id=%s)", e.reason, e.decision_id)
272
337
  ```
273
338
 
339
+ ### Optional risk signals (OWASP Agentic gap operands)
340
+
341
+ For threats only your app can observe, pass an ABAC signal on a `decide()` and
342
+ author a policy on it (one-click templates ship under **Agentic → Templates**):
343
+
344
+ | Signal | Threat | Policy operand |
345
+ | --- | --- | --- |
346
+ | `memory_integrity` | T1 Memory Poisoning | `memory_integrity eq true` |
347
+ | `hallucination_risk` | T5 Cascading Hallucination | `hallucination_risk gte 0.8` |
348
+ | `goal_drift` | T7 Misaligned & Deceptive | `goal_drift eq true` |
349
+ | `comm_integrity` | T12 Agent Comm Poisoning | `comm_integrity eq true` |
350
+ | `delegation_depth` | T14 Human Attacks on MAS | `delegation_depth gt 4` (server-computed) |
351
+
352
+ ```python
353
+ from deepintshield import ContextBag, DelegationContext
354
+
355
+ shield.agentic.decide(DelegationContext(
356
+ tool="ledger.post", virtual_key=shield.virtual_key,
357
+ context=ContextBag(hallucination_risk=0.91, goal_drift=True),
358
+ ))
359
+ ```
360
+
274
361
  ### Agent identity (zero config)
275
362
 
276
363
  When the virtual key is bound to an identity provider, the SDK auto-discovers
@@ -312,6 +399,35 @@ app = graph.compile()
312
399
 
313
400
  ---
314
401
 
402
+ ## Multimodal guardrails (transparent)
403
+
404
+ Image generation, image edits, audio (TTS / transcription), video, embedding and
405
+ rerank requests are guarded **at the gateway** — no SDK changes and no extra code.
406
+ Keep using the native provider SDKs through DeepIntShield; when the operator
407
+ enables `GUARDRAILS_MULTIMODAL`, the gateway evaluates the text these requests
408
+ already carry (image/TTS/video prompts, transcripts) and the binary artifacts
409
+ themselves, blocking or flagging per your policies.
410
+
411
+ ```python
412
+ client = shield.openai()
413
+
414
+ # Guarded automatically — the image prompt is evaluated before generation.
415
+ img = client.images.generate(model="gpt-image-1", prompt="a serene mountain lake")
416
+
417
+ # A blocked prompt surfaces as the provider SDK's normal HTTP error:
418
+ from deepintshield import DeepintShieldError
419
+ try:
420
+ client.audio.speech.create(model="tts-1", voice="alloy", input="<disallowed text>")
421
+ except DeepintShieldError as exc:
422
+ print(exc.status_code, exc.payload) # 403 guardrail_blocked
423
+ ```
424
+
425
+ For an explicit verdict (rather than transparent enforcement), `evaluate_guardrail`
426
+ returns a `GuardrailResult`; `result.mode` reports whether the verdict was
427
+ enforcing (`sync`) or observe-only (`shadow`).
428
+
429
+ ---
430
+
315
431
  ## MCP
316
432
 
317
433
  Generic MCP support — works with any server connected to your DeepintShield
@@ -468,5 +584,5 @@ the client.
468
584
 
469
585
  ## More examples
470
586
 
471
- See [examples/](examples/) for runnable per-provider chat, RAG, agent, and MCP
587
+ See [examples/](https://github.com/deepintai/DeepintShieldFull/tree/develop/deepintshield/examples) for runnable per-provider chat, RAG, agent, and MCP
472
588
  scripts.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deepintshield"
7
- version = "2.0.0"
7
+ version = "2.2.0"
8
8
  description = "Unified Python SDK for routing chat, RAG, agentic tool-gating, identity, and MCP traffic through DeepintShield — drop-in across the top agentic frameworks."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -64,6 +64,8 @@ dev = [
64
64
  [project.urls]
65
65
  Homepage = "https://app.deepintshield.com"
66
66
  Documentation = "https://app.deepintshield.com/docs"
67
+ Examples = "https://github.com/deepintai/DeepintShieldFull/tree/develop/deepintshield/examples"
68
+ Source = "https://github.com/deepintai/DeepintShieldFull/tree/develop/deepintshield"
67
69
 
68
70
  [tool.setuptools]
69
71
  package-dir = { "" = "src" }
@@ -0,0 +1,63 @@
1
+ """Non-bypassable enforcement installer for every supported framework.
2
+
3
+ The per-framework adapters gate tools *when the developer calls* ``govern()`` /
4
+ ``guard()``. That still leaves a gap: nothing stops code from skipping that call
5
+ and running the agent directly. ``install_all`` closes it by monkey-patching each
6
+ framework's build/execute boundary the moment a DeepintShield client is created,
7
+ so a tool/graph can't run ungoverned — the developer no longer has to remember.
8
+
9
+ Design rules every framework installer follows:
10
+ * **Lazy engine** — takes a ``get_engine`` callable resolved on first use, so
11
+ installing at client construction never forces the agentic surface to build.
12
+ * **Only if imported** — we patch a framework only when it's already in
13
+ ``sys.modules`` (never force-import a dep the user isn't using).
14
+ * **Idempotent** — re-installing re-binds the provider, never double-wraps.
15
+ * **Fail-open on infra, fail-CLOSED on a verdict** — a gateway hiccup must not
16
+ break the app, but a DENY must still block. Each installer swallows
17
+ infrastructure errors and re-raises ``GuardrailDenied`` / approval timeouts.
18
+
19
+ Cooperative defense-in-depth: a determined process can un-patch these or call a
20
+ tool's raw function object, so the gateway (MCP/LLM in the call path) stays the
21
+ authoritative boundary. For ordinary application code this makes enforcement the
22
+ default rather than something to remember.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import sys
28
+ from importlib import import_module
29
+ from typing import Any, Callable
30
+
31
+ # top-level import name → enforcement integration module (relative to this package)
32
+ _FRAMEWORKS: list[tuple[str, str]] = [
33
+ ("langgraph", ".integrations.langgraph"),
34
+ ("crewai", ".integrations.crewai"),
35
+ ("llama_index", ".integrations.llamaindex"),
36
+ ("autogen", ".integrations.autogen"),
37
+ ("autogen_core", ".integrations.autogen"),
38
+ ("pydantic_ai", ".integrations.pydanticai"),
39
+ ("agents", ".integrations.openai_agents"), # `openai-agents` imports as `agents`
40
+ ("litellm", ".integrations.litellm"),
41
+ ]
42
+
43
+
44
+ def install_all(get_engine: Callable[[], Any]) -> list[str]:
45
+ """Install enforcement guards for every supported framework currently
46
+ imported. Returns the list of frameworks guarded. Never raises."""
47
+ installed: list[str] = []
48
+ seen: set[str] = set()
49
+ for mod_name, integ in _FRAMEWORKS:
50
+ if mod_name not in sys.modules or integ in seen:
51
+ continue
52
+ seen.add(integ)
53
+ try:
54
+ m = import_module(integ, package=__package__)
55
+ fn = getattr(m, "enforce", None)
56
+ if callable(fn) and fn(get_engine):
57
+ installed.append(mod_name)
58
+ except Exception: # a single framework's guard never blocks the others
59
+ continue
60
+ return installed
61
+
62
+
63
+ __all__ = ["install_all"]
@@ -23,6 +23,7 @@ from __future__ import annotations
23
23
  import logging
24
24
  import os
25
25
  import time
26
+ import uuid
26
27
  from typing import TYPE_CHECKING, Optional
27
28
 
28
29
  import httpx
@@ -57,6 +58,11 @@ class AgenticEngine:
57
58
  self._agent_credential: Optional[AgentCredential] = agent_credential
58
59
  self._approval_timeout = approval_poll_timeout_seconds
59
60
  self._approval_interval = approval_poll_interval_seconds
61
+ # Per-process run id (OTel/Langfuse style). Stamped on every decide so all
62
+ # of this client's steps group into ONE Agent Execution; a fresh process
63
+ # (new engine) → a new execution. Override per call via dc.session_id, or
64
+ # set engine.session_id to bind a longer-lived agent run to one execution.
65
+ self.session_id: str = "sdk-" + uuid.uuid4().hex[:12]
60
66
 
61
67
  # ──────────────────────────────────────────────────────────────────
62
68
  # Parent-backed connection details
@@ -117,6 +123,10 @@ class AgenticEngine:
117
123
 
118
124
  Retries once with fresh discovery info on 401/403 to handle the case
119
125
  where a platform admin re-bound the VK mid-process."""
126
+ # Stamp the per-process session id unless the caller set their own, so
127
+ # the run's steps group into one execution server-side.
128
+ if not dc.session_id:
129
+ dc.session_id = self.session_id
120
130
  try:
121
131
  return self._post_decide(dc)
122
132
  except GatewayUnavailable:
@@ -130,6 +140,34 @@ class AgenticEngine:
130
140
  return self._post_decide(dc)
131
141
  raise
132
142
 
143
+ def register_blueprint(self, manifest: object) -> Optional[str]:
144
+ """Register the agent's declared tool surface (manifest) with the server
145
+ BEFORE the run — the server stores the declared topology for full-graph
146
+ visualization, policy pre-validation, and declared-vs-observed drift.
147
+
148
+ Best-effort and NON-fatal: a registration failure (offline gateway, older
149
+ server) must never break the agent, so this never raises. Returns the
150
+ server-assigned blueprint id, or None."""
151
+ try:
152
+ payload = manifest.to_dict() if hasattr(manifest, "to_dict") else manifest
153
+ # Stamp the per-process session + principal so the blueprint binds to
154
+ # the same execution the decisions will carry.
155
+ if isinstance(payload, dict):
156
+ payload.setdefault("session_id", self.session_id)
157
+ resp = self._http.post(
158
+ f"{self.gateway_url}/api/agentic-security/blueprints",
159
+ json=payload,
160
+ headers=self._headers(include_agent_token=False),
161
+ )
162
+ if resp.status_code < 300:
163
+ try:
164
+ return resp.json().get("blueprint_id")
165
+ except Exception:
166
+ return None
167
+ except Exception as exc: # never break the run on a registration hiccup
168
+ log.warning("blueprint registration skipped: %s", exc)
169
+ return None
170
+
133
171
  def poll_approval(self, decision_id: str) -> Decision:
134
172
  """Block waiting for a REQUIRE_APPROVAL decision to be resolved by a
135
173
  human. Returns the final Decision (ALLOW or DENY)."""
@@ -140,10 +178,16 @@ class AgenticEngine:
140
178
  headers=self._headers(include_agent_token=False),
141
179
  )
142
180
  if resp.status_code == 200:
143
- state = resp.json().get("state", "pending")
181
+ # Be defensive: a transient/non-JSON body (proxy error page, SPA
182
+ # fallback) must not crash the poll — treat it as "still pending"
183
+ # and keep waiting until the deadline (→ GuardrailApprovalPending).
184
+ try:
185
+ state = resp.json().get("state", "pending")
186
+ except Exception: # noqa: BLE001
187
+ state = "pending"
144
188
  if state == "approved":
145
189
  return Decision(verdict=Verdict.ALLOW, decision_id=decision_id)
146
- if state == "denied":
190
+ if state in ("denied", "expired"):
147
191
  return Decision(
148
192
  verdict=Verdict.DENY,
149
193
  decision_id=decision_id,
@@ -31,6 +31,7 @@ def resolve(
31
31
  *,
32
32
  recovery_cost: str = "",
33
33
  rag_provenance: str = "",
34
+ tool_fingerprint: str = "",
34
35
  ) -> Decision:
35
36
  """Run a PDP decision for ``tool_name`` and raise on any blocking verdict.
36
37
 
@@ -43,7 +44,20 @@ def resolve(
43
44
  tool=tool_name,
44
45
  args_digest=digest(args, kwargs),
45
46
  virtual_key=engine.virtual_key,
46
- context=ContextBag(recovery_cost=recovery_cost, rag_provenance=rag_provenance),
47
+ # PDP subject matchers are authored against an agent role
48
+ # (any_role: ["agent", ...]); the decorator/adapter caller doesn't
49
+ # spell out an identity. Carry a default agent principal + actor_chain
50
+ # so role-scoped policies match the decorator path the same way they
51
+ # match an explicitly-populated DelegationContext. The server only
52
+ # synthesises this for agent-bound VKs; LLM-only VKs need it from here.
53
+ principal="agent:sdk",
54
+ actor_chain=["agent:sdk"],
55
+ identity_type="application",
56
+ context=ContextBag(
57
+ recovery_cost=recovery_cost,
58
+ rag_provenance=rag_provenance,
59
+ tool_fingerprint=tool_fingerprint,
60
+ ),
47
61
  )
48
62
  decision = engine.decide(dc)
49
63
 
@@ -81,6 +95,7 @@ def enforce(
81
95
  *,
82
96
  recovery_cost: str = "",
83
97
  rag_provenance: str = "",
98
+ tool_fingerprint: str = "",
84
99
  ) -> dict[str, Any]:
85
100
  """``resolve`` + apply MASK obligations to ``kwargs``.
86
101
 
@@ -93,6 +108,7 @@ def enforce(
93
108
  kwargs,
94
109
  recovery_cost=recovery_cost,
95
110
  rag_provenance=rag_provenance,
111
+ tool_fingerprint=tool_fingerprint,
96
112
  )
97
113
  return apply_obligations(kwargs, decision.obligations)
98
114