xshieldai-langchain 1.0.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.
- xshieldai_langchain-1.0.0/PKG-INFO +113 -0
- xshieldai_langchain-1.0.0/README.md +85 -0
- xshieldai_langchain-1.0.0/pyproject.toml +39 -0
- xshieldai_langchain-1.0.0/setup.cfg +4 -0
- xshieldai_langchain-1.0.0/xshieldai_langchain/__init__.py +10 -0
- xshieldai_langchain-1.0.0/xshieldai_langchain/gate.py +232 -0
- xshieldai_langchain-1.0.0/xshieldai_langchain.egg-info/PKG-INFO +113 -0
- xshieldai_langchain-1.0.0/xshieldai_langchain.egg-info/SOURCES.txt +9 -0
- xshieldai_langchain-1.0.0/xshieldai_langchain.egg-info/dependency_links.txt +1 -0
- xshieldai_langchain-1.0.0/xshieldai_langchain.egg-info/requires.txt +7 -0
- xshieldai_langchain-1.0.0/xshieldai_langchain.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xshieldai-langchain
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: xShieldAI / AEGIS DAN gate callback for LangChain agents — pre-execution governance via @xshieldai/aegis
|
|
5
|
+
Author-email: "Capt. Anil Sharma" <capt.anil.sharma@powerpbox.org>
|
|
6
|
+
License: AGPL-3.0
|
|
7
|
+
Project-URL: Homepage, https://xshieldai.com
|
|
8
|
+
Project-URL: Repository, https://github.com/rocketlang/aegis/tree/main/packages/xshieldai-langchain
|
|
9
|
+
Project-URL: Documentation, https://xshieldai.com/docs/langchain
|
|
10
|
+
Keywords: langchain,xshieldai,aegis,ai-governance,agent-safety
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Provides-Extra: langchain
|
|
24
|
+
Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
27
|
+
Requires-Dist: langchain-core>=0.1.0; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# xshieldai-langchain
|
|
30
|
+
|
|
31
|
+
KavachOS DAN gate callback for LangChain agents.
|
|
32
|
+
|
|
33
|
+
Intercepts every tool call through the AEGIS KAVACH gate before execution.
|
|
34
|
+
Zero agent code changes — add the callback and every tool invocation is governed.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install xshieldai-langchain
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from xshieldai_langchain import KavachGateCallback
|
|
46
|
+
|
|
47
|
+
callback = KavachGateCallback(
|
|
48
|
+
base_url="http://localhost:4850", # AEGIS server
|
|
49
|
+
on_block="raise", # raise KavachGateError on DAN-3/4
|
|
50
|
+
dry_run=False,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# LangChain agent — pass callback in config
|
|
54
|
+
result = agent.invoke(
|
|
55
|
+
{"input": "summarise the quarterly report"},
|
|
56
|
+
config={"callbacks": [callback]},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Or attach to a single tool:
|
|
60
|
+
result = my_tool.invoke("drop table users", config={"callbacks": [callback]})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## KavachGateCallback parameters
|
|
64
|
+
|
|
65
|
+
| Parameter | Default | Description |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `base_url` | `http://localhost:4850` | AEGIS server URL |
|
|
68
|
+
| `token` | `$AEGIS_TOKEN` | Bearer auth token |
|
|
69
|
+
| `on_block` | `"raise"` | `"raise"` → KavachGateError · `"warn"` → print + continue |
|
|
70
|
+
| `dry_run` | `False` | Classify only — no notification, no human-in-loop polling |
|
|
71
|
+
| `tool_name` | `"langchain"` | Label appearing in audit records |
|
|
72
|
+
| `session_id` | auto-generated | Audit grouping key (one per agent session) |
|
|
73
|
+
|
|
74
|
+
## Direct client
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from xshieldai_langchain import AegisClient
|
|
78
|
+
|
|
79
|
+
client = AegisClient(base_url="http://localhost:4850")
|
|
80
|
+
|
|
81
|
+
# Pre-flight budget check
|
|
82
|
+
state = client.state()
|
|
83
|
+
if state["budget"]["breached"]:
|
|
84
|
+
raise RuntimeError("Daily budget breached — halt")
|
|
85
|
+
|
|
86
|
+
# Manual gate call
|
|
87
|
+
result = client.gate(command="rm -rf /var/postgres", tool_name="my-agent")
|
|
88
|
+
print(result) # {"allow": false, "level": 4, "reason": "DAN-4 catastrophic..."}
|
|
89
|
+
|
|
90
|
+
# Audit query
|
|
91
|
+
records = client.audit(session_id="lc-abc123", status="stop", limit=20)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## How it works
|
|
95
|
+
|
|
96
|
+
`KavachGateCallback.on_tool_start()` fires before any tool execution.
|
|
97
|
+
It POSTs to `POST /api/v1/kavach/gate` on the AEGIS server.
|
|
98
|
+
|
|
99
|
+
- **DAN-1/2**: allowed immediately, logged.
|
|
100
|
+
- **DAN-3**: notify approver via Telegram/WhatsApp, wait for ALLOW/STOP.
|
|
101
|
+
- **DAN-4**: blocked immediately, `KavachGateError` raised.
|
|
102
|
+
|
|
103
|
+
All policy is in AEGIS — the callback is a thin HTTP relay.
|
|
104
|
+
|
|
105
|
+
## AEGIS server
|
|
106
|
+
|
|
107
|
+
Run with: `bun /root/aegis/src/dashboard/server.ts`
|
|
108
|
+
Default port: `4850`
|
|
109
|
+
Gate endpoint: `POST /api/v1/kavach/gate`
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
AGPL-3.0 — see [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# xshieldai-langchain
|
|
2
|
+
|
|
3
|
+
KavachOS DAN gate callback for LangChain agents.
|
|
4
|
+
|
|
5
|
+
Intercepts every tool call through the AEGIS KAVACH gate before execution.
|
|
6
|
+
Zero agent code changes — add the callback and every tool invocation is governed.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install xshieldai-langchain
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from xshieldai_langchain import KavachGateCallback
|
|
18
|
+
|
|
19
|
+
callback = KavachGateCallback(
|
|
20
|
+
base_url="http://localhost:4850", # AEGIS server
|
|
21
|
+
on_block="raise", # raise KavachGateError on DAN-3/4
|
|
22
|
+
dry_run=False,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# LangChain agent — pass callback in config
|
|
26
|
+
result = agent.invoke(
|
|
27
|
+
{"input": "summarise the quarterly report"},
|
|
28
|
+
config={"callbacks": [callback]},
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Or attach to a single tool:
|
|
32
|
+
result = my_tool.invoke("drop table users", config={"callbacks": [callback]})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## KavachGateCallback parameters
|
|
36
|
+
|
|
37
|
+
| Parameter | Default | Description |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `base_url` | `http://localhost:4850` | AEGIS server URL |
|
|
40
|
+
| `token` | `$AEGIS_TOKEN` | Bearer auth token |
|
|
41
|
+
| `on_block` | `"raise"` | `"raise"` → KavachGateError · `"warn"` → print + continue |
|
|
42
|
+
| `dry_run` | `False` | Classify only — no notification, no human-in-loop polling |
|
|
43
|
+
| `tool_name` | `"langchain"` | Label appearing in audit records |
|
|
44
|
+
| `session_id` | auto-generated | Audit grouping key (one per agent session) |
|
|
45
|
+
|
|
46
|
+
## Direct client
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from xshieldai_langchain import AegisClient
|
|
50
|
+
|
|
51
|
+
client = AegisClient(base_url="http://localhost:4850")
|
|
52
|
+
|
|
53
|
+
# Pre-flight budget check
|
|
54
|
+
state = client.state()
|
|
55
|
+
if state["budget"]["breached"]:
|
|
56
|
+
raise RuntimeError("Daily budget breached — halt")
|
|
57
|
+
|
|
58
|
+
# Manual gate call
|
|
59
|
+
result = client.gate(command="rm -rf /var/postgres", tool_name="my-agent")
|
|
60
|
+
print(result) # {"allow": false, "level": 4, "reason": "DAN-4 catastrophic..."}
|
|
61
|
+
|
|
62
|
+
# Audit query
|
|
63
|
+
records = client.audit(session_id="lc-abc123", status="stop", limit=20)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## How it works
|
|
67
|
+
|
|
68
|
+
`KavachGateCallback.on_tool_start()` fires before any tool execution.
|
|
69
|
+
It POSTs to `POST /api/v1/kavach/gate` on the AEGIS server.
|
|
70
|
+
|
|
71
|
+
- **DAN-1/2**: allowed immediately, logged.
|
|
72
|
+
- **DAN-3**: notify approver via Telegram/WhatsApp, wait for ALLOW/STOP.
|
|
73
|
+
- **DAN-4**: blocked immediately, `KavachGateError` raised.
|
|
74
|
+
|
|
75
|
+
All policy is in AEGIS — the callback is a thin HTTP relay.
|
|
76
|
+
|
|
77
|
+
## AEGIS server
|
|
78
|
+
|
|
79
|
+
Run with: `bun /root/aegis/src/dashboard/server.ts`
|
|
80
|
+
Default port: `4850`
|
|
81
|
+
Gate endpoint: `POST /api/v1/kavach/gate`
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
AGPL-3.0 — see [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "xshieldai-langchain"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "xShieldAI / AEGIS DAN gate callback for LangChain agents — pre-execution governance via @xshieldai/aegis"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "AGPL-3.0" }
|
|
11
|
+
authors = [{ name = "Capt. Anil Sharma", email = "capt.anil.sharma@powerpbox.org" }]
|
|
12
|
+
keywords = ["langchain", "xshieldai", "aegis", "ai-governance", "agent-safety"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: GNU Affero General Public License v3",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
"Topic :: Security",
|
|
24
|
+
]
|
|
25
|
+
requires-python = ">=3.9"
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
langchain = ["langchain-core>=0.1.0"]
|
|
30
|
+
dev = ["pytest>=7", "langchain-core>=0.1.0"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://xshieldai.com"
|
|
34
|
+
Repository = "https://github.com/rocketlang/aegis/tree/main/packages/xshieldai-langchain"
|
|
35
|
+
Documentation = "https://xshieldai.com/docs/langchain"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["."]
|
|
39
|
+
include = ["xshieldai_langchain*"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# xshieldai-langchain — KavachOS governance callbacks for LangChain agents
|
|
3
|
+
# @rule:AEG-012 framework-agnostic: thin HTTP client, all policy in aegis
|
|
4
|
+
# @rule:KAV-001 every dangerous action intercepted before execution
|
|
5
|
+
# @rule:INF-KAV-025 LangChain callback intercepts tool calls at on_tool_start
|
|
6
|
+
|
|
7
|
+
from .gate import KavachGateCallback, KavachGateError, AegisClient
|
|
8
|
+
|
|
9
|
+
__all__ = ["KavachGateCallback", "KavachGateError", "AegisClient"]
|
|
10
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# @rule:KAV-001 every dangerous action intercepted before execution
|
|
3
|
+
# @rule:AEG-011 framework-agnostic: thin HTTP client, all policy in aegis
|
|
4
|
+
# @rule:AEG-012 LangChain adapter is the deployment surface for aegis governance
|
|
5
|
+
# @rule:INF-KAV-025 on_tool_start fires before any tool execution — correct interception point
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Any, Dict, Optional, Union
|
|
13
|
+
from urllib.request import Request, urlopen
|
|
14
|
+
from urllib.error import URLError, HTTPError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class KavachGateError(Exception):
|
|
18
|
+
"""Raised when the KAVACH gate blocks an action."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, command: str, level: int, reason: str, approval_id: Optional[str] = None):
|
|
21
|
+
self.command = command
|
|
22
|
+
self.level = level
|
|
23
|
+
self.reason = reason
|
|
24
|
+
self.approval_id = approval_id
|
|
25
|
+
super().__init__(
|
|
26
|
+
f"KAVACH blocked (DAN-{level}): {reason}"
|
|
27
|
+
+ (f" [approval_id={approval_id}]" if approval_id else "")
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AegisClient:
|
|
32
|
+
"""Thin HTTP client for the AEGIS KAVACH gate API.
|
|
33
|
+
|
|
34
|
+
All policy is enforced in the AEGIS server — this client just relays calls.
|
|
35
|
+
stdlib only: no httpx/requests dependency.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
base_url: str = "http://localhost:4850",
|
|
41
|
+
token: Optional[str] = None,
|
|
42
|
+
timeout: int = 30,
|
|
43
|
+
):
|
|
44
|
+
self.base_url = base_url.rstrip("/")
|
|
45
|
+
self.token = token or os.environ.get("AEGIS_TOKEN")
|
|
46
|
+
self.timeout = timeout
|
|
47
|
+
|
|
48
|
+
def _headers(self) -> Dict[str, str]:
|
|
49
|
+
h = {"Content-Type": "application/json"}
|
|
50
|
+
if self.token:
|
|
51
|
+
h["Authorization"] = f"Bearer {self.token}"
|
|
52
|
+
return h
|
|
53
|
+
|
|
54
|
+
def _post(self, path: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
55
|
+
url = f"{self.base_url}{path}"
|
|
56
|
+
data = json.dumps(payload).encode()
|
|
57
|
+
req = Request(url, data=data, headers=self._headers(), method="POST")
|
|
58
|
+
try:
|
|
59
|
+
with urlopen(req, timeout=self.timeout) as resp:
|
|
60
|
+
return json.loads(resp.read())
|
|
61
|
+
except HTTPError as e:
|
|
62
|
+
body = e.read().decode(errors="replace")
|
|
63
|
+
raise RuntimeError(f"Aegis HTTP {e.code} at {url}: {body}") from e
|
|
64
|
+
except URLError as e:
|
|
65
|
+
raise RuntimeError(f"Cannot reach Aegis at {url}: {e.reason}") from e
|
|
66
|
+
|
|
67
|
+
def _get(self, path: str, params: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
|
68
|
+
qs = ""
|
|
69
|
+
if params:
|
|
70
|
+
from urllib.parse import urlencode
|
|
71
|
+
qs = "?" + urlencode({k: v for k, v in params.items() if v is not None})
|
|
72
|
+
url = f"{self.base_url}{path}{qs}"
|
|
73
|
+
req = Request(url, headers={k: v for k, v in self._headers().items() if k != "Content-Type"})
|
|
74
|
+
try:
|
|
75
|
+
with urlopen(req, timeout=self.timeout) as resp:
|
|
76
|
+
return json.loads(resp.read())
|
|
77
|
+
except HTTPError as e:
|
|
78
|
+
body = e.read().decode(errors="replace")
|
|
79
|
+
raise RuntimeError(f"Aegis HTTP {e.code} at {url}: {body}") from e
|
|
80
|
+
except URLError as e:
|
|
81
|
+
raise RuntimeError(f"Cannot reach Aegis at {url}: {e.reason}") from e
|
|
82
|
+
|
|
83
|
+
def gate(
|
|
84
|
+
self,
|
|
85
|
+
command: str,
|
|
86
|
+
tool_name: str = "langchain",
|
|
87
|
+
session_id: Optional[str] = None,
|
|
88
|
+
dry_run: bool = False,
|
|
89
|
+
) -> Dict[str, Any]:
|
|
90
|
+
"""POST /api/v1/kavach/gate — returns gate result dict."""
|
|
91
|
+
return self._post(
|
|
92
|
+
"/api/v1/kavach/gate",
|
|
93
|
+
{
|
|
94
|
+
"command": command,
|
|
95
|
+
"tool_name": tool_name,
|
|
96
|
+
"session_id": session_id,
|
|
97
|
+
"dry_run": dry_run,
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def health(self) -> Dict[str, Any]:
|
|
102
|
+
return self._get("/api/v1/kavach/health")
|
|
103
|
+
|
|
104
|
+
def state(self) -> Dict[str, Any]:
|
|
105
|
+
return self._get("/api/v1/kavach/state")
|
|
106
|
+
|
|
107
|
+
def audit(
|
|
108
|
+
self,
|
|
109
|
+
session_id: Optional[str] = None,
|
|
110
|
+
status: Optional[str] = None,
|
|
111
|
+
level: Optional[int] = None,
|
|
112
|
+
limit: int = 50,
|
|
113
|
+
) -> Dict[str, Any]:
|
|
114
|
+
params: Dict[str, str] = {"limit": str(limit)}
|
|
115
|
+
if session_id:
|
|
116
|
+
params["session_id"] = session_id
|
|
117
|
+
if status:
|
|
118
|
+
params["status"] = status
|
|
119
|
+
if level is not None:
|
|
120
|
+
params["level"] = str(level)
|
|
121
|
+
return self._get("/api/v1/kavach/audit", params)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class KavachGateCallback:
|
|
125
|
+
"""LangChain callback handler that intercepts tool calls through the KAVACH DAN gate.
|
|
126
|
+
|
|
127
|
+
Drop-in: pass an instance in ``callbacks=[KavachGateCallback()]`` on any LangChain
|
|
128
|
+
agent or tool invocation. No agent code changes needed beyond adding the callback.
|
|
129
|
+
|
|
130
|
+
Usage::
|
|
131
|
+
|
|
132
|
+
from xshieldai_langchain import KavachGateCallback
|
|
133
|
+
|
|
134
|
+
callback = KavachGateCallback(
|
|
135
|
+
base_url="http://localhost:4850",
|
|
136
|
+
on_block="raise", # or "warn"
|
|
137
|
+
dry_run=False,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
result = agent.invoke({"input": "drop all backups"}, config={"callbacks": [callback]})
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
base_url: str = "http://localhost:4850",
|
|
146
|
+
token: Optional[str] = None,
|
|
147
|
+
on_block: str = "raise",
|
|
148
|
+
dry_run: bool = False,
|
|
149
|
+
tool_name: str = "langchain",
|
|
150
|
+
session_id: Optional[str] = None,
|
|
151
|
+
):
|
|
152
|
+
"""
|
|
153
|
+
Args:
|
|
154
|
+
base_url: AEGIS server URL.
|
|
155
|
+
token: Bearer token (or set AEGIS_TOKEN env var).
|
|
156
|
+
on_block: ``"raise"`` (raise KavachGateError) or ``"warn"`` (print warning, continue).
|
|
157
|
+
dry_run: Classify only — no notification, no polling.
|
|
158
|
+
tool_name: Label appearing in audit records.
|
|
159
|
+
session_id: Audit grouping key. Auto-generated per callback instance if not set.
|
|
160
|
+
"""
|
|
161
|
+
if on_block not in ("raise", "warn"):
|
|
162
|
+
raise ValueError("on_block must be 'raise' or 'warn'")
|
|
163
|
+
self.client = AegisClient(base_url=base_url, token=token)
|
|
164
|
+
self.on_block = on_block
|
|
165
|
+
self.dry_run = dry_run
|
|
166
|
+
self.tool_name = tool_name
|
|
167
|
+
self.session_id = session_id or f"lc-{uuid.uuid4().hex[:12]}"
|
|
168
|
+
|
|
169
|
+
# ---- LangChain BaseCallbackHandler interface (duck-typed) ----
|
|
170
|
+
|
|
171
|
+
def on_tool_start(
|
|
172
|
+
self,
|
|
173
|
+
serialized: Dict[str, Any],
|
|
174
|
+
input_str: str,
|
|
175
|
+
*,
|
|
176
|
+
run_id: Any = None,
|
|
177
|
+
parent_run_id: Any = None,
|
|
178
|
+
tags: Optional[list] = None,
|
|
179
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
180
|
+
inputs: Optional[Dict[str, Any]] = None,
|
|
181
|
+
**kwargs: Any,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Intercept tool invocation before execution — the correct KAVACH gate point."""
|
|
184
|
+
tool_name = serialized.get("name", self.tool_name) if serialized else self.tool_name
|
|
185
|
+
command = input_str if isinstance(input_str, str) else json.dumps(input_str)
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
result = self.client.gate(
|
|
189
|
+
command=command,
|
|
190
|
+
tool_name=tool_name,
|
|
191
|
+
session_id=self.session_id,
|
|
192
|
+
dry_run=self.dry_run,
|
|
193
|
+
)
|
|
194
|
+
except RuntimeError as e:
|
|
195
|
+
# Aegis unreachable — fail open (log only) to avoid blocking all tooling
|
|
196
|
+
import warnings
|
|
197
|
+
warnings.warn(f"[KavachGateCallback] Aegis unreachable — proceeding unguarded: {e}", stacklevel=2)
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
if not result.get("allow", True):
|
|
201
|
+
level = result.get("level", 0)
|
|
202
|
+
reason = result.get("reason", "blocked")
|
|
203
|
+
approval_id = result.get("approval_id")
|
|
204
|
+
|
|
205
|
+
if self.on_block == "raise":
|
|
206
|
+
raise KavachGateError(command=command, level=level, reason=reason, approval_id=approval_id)
|
|
207
|
+
else:
|
|
208
|
+
import warnings
|
|
209
|
+
warnings.warn(
|
|
210
|
+
f"[KavachGateCallback] KAVACH blocked DAN-{level}: {reason} — continuing (on_block=warn)",
|
|
211
|
+
stacklevel=2,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def on_tool_end(
|
|
215
|
+
self,
|
|
216
|
+
output: str,
|
|
217
|
+
*,
|
|
218
|
+
run_id: Any = None,
|
|
219
|
+
parent_run_id: Any = None,
|
|
220
|
+
**kwargs: Any,
|
|
221
|
+
) -> None:
|
|
222
|
+
pass # post-execution hook — reserved for future telemetry
|
|
223
|
+
|
|
224
|
+
def on_tool_error(
|
|
225
|
+
self,
|
|
226
|
+
error: Union[Exception, KeyboardInterrupt],
|
|
227
|
+
*,
|
|
228
|
+
run_id: Any = None,
|
|
229
|
+
parent_run_id: Any = None,
|
|
230
|
+
**kwargs: Any,
|
|
231
|
+
) -> None:
|
|
232
|
+
pass # error path — no gate action needed
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xshieldai-langchain
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: xShieldAI / AEGIS DAN gate callback for LangChain agents — pre-execution governance via @xshieldai/aegis
|
|
5
|
+
Author-email: "Capt. Anil Sharma" <capt.anil.sharma@powerpbox.org>
|
|
6
|
+
License: AGPL-3.0
|
|
7
|
+
Project-URL: Homepage, https://xshieldai.com
|
|
8
|
+
Project-URL: Repository, https://github.com/rocketlang/aegis/tree/main/packages/xshieldai-langchain
|
|
9
|
+
Project-URL: Documentation, https://xshieldai.com/docs/langchain
|
|
10
|
+
Keywords: langchain,xshieldai,aegis,ai-governance,agent-safety
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Provides-Extra: langchain
|
|
24
|
+
Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
27
|
+
Requires-Dist: langchain-core>=0.1.0; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# xshieldai-langchain
|
|
30
|
+
|
|
31
|
+
KavachOS DAN gate callback for LangChain agents.
|
|
32
|
+
|
|
33
|
+
Intercepts every tool call through the AEGIS KAVACH gate before execution.
|
|
34
|
+
Zero agent code changes — add the callback and every tool invocation is governed.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install xshieldai-langchain
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from xshieldai_langchain import KavachGateCallback
|
|
46
|
+
|
|
47
|
+
callback = KavachGateCallback(
|
|
48
|
+
base_url="http://localhost:4850", # AEGIS server
|
|
49
|
+
on_block="raise", # raise KavachGateError on DAN-3/4
|
|
50
|
+
dry_run=False,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# LangChain agent — pass callback in config
|
|
54
|
+
result = agent.invoke(
|
|
55
|
+
{"input": "summarise the quarterly report"},
|
|
56
|
+
config={"callbacks": [callback]},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Or attach to a single tool:
|
|
60
|
+
result = my_tool.invoke("drop table users", config={"callbacks": [callback]})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## KavachGateCallback parameters
|
|
64
|
+
|
|
65
|
+
| Parameter | Default | Description |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `base_url` | `http://localhost:4850` | AEGIS server URL |
|
|
68
|
+
| `token` | `$AEGIS_TOKEN` | Bearer auth token |
|
|
69
|
+
| `on_block` | `"raise"` | `"raise"` → KavachGateError · `"warn"` → print + continue |
|
|
70
|
+
| `dry_run` | `False` | Classify only — no notification, no human-in-loop polling |
|
|
71
|
+
| `tool_name` | `"langchain"` | Label appearing in audit records |
|
|
72
|
+
| `session_id` | auto-generated | Audit grouping key (one per agent session) |
|
|
73
|
+
|
|
74
|
+
## Direct client
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from xshieldai_langchain import AegisClient
|
|
78
|
+
|
|
79
|
+
client = AegisClient(base_url="http://localhost:4850")
|
|
80
|
+
|
|
81
|
+
# Pre-flight budget check
|
|
82
|
+
state = client.state()
|
|
83
|
+
if state["budget"]["breached"]:
|
|
84
|
+
raise RuntimeError("Daily budget breached — halt")
|
|
85
|
+
|
|
86
|
+
# Manual gate call
|
|
87
|
+
result = client.gate(command="rm -rf /var/postgres", tool_name="my-agent")
|
|
88
|
+
print(result) # {"allow": false, "level": 4, "reason": "DAN-4 catastrophic..."}
|
|
89
|
+
|
|
90
|
+
# Audit query
|
|
91
|
+
records = client.audit(session_id="lc-abc123", status="stop", limit=20)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## How it works
|
|
95
|
+
|
|
96
|
+
`KavachGateCallback.on_tool_start()` fires before any tool execution.
|
|
97
|
+
It POSTs to `POST /api/v1/kavach/gate` on the AEGIS server.
|
|
98
|
+
|
|
99
|
+
- **DAN-1/2**: allowed immediately, logged.
|
|
100
|
+
- **DAN-3**: notify approver via Telegram/WhatsApp, wait for ALLOW/STOP.
|
|
101
|
+
- **DAN-4**: blocked immediately, `KavachGateError` raised.
|
|
102
|
+
|
|
103
|
+
All policy is in AEGIS — the callback is a thin HTTP relay.
|
|
104
|
+
|
|
105
|
+
## AEGIS server
|
|
106
|
+
|
|
107
|
+
Run with: `bun /root/aegis/src/dashboard/server.ts`
|
|
108
|
+
Default port: `4850`
|
|
109
|
+
Gate endpoint: `POST /api/v1/kavach/gate`
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
AGPL-3.0 — see [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
xshieldai_langchain/__init__.py
|
|
4
|
+
xshieldai_langchain/gate.py
|
|
5
|
+
xshieldai_langchain.egg-info/PKG-INFO
|
|
6
|
+
xshieldai_langchain.egg-info/SOURCES.txt
|
|
7
|
+
xshieldai_langchain.egg-info/dependency_links.txt
|
|
8
|
+
xshieldai_langchain.egg-info/requires.txt
|
|
9
|
+
xshieldai_langchain.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xshieldai_langchain
|