fortifyos-langchain 0.1.0__py3-none-any.whl
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.
- fortifyos_langchain/__init__.py +4 -0
- fortifyos_langchain/_autoinstall.py +36 -0
- fortifyos_langchain/handler.py +128 -0
- fortifyos_langchain-0.1.0.data/data/fortifyos_langchain.pth +1 -0
- fortifyos_langchain-0.1.0.dist-info/METADATA +164 -0
- fortifyos_langchain-0.1.0.dist-info/RECORD +9 -0
- fortifyos_langchain-0.1.0.dist-info/WHEEL +5 -0
- fortifyos_langchain-0.1.0.dist-info/licenses/LICENSE +21 -0
- fortifyos_langchain-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-attach the FortifyOS handler to every LangChain run in this process.
|
|
3
|
+
|
|
4
|
+
Loaded automatically by Python on interpreter startup via the
|
|
5
|
+
fortifyos_langchain.pth file that ships with this package.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from contextvars import ContextVar
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from langchain_core.tracers.context import register_configure_hook
|
|
15
|
+
from .handler import FortifyHandler
|
|
16
|
+
|
|
17
|
+
_fortifyos_var: ContextVar[Optional[FortifyHandler]] = ContextVar(
|
|
18
|
+
"fortifyos", default=None
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
register_configure_hook(
|
|
22
|
+
context_var=_fortifyos_var,
|
|
23
|
+
inheritable=True,
|
|
24
|
+
handle_class=FortifyHandler,
|
|
25
|
+
env_var="FORTIFYOS_ENABLE",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if not os.environ.get("FORTIFYOS_DISABLE"):
|
|
29
|
+
_fortifyos_var.set(FortifyHandler())
|
|
30
|
+
|
|
31
|
+
if os.environ.get("FORTIFYOS_VERBOSE"):
|
|
32
|
+
print(" [FortifyOS] handler auto-attached to LangChain", file=sys.stderr)
|
|
33
|
+
|
|
34
|
+
except Exception as e:
|
|
35
|
+
if os.environ.get("FORTIFYOS_VERBOSE"):
|
|
36
|
+
print(f" [FortifyOS] auto-attach failed: {e}", file=sys.stderr)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FortifyOS callback handler — fires shell scripts on LangChain lifecycle events.
|
|
3
|
+
|
|
4
|
+
Reads a mapping from ~/.fortifyos/langchain.json that looks like:
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
"pre_tool": "pre_tool.sh",
|
|
8
|
+
"post_tool": "post_tool.sh",
|
|
9
|
+
"pre_llm": "pre_llm.sh",
|
|
10
|
+
"post_llm": "post_llm.sh"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
When LangChain fires an event, we look up the script name and run it from
|
|
14
|
+
~/.fortifyos/hooks/langchain/. Script gets the event payload as JSON on
|
|
15
|
+
stdin. Script exit code 0 = allow, non-zero = block (we raise).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
|
|
23
|
+
from langchain_core.callbacks import BaseCallbackHandler
|
|
24
|
+
|
|
25
|
+
HOOKS_DIR = os.path.expanduser("~/.fortifyos/hooks/langchain")
|
|
26
|
+
CONFIG = os.path.expanduser("~/.fortifyos/langchain.json")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _load_config():
|
|
30
|
+
try:
|
|
31
|
+
with open(CONFIG, "r") as f:
|
|
32
|
+
return json.load(f)
|
|
33
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
34
|
+
return {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _find_bash() -> str:
|
|
38
|
+
"""
|
|
39
|
+
On Windows, `shutil.which('bash')` often returns WSL bash, which cannot
|
|
40
|
+
read Windows paths and will fail. We must prefer Git Bash explicitly.
|
|
41
|
+
"""
|
|
42
|
+
if os.name == "nt":
|
|
43
|
+
candidates = [
|
|
44
|
+
r"C:\Program Files\Git\bin\bash.exe",
|
|
45
|
+
r"C:\Program Files\Git\usr\bin\bash.exe",
|
|
46
|
+
r"C:\Program Files (x86)\Git\bin\bash.exe",
|
|
47
|
+
]
|
|
48
|
+
for c in candidates:
|
|
49
|
+
if os.path.exists(c):
|
|
50
|
+
return c
|
|
51
|
+
path_bash = shutil.which("bash")
|
|
52
|
+
if path_bash and "System32" not in path_bash:
|
|
53
|
+
return path_bash
|
|
54
|
+
return shutil.which("bash") or "bash"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _resolve_runner(script_path: str):
|
|
58
|
+
if script_path.endswith(".sh"):
|
|
59
|
+
return [_find_bash(), script_path]
|
|
60
|
+
return [script_path]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FortifyHandler(BaseCallbackHandler):
|
|
64
|
+
# Let our exceptions propagate so blocking actually blocks
|
|
65
|
+
raise_error = True
|
|
66
|
+
|
|
67
|
+
def _run(self, event: str, payload: dict):
|
|
68
|
+
cfg = _load_config()
|
|
69
|
+
script_name = cfg.get(event)
|
|
70
|
+
if not script_name:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
script_path = os.path.join(HOOKS_DIR, script_name)
|
|
74
|
+
if not os.path.exists(script_path):
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
result = subprocess.run(
|
|
79
|
+
_resolve_runner(script_path),
|
|
80
|
+
input=json.dumps(payload, default=str),
|
|
81
|
+
text=True,
|
|
82
|
+
capture_output=True,
|
|
83
|
+
timeout=30,
|
|
84
|
+
encoding="utf-8",
|
|
85
|
+
errors="replace",
|
|
86
|
+
)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f" [FortifyOS] hook runner failed: {e}")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
if result.stdout:
|
|
92
|
+
print(result.stdout.rstrip())
|
|
93
|
+
if result.stderr:
|
|
94
|
+
print(result.stderr.rstrip())
|
|
95
|
+
|
|
96
|
+
if result.returncode != 0:
|
|
97
|
+
raise RuntimeError(
|
|
98
|
+
f"[FortifyOS] {event} blocked by policy (exit {result.returncode})"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# ── LangChain lifecycle hooks ──
|
|
102
|
+
def on_tool_start(self, serialized, input_str, **kwargs):
|
|
103
|
+
self._run("pre_tool", {
|
|
104
|
+
"tool": (serialized or {}).get("name", "unknown"),
|
|
105
|
+
"input": input_str,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
def on_tool_end(self, output, **kwargs):
|
|
109
|
+
self._run("post_tool", {"output": str(output)})
|
|
110
|
+
|
|
111
|
+
def on_llm_start(self, serialized, prompts, **kwargs):
|
|
112
|
+
self._run("pre_llm", {
|
|
113
|
+
"model": (serialized or {}).get("name", "unknown"),
|
|
114
|
+
"prompts": prompts,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
def on_llm_end(self, response, **kwargs):
|
|
118
|
+
self._run("post_llm", {"response_type": type(response).__name__})
|
|
119
|
+
|
|
120
|
+
def on_chat_model_start(self, serialized, messages, **kwargs):
|
|
121
|
+
flat = []
|
|
122
|
+
for batch in (messages or []):
|
|
123
|
+
for m in batch:
|
|
124
|
+
flat.append(getattr(m, "content", str(m)))
|
|
125
|
+
self._run("pre_llm", {
|
|
126
|
+
"model": (serialized or {}).get("name", "unknown"),
|
|
127
|
+
"messages": flat,
|
|
128
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import sys, sysconfig; sys.path.insert(0, sysconfig.get_paths()['purelib']); import fortifyos_langchain._autoinstall
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fortifyos-langchain
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FortifyOS runtime protection for LangChain and LangGraph agents — zero code changes required.
|
|
5
|
+
Author-email: FortifyAI <support@fortifyai.co>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://fortifyai.co
|
|
8
|
+
Project-URL: Documentation, https://fortifyai.co/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/fortifyai/fortifyos-langchain
|
|
10
|
+
Project-URL: Issues, https://github.com/fortifyai/fortifyos-langchain/issues
|
|
11
|
+
Keywords: fortifyos,langchain,langgraph,security,ai-agent,hooks,policy,guardrails
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: langchain-core>=0.3
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# fortifyos-langchain
|
|
32
|
+
|
|
33
|
+
**Runtime security for LangChain and LangGraph agents — with zero code changes.**
|
|
34
|
+
|
|
35
|
+
`fortifyos-langchain` auto-attaches the [FortifyOS](https://fortifyai.co) policy engine to every LangChain agent running in your Python environment. It intercepts LLM calls and tool invocations, runs your security policies as plain shell scripts, and allows or blocks the action based on the result.
|
|
36
|
+
|
|
37
|
+
No imports. No callback wiring. No modifications to your agent code.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## How It Works
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
python my_agent.py
|
|
45
|
+
│
|
|
46
|
+
▼
|
|
47
|
+
Python auto-loads the .pth file shipped by this package
|
|
48
|
+
│
|
|
49
|
+
▼
|
|
50
|
+
FortifyHandler attaches globally to LangChain
|
|
51
|
+
│
|
|
52
|
+
▼
|
|
53
|
+
On every LLM call / tool call → run matching policy script
|
|
54
|
+
│
|
|
55
|
+
▼
|
|
56
|
+
exit 0 = ALLOW | exit 1 = BLOCK
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The trick is the same one used by Datadog, Sentry, and OpenTelemetry — a Python `.pth` file inside the installed package is auto-loaded by the interpreter on startup, before any user code runs.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install fortifyos-langchain
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Install it into the same Python environment (system Python or venv) that you use to run your agents. That's the only requirement.
|
|
70
|
+
|
|
71
|
+
After install, every LangChain agent run by that Python is automatically protected.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Policy Setup
|
|
76
|
+
|
|
77
|
+
Policies live as plain shell scripts under `~/.fortifyos/hooks/langchain/` and are mapped to lifecycle events via `~/.fortifyos/langchain.json`.
|
|
78
|
+
|
|
79
|
+
Example `~/.fortifyos/langchain.json`:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"pre_tool": "pre_tool.sh",
|
|
84
|
+
"post_tool": "post_tool.sh",
|
|
85
|
+
"pre_llm": "pre_llm.sh",
|
|
86
|
+
"post_llm": "post_llm.sh"
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Example `~/.fortifyos/hooks/langchain/pre_tool.sh`:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
#!/bin/bash
|
|
94
|
+
# Block any tool call whose input mentions a forbidden keyword
|
|
95
|
+
payload="$(cat)"
|
|
96
|
+
if echo "$payload" | grep -q "rm -rf"; then
|
|
97
|
+
echo "Blocked: dangerous shell input detected"
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
exit 0
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The script receives the event payload as JSON on stdin. Exit code 0 allows the action; any non-zero exit code blocks it.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Supported Events
|
|
108
|
+
|
|
109
|
+
| Event name | Fires when |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `pre_llm` | Before an LLM / chat-model call |
|
|
112
|
+
| `post_llm` | After an LLM responds |
|
|
113
|
+
| `pre_tool` | Before a tool runs |
|
|
114
|
+
| `post_tool` | After a tool returns |
|
|
115
|
+
|
|
116
|
+
Additional lifecycle events (agent action, retriever calls, error hooks) are planned.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Disabling Protection
|
|
121
|
+
|
|
122
|
+
Set `FORTIFYOS_DISABLE=1` in the environment to skip the auto-attach for a single run — useful for debugging.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
FORTIFYOS_DISABLE=1 python my_agent.py
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Verifying The Install
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
FORTIFYOS_VERBOSE=1 python -c "print('ok')"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Expected output:
|
|
137
|
+
```
|
|
138
|
+
[FortifyOS] handler auto-attached to LangChain
|
|
139
|
+
ok
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
If the `[FortifyOS]` line does not appear, the `.pth` file did not land in `site-packages`. See the troubleshooting section in the docs.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Requirements
|
|
147
|
+
|
|
148
|
+
- Python 3.9 or newer
|
|
149
|
+
- `langchain-core >= 0.3`
|
|
150
|
+
- Bash (Git Bash on Windows) — required only to execute the shell-based policy scripts
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT — see [LICENSE](./LICENSE).
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Links
|
|
161
|
+
|
|
162
|
+
- Homepage: <https://fortifyai.co>
|
|
163
|
+
- Docs: <https://fortifyai.co/docs>
|
|
164
|
+
- Issues: <https://github.com/fortifyai/fortifyos-langchain/issues>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
fortifyos_langchain/__init__.py,sha256=uluwC7P9P2JldgS_qaLhFud5vt8RPwRQKhxtuQVnvyg,186
|
|
2
|
+
fortifyos_langchain/_autoinstall.py,sha256=ySxBIairOiRRYwJsP4mtn0rs6m4wKOnoEfwoesKLh8k,1052
|
|
3
|
+
fortifyos_langchain/handler.py,sha256=xh7zwC-nsuvjuNfXhwiUu8EtD2KqpP6XPzRx4PWVZwk,3910
|
|
4
|
+
fortifyos_langchain-0.1.0.data/data/fortifyos_langchain.pth,sha256=OBE17a-Vy0UnN2Z_xND4uCBqtdYSSvix4A86xMF9AK8,117
|
|
5
|
+
fortifyos_langchain-0.1.0.dist-info/licenses/LICENSE,sha256=VSWKDxUEjVmxfBcPpOJSO8yEZbGJIZzANkWMrNMP_lg,1066
|
|
6
|
+
fortifyos_langchain-0.1.0.dist-info/METADATA,sha256=adzcKJZHn0pWKNo45NHvyc7lphhtq0hYnYuVEXPqDIY,4729
|
|
7
|
+
fortifyos_langchain-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
8
|
+
fortifyos_langchain-0.1.0.dist-info/top_level.txt,sha256=8ohJs-_kGkziitMRqI2mdCoe9Gkbhct58K9gu8s8wY8,20
|
|
9
|
+
fortifyos_langchain-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FortifyAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fortifyos_langchain
|