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.
@@ -0,0 +1,4 @@
1
+ # fortifyos-langchain — runtime security for LangChain / LangGraph agents.
2
+ # The auto-attach logic lives in _autoinstall.py; the callback handler in handler.py.
3
+
4
+ __version__ = "0.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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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