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.
- {deepintshield-2.0.0/src/deepintshield.egg-info → deepintshield-2.2.0}/PKG-INFO +132 -14
- {deepintshield-2.0.0 → deepintshield-2.2.0}/README.md +129 -13
- {deepintshield-2.0.0 → deepintshield-2.2.0}/pyproject.toml +3 -1
- deepintshield-2.2.0/src/deepintshield/agentic/enforcement.py +63 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/engine.py +46 -2
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/gate.py +17 -1
- deepintshield-2.2.0/src/deepintshield/agentic/integrations/_common.py +190 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/autogen.py +22 -1
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/crewai.py +22 -1
- deepintshield-2.2.0/src/deepintshield/agentic/integrations/langchain.py +91 -0
- deepintshield-2.2.0/src/deepintshield/agentic/integrations/langgraph.py +189 -0
- deepintshield-2.2.0/src/deepintshield/agentic/integrations/litellm.py +104 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/llamaindex.py +26 -1
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/openai_agents.py +43 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/pydanticai.py +21 -1
- deepintshield-2.2.0/src/deepintshield/agentic/manifest.py +174 -0
- deepintshield-2.2.0/src/deepintshield/agentic/surface.py +227 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/types.py +27 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/client.py +18 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/types.py +6 -0
- deepintshield-2.2.0/src/deepintshield/version.py +1 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0/src/deepintshield.egg-info}/PKG-INFO +132 -14
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield.egg-info/SOURCES.txt +5 -0
- deepintshield-2.2.0/tests/test_agentic_langchain.py +62 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_types.py +9 -0
- deepintshield-2.0.0/src/deepintshield/agentic/integrations/_common.py +0 -84
- deepintshield-2.0.0/src/deepintshield/agentic/integrations/langgraph.py +0 -62
- deepintshield-2.0.0/src/deepintshield/agentic/surface.py +0 -118
- deepintshield-2.0.0/src/deepintshield/version.py +0 -1
- {deepintshield-2.0.0 → deepintshield-2.2.0}/LICENSE +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/setup.cfg +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/__init__.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/_gemini_cache.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/_prompt_cache.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agent.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/__init__.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/__init__.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/base.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/entra.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/oidc.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/credentials/zeroid.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/decorators.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/errors.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/integrations/__init__.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/agentic/obligations.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/config.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/errors.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/__init__.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/autogen.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/crewai.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/langgraph.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/llamaindex.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/openai_agents.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/frameworks/pydanticai.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/__init__.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/adapters/__init__.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/adapters/anthropic.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/adapters/langchain.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/adapters/openai.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/client.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/mcp/tool.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/__init__.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/anthropic.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/bedrock.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/genai.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/langchain.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/langgraph.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/litellm.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/openai.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/providers/pydanticai.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/rag.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield/transport.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield.egg-info/dependency_links.txt +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield.egg-info/requires.txt +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/src/deepintshield.egg-info/top_level.txt +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_agent.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_agentic.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_client.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_config.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_errors.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_gemini_cache.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_prompt_cache.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_providers.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_rag.py +0 -0
- {deepintshield-2.0.0 → deepintshield-2.2.0}/tests/test_rag_guard.py +0 -0
- {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.
|
|
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
|
-
|
|
316
|
-
|
|
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
|
-
#
|
|
320
|
-
|
|
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
|
-
|
|
382
|
+
`guard(target)` auto-detects and instruments a framework object in place:
|
|
324
383
|
|
|
325
384
|
```python
|
|
326
|
-
shield.agentic.
|
|
327
|
-
shield.agentic.
|
|
328
|
-
shield.agentic.
|
|
329
|
-
shield.agentic.
|
|
330
|
-
|
|
331
|
-
|
|
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
|
|
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
|
-
|
|
247
|
-
|
|
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
|
-
#
|
|
251
|
-
|
|
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
|
-
|
|
311
|
+
`guard(target)` auto-detects and instruments a framework object in place:
|
|
255
312
|
|
|
256
313
|
```python
|
|
257
|
-
shield.agentic.
|
|
258
|
-
shield.agentic.
|
|
259
|
-
shield.agentic.
|
|
260
|
-
shield.agentic.
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|