atbash-hermes-plugin 0.1.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.
- atbash_hermes_plugin-0.1.0/LICENSE +31 -0
- atbash_hermes_plugin-0.1.0/PKG-INFO +65 -0
- atbash_hermes_plugin-0.1.0/README.md +50 -0
- atbash_hermes_plugin-0.1.0/atbash_hermes_plugin/__init__.py +274 -0
- atbash_hermes_plugin-0.1.0/atbash_hermes_plugin.egg-info/PKG-INFO +65 -0
- atbash_hermes_plugin-0.1.0/atbash_hermes_plugin.egg-info/SOURCES.txt +10 -0
- atbash_hermes_plugin-0.1.0/atbash_hermes_plugin.egg-info/dependency_links.txt +1 -0
- atbash_hermes_plugin-0.1.0/atbash_hermes_plugin.egg-info/entry_points.txt +2 -0
- atbash_hermes_plugin-0.1.0/atbash_hermes_plugin.egg-info/requires.txt +1 -0
- atbash_hermes_plugin-0.1.0/atbash_hermes_plugin.egg-info/top_level.txt +1 -0
- atbash_hermes_plugin-0.1.0/pyproject.toml +38 -0
- atbash_hermes_plugin-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Atbash CLI -- Proprietary Software License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Atbash. All rights reserved.
|
|
4
|
+
|
|
5
|
+
The full license is available at https://atbash.ai/license
|
|
6
|
+
|
|
7
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
8
|
+
are permitted provided that the following conditions are met:
|
|
9
|
+
|
|
10
|
+
Redistributions of source code must retain the above copyright notice, this list
|
|
11
|
+
of conditions and the following disclaimer.
|
|
12
|
+
|
|
13
|
+
Redistributions in binary form must reproduce the above copyright notice, this
|
|
14
|
+
list of conditions and the following disclaimer in the documentation and/or other
|
|
15
|
+
materials provided with the distribution.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
18
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
19
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
20
|
+
NONINFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
21
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|
22
|
+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
23
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
24
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
25
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
26
|
+
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
|
27
|
+
OF SUCH DAMAGE.
|
|
28
|
+
|
|
29
|
+
The views and conclusions contained in the software and documentation are those
|
|
30
|
+
of the authors and should not be interpreted as representing official policies,
|
|
31
|
+
either expressed or implied, of Atbash.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atbash-hermes-plugin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Atbash safety plugin for Hermes Agent
|
|
5
|
+
Author: atbash
|
|
6
|
+
License-Expression: LicenseRef-Atbash-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/Atbash-Ai/atbash-hermes-plugin
|
|
8
|
+
Project-URL: Repository, https://github.com/Atbash-Ai/atbash-hermes-plugin
|
|
9
|
+
Keywords: atbash,hermes,hermes-agent,agent-safety,ai-safety,tool-guard,judge,policy
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: atbash-sdk==0.1.3
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# atbash-hermes-plugin
|
|
17
|
+
|
|
18
|
+
Atbash guardrail plugin for Hermes Agent using the official Python SDK (`atbash-sdk`).
|
|
19
|
+
|
|
20
|
+
## What it does
|
|
21
|
+
|
|
22
|
+
- Intercepts every Hermes tool call in `pre_tool_call`
|
|
23
|
+
- Sends a verdict request to Atbash before the tool executes
|
|
24
|
+
- Blocks tool execution when verdict is denied
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
1. Clone/copy this folder into your Hermes plugins directory.
|
|
29
|
+
2. Install dependencies:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python3 -m pip install -r requirements.txt
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Configure env vars:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export ATBASH_AGENT_PRIVKEY='{"pubkey":"...","privkey":"..."}'
|
|
39
|
+
# or
|
|
40
|
+
export ATBASH_KEY_PATH="$HOME/.config/atbash/guard-client-key"
|
|
41
|
+
|
|
42
|
+
# optional
|
|
43
|
+
export ATBASH_ENDPOINT="https://api.atbash.io"
|
|
44
|
+
export ATBASH_ENFORCE_DECISION="true"
|
|
45
|
+
export ATBASH_DEBUG="false"
|
|
46
|
+
export ATBASH_TOOL_MAP_PATH="$HOME/.config/atbash/hermes-tool-map.json"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
4. Enable plugin in Hermes config.
|
|
50
|
+
|
|
51
|
+
## Tool classification
|
|
52
|
+
|
|
53
|
+
The plugin records previously unseen Hermes tool names in
|
|
54
|
+
`~/.config/atbash/hermes-tool-map.json`. The learned mappings are loaded when
|
|
55
|
+
each Hermes session starts, so tool classifications persist across restarts.
|
|
56
|
+
Set `ATBASH_TOOL_MAP_PATH` to override the location.
|
|
57
|
+
|
|
58
|
+
## Behavior
|
|
59
|
+
|
|
60
|
+
- `ALLOW` => tool proceeds
|
|
61
|
+
- `HOLD` => tool is paused and must be reviewed before retrying
|
|
62
|
+
- `BLOCK`/`DENY`/`REJECT` => tool is blocked with a user-facing message
|
|
63
|
+
- Errors talking to Atbash:
|
|
64
|
+
- if `ATBASH_ENFORCE_DECISION=true` (default): block fail-closed
|
|
65
|
+
- if `false`: allow fail-open
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# atbash-hermes-plugin
|
|
2
|
+
|
|
3
|
+
Atbash guardrail plugin for Hermes Agent using the official Python SDK (`atbash-sdk`).
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- Intercepts every Hermes tool call in `pre_tool_call`
|
|
8
|
+
- Sends a verdict request to Atbash before the tool executes
|
|
9
|
+
- Blocks tool execution when verdict is denied
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
1. Clone/copy this folder into your Hermes plugins directory.
|
|
14
|
+
2. Install dependencies:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python3 -m pip install -r requirements.txt
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
3. Configure env vars:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
export ATBASH_AGENT_PRIVKEY='{"pubkey":"...","privkey":"..."}'
|
|
24
|
+
# or
|
|
25
|
+
export ATBASH_KEY_PATH="$HOME/.config/atbash/guard-client-key"
|
|
26
|
+
|
|
27
|
+
# optional
|
|
28
|
+
export ATBASH_ENDPOINT="https://api.atbash.io"
|
|
29
|
+
export ATBASH_ENFORCE_DECISION="true"
|
|
30
|
+
export ATBASH_DEBUG="false"
|
|
31
|
+
export ATBASH_TOOL_MAP_PATH="$HOME/.config/atbash/hermes-tool-map.json"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
4. Enable plugin in Hermes config.
|
|
35
|
+
|
|
36
|
+
## Tool classification
|
|
37
|
+
|
|
38
|
+
The plugin records previously unseen Hermes tool names in
|
|
39
|
+
`~/.config/atbash/hermes-tool-map.json`. The learned mappings are loaded when
|
|
40
|
+
each Hermes session starts, so tool classifications persist across restarts.
|
|
41
|
+
Set `ATBASH_TOOL_MAP_PATH` to override the location.
|
|
42
|
+
|
|
43
|
+
## Behavior
|
|
44
|
+
|
|
45
|
+
- `ALLOW` => tool proceeds
|
|
46
|
+
- `HOLD` => tool is paused and must be reviewed before retrying
|
|
47
|
+
- `BLOCK`/`DENY`/`REJECT` => tool is blocked with a user-facing message
|
|
48
|
+
- Errors talking to Atbash:
|
|
49
|
+
- if `ATBASH_ENFORCE_DECISION=true` (default): block fail-closed
|
|
50
|
+
- if `false`: allow fail-open
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("atbash-hermes-plugin")
|
|
10
|
+
|
|
11
|
+
DEFAULT_TOOL_CLASS_MAP = {
|
|
12
|
+
"browser_click": "browser_interaction",
|
|
13
|
+
"browser_navigate": "network",
|
|
14
|
+
"browser_type": "browser_interaction",
|
|
15
|
+
"read_file": "filesystem_read",
|
|
16
|
+
"skill_view": "skill_read",
|
|
17
|
+
"terminal": "command_execution",
|
|
18
|
+
"write_file": "filesystem_write",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _default_tool_map_path() -> Path:
|
|
23
|
+
return Path("~/.config/atbash/hermes-tool-map.json").expanduser()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_tool_map(path: Path) -> Dict[str, str]:
|
|
27
|
+
try:
|
|
28
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
29
|
+
except FileNotFoundError:
|
|
30
|
+
return {}
|
|
31
|
+
except Exception as e:
|
|
32
|
+
logger.warning("Could not read Atbash Hermes tool map path=%s err=%s", path, e)
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
if not isinstance(raw, dict):
|
|
36
|
+
logger.warning("Ignoring invalid Atbash Hermes tool map path=%s", path)
|
|
37
|
+
return {}
|
|
38
|
+
return {
|
|
39
|
+
str(tool_name): str(action_class)
|
|
40
|
+
for tool_name, action_class in raw.items()
|
|
41
|
+
if isinstance(tool_name, str) and isinstance(action_class, str)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _save_tool_map(path: Path, tool_map: Dict[str, str]) -> None:
|
|
46
|
+
try:
|
|
47
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
temp_path = path.with_suffix(f"{path.suffix}.tmp")
|
|
49
|
+
temp_path.write_text(
|
|
50
|
+
json.dumps(dict(sorted(tool_map.items())), indent=2) + "\n",
|
|
51
|
+
encoding="utf-8",
|
|
52
|
+
)
|
|
53
|
+
temp_path.replace(path)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.warning("Could not persist Atbash Hermes tool map path=%s err=%s", path, e)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _extract_command(tool_name: str, tool_args: Dict[str, Any]) -> str:
|
|
61
|
+
if not isinstance(tool_args, dict):
|
|
62
|
+
return ""
|
|
63
|
+
for key in ("command", "cmd", "shell_command", "bash_command", "zsh_command", "script"):
|
|
64
|
+
v = tool_args.get(key)
|
|
65
|
+
if isinstance(v, str) and v.strip():
|
|
66
|
+
return v.strip()
|
|
67
|
+
# Hermes terminal/shell tools sometimes nest payloads
|
|
68
|
+
for key in ("input", "args", "payload"):
|
|
69
|
+
v = tool_args.get(key)
|
|
70
|
+
if isinstance(v, dict):
|
|
71
|
+
for k in ("command", "cmd"):
|
|
72
|
+
vv = v.get(k)
|
|
73
|
+
if isinstance(vv, str) and vv.strip():
|
|
74
|
+
return vv.strip()
|
|
75
|
+
return ""
|
|
76
|
+
|
|
77
|
+
def _infer_action_class(tool_name: str) -> str:
|
|
78
|
+
t = (tool_name or "").lower()
|
|
79
|
+
if any(k in t for k in ("write", "edit", "delete", "create", "mv", "cp")):
|
|
80
|
+
return "filesystem_write"
|
|
81
|
+
if any(k in t for k in ("read", "list", "ls", "glob", "find", "search")):
|
|
82
|
+
return "filesystem_read"
|
|
83
|
+
if any(k in t for k in ("shell", "terminal", "exec", "command", "bash", "zsh")):
|
|
84
|
+
return "command_execution"
|
|
85
|
+
if any(k in t for k in ("http", "web", "fetch", "request", "browser", "mcp")):
|
|
86
|
+
return "network"
|
|
87
|
+
if any(k in t for k in ("image", "video", "audio")):
|
|
88
|
+
return "media_generation"
|
|
89
|
+
return "unknown"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _as_bool(value: Optional[str], default: bool) -> bool:
|
|
93
|
+
if value is None:
|
|
94
|
+
return default
|
|
95
|
+
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _normalize_verdict(raw: Any) -> str:
|
|
99
|
+
# atbash-sdk JudgeResult object path
|
|
100
|
+
verdict_attr = getattr(raw, "verdict", None)
|
|
101
|
+
if verdict_attr is None:
|
|
102
|
+
verdict_attr = getattr(raw, "decision", None)
|
|
103
|
+
if isinstance(verdict_attr, str) and verdict_attr.strip():
|
|
104
|
+
return verdict_attr.strip().upper()
|
|
105
|
+
|
|
106
|
+
if raw is None:
|
|
107
|
+
return "ERROR"
|
|
108
|
+
if isinstance(raw, str):
|
|
109
|
+
return raw.strip().upper()
|
|
110
|
+
if isinstance(raw, dict):
|
|
111
|
+
for key in ("verdict", "decision", "status", "result"):
|
|
112
|
+
v = raw.get(key)
|
|
113
|
+
if isinstance(v, str) and v.strip():
|
|
114
|
+
return v.strip().upper()
|
|
115
|
+
return str(raw).strip().upper()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _extract_reason(raw: Any) -> str:
|
|
119
|
+
reason_attr = getattr(raw, "reason", None)
|
|
120
|
+
if reason_attr is None:
|
|
121
|
+
reason_attr = getattr(raw, "message", None)
|
|
122
|
+
if isinstance(reason_attr, str) and reason_attr.strip():
|
|
123
|
+
return reason_attr
|
|
124
|
+
|
|
125
|
+
if isinstance(raw, str):
|
|
126
|
+
return raw
|
|
127
|
+
if isinstance(raw, dict):
|
|
128
|
+
for key in ("reason", "message", "explanation", "details"):
|
|
129
|
+
v = raw.get(key)
|
|
130
|
+
if isinstance(v, str) and v.strip():
|
|
131
|
+
return v
|
|
132
|
+
try:
|
|
133
|
+
return json.dumps(raw, ensure_ascii=True)
|
|
134
|
+
except Exception:
|
|
135
|
+
return str(raw)
|
|
136
|
+
return str(raw)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class AtbashHermesGuard:
|
|
140
|
+
def __init__(self) -> None:
|
|
141
|
+
self.debug = _as_bool(os.getenv("ATBASH_DEBUG"), False)
|
|
142
|
+
self.fail_closed = _as_bool(os.getenv("ATBASH_ENFORCE_DECISION"), True)
|
|
143
|
+
self.endpoint = os.getenv("ATBASH_ENDPOINT")
|
|
144
|
+
self.key_path = os.getenv("ATBASH_KEY_PATH")
|
|
145
|
+
self.privkey = os.getenv("ATBASH_AGENT_PRIVKEY")
|
|
146
|
+
self.provider = os.getenv("HERMES_PROVIDER")
|
|
147
|
+
self.model = os.getenv("HERMES_MODEL")
|
|
148
|
+
self.tool_map_path = Path(
|
|
149
|
+
os.getenv("ATBASH_TOOL_MAP_PATH", str(_default_tool_map_path()))
|
|
150
|
+
).expanduser()
|
|
151
|
+
self.tool_map = {**DEFAULT_TOOL_CLASS_MAP, **_load_tool_map(self.tool_map_path)}
|
|
152
|
+
|
|
153
|
+
self.client = self._build_client()
|
|
154
|
+
|
|
155
|
+
def _action_class(self, tool_name: str) -> str:
|
|
156
|
+
mapped = self.tool_map.get(tool_name)
|
|
157
|
+
if mapped:
|
|
158
|
+
return mapped
|
|
159
|
+
|
|
160
|
+
action_class = _infer_action_class(tool_name)
|
|
161
|
+
self.tool_map[tool_name or "unknown"] = action_class
|
|
162
|
+
_save_tool_map(self.tool_map_path, self.tool_map)
|
|
163
|
+
logger.info(
|
|
164
|
+
"Atbash learned tool mapping tool=%s action_class=%s path=%s",
|
|
165
|
+
tool_name or "unknown",
|
|
166
|
+
action_class,
|
|
167
|
+
self.tool_map_path,
|
|
168
|
+
)
|
|
169
|
+
return action_class
|
|
170
|
+
|
|
171
|
+
def _build_client(self):
|
|
172
|
+
try:
|
|
173
|
+
from atbash import Atbash, ToolCallInput # type: ignore
|
|
174
|
+
except Exception as e:
|
|
175
|
+
try:
|
|
176
|
+
from atbash import Atbash # type: ignore
|
|
177
|
+
from atbash.types import ToolCallInput # type: ignore
|
|
178
|
+
except Exception:
|
|
179
|
+
raise RuntimeError("atbash-sdk is required. Install with: pip install atbash-sdk==0.1.3") from e
|
|
180
|
+
|
|
181
|
+
self.ToolCallInput = ToolCallInput
|
|
182
|
+
judge_cfg = {"endpoint": self.endpoint} if self.endpoint else None
|
|
183
|
+
|
|
184
|
+
# Prefer explicit key material if provided.
|
|
185
|
+
if self.privkey:
|
|
186
|
+
try:
|
|
187
|
+
return Atbash(self.privkey, endpoint=self.endpoint or "https://atbash.ai")
|
|
188
|
+
except TypeError:
|
|
189
|
+
return Atbash(self.privkey)
|
|
190
|
+
|
|
191
|
+
# Use SDK-native config loader for key file/env/config.
|
|
192
|
+
return Atbash.from_config(
|
|
193
|
+
key_path=self.key_path,
|
|
194
|
+
judge=judge_cfg,
|
|
195
|
+
fail_closed=self.fail_closed,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _judge(self, tool_name: str, tool_args: Dict[str, Any], session_id: str = "", task_id: str = "", tool_call_id: str = "") -> Any:
|
|
199
|
+
action_class = self._action_class(tool_name)
|
|
200
|
+
command = _extract_command(tool_name, tool_args or {})
|
|
201
|
+
|
|
202
|
+
# Mirror OpenClaw plugin semantics: pass toolName + full args.
|
|
203
|
+
# Keep context concise but informative; args already carry command/body.
|
|
204
|
+
context = json.dumps(
|
|
205
|
+
{
|
|
206
|
+
"tool_name": tool_name,
|
|
207
|
+
"action_class": action_class,
|
|
208
|
+
"session_id": session_id or None,
|
|
209
|
+
"task_id": task_id or None,
|
|
210
|
+
"tool_call_id": tool_call_id or None,
|
|
211
|
+
"command": command or None,
|
|
212
|
+
"cwd": os.getcwd(),
|
|
213
|
+
},
|
|
214
|
+
ensure_ascii=True,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Make action payload explicit so Atbash UI shows tool name, not only raw args.
|
|
218
|
+
action_payload = {
|
|
219
|
+
"tool_name": tool_name or "unknown",
|
|
220
|
+
"args": tool_args or {},
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return self.client.audit_tool_call(
|
|
224
|
+
self.ToolCallInput(
|
|
225
|
+
tool_name=tool_name or "unknown",
|
|
226
|
+
args=action_payload,
|
|
227
|
+
context=context,
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def pre_tool_call(self, *, tool_name: str = "", args: Dict[str, Any] | None = None, task_id: str = "", session_id: str = "", tool_call_id: str = ""):
|
|
232
|
+
if self.debug:
|
|
233
|
+
logger.info("Atbash pre_tool_call enter tool=%s session_id=%s task_id=%s tool_call_id=%s", tool_name, session_id, task_id, tool_call_id)
|
|
234
|
+
try:
|
|
235
|
+
verdict_raw = self._judge(
|
|
236
|
+
tool_name=tool_name,
|
|
237
|
+
tool_args=args or {},
|
|
238
|
+
session_id=session_id,
|
|
239
|
+
task_id=task_id,
|
|
240
|
+
tool_call_id=tool_call_id,
|
|
241
|
+
)
|
|
242
|
+
verdict = _normalize_verdict(verdict_raw)
|
|
243
|
+
reason = _extract_reason(verdict_raw)
|
|
244
|
+
|
|
245
|
+
if self.debug:
|
|
246
|
+
logger.info("Atbash verdict tool=%s verdict=%s reason=%s", tool_name, verdict, reason)
|
|
247
|
+
|
|
248
|
+
if verdict == "HOLD":
|
|
249
|
+
return {
|
|
250
|
+
"action": "block",
|
|
251
|
+
"message": f"Held by Atbash for operator review: {reason}",
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if verdict in {"BLOCK", "DENY", "REJECT", "DISALLOW"}:
|
|
255
|
+
return {
|
|
256
|
+
"action": "block",
|
|
257
|
+
"message": f"Blocked by Atbash policy: {reason}",
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return None
|
|
261
|
+
except Exception as e:
|
|
262
|
+
logger.warning("Atbash guard error tool=%s err=%s", tool_name, e)
|
|
263
|
+
if self.fail_closed:
|
|
264
|
+
return {
|
|
265
|
+
"action": "block",
|
|
266
|
+
"message": f"Blocked (Atbash unavailable): {e}",
|
|
267
|
+
}
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def register(ctx):
|
|
272
|
+
guard = AtbashHermesGuard()
|
|
273
|
+
ctx.register_hook("pre_tool_call", guard.pre_tool_call)
|
|
274
|
+
logger.info("[atbash-hermes-plugin] registered pre_tool_call hook")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atbash-hermes-plugin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Atbash safety plugin for Hermes Agent
|
|
5
|
+
Author: atbash
|
|
6
|
+
License-Expression: LicenseRef-Atbash-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/Atbash-Ai/atbash-hermes-plugin
|
|
8
|
+
Project-URL: Repository, https://github.com/Atbash-Ai/atbash-hermes-plugin
|
|
9
|
+
Keywords: atbash,hermes,hermes-agent,agent-safety,ai-safety,tool-guard,judge,policy
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: atbash-sdk==0.1.3
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# atbash-hermes-plugin
|
|
17
|
+
|
|
18
|
+
Atbash guardrail plugin for Hermes Agent using the official Python SDK (`atbash-sdk`).
|
|
19
|
+
|
|
20
|
+
## What it does
|
|
21
|
+
|
|
22
|
+
- Intercepts every Hermes tool call in `pre_tool_call`
|
|
23
|
+
- Sends a verdict request to Atbash before the tool executes
|
|
24
|
+
- Blocks tool execution when verdict is denied
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
1. Clone/copy this folder into your Hermes plugins directory.
|
|
29
|
+
2. Install dependencies:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python3 -m pip install -r requirements.txt
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Configure env vars:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
export ATBASH_AGENT_PRIVKEY='{"pubkey":"...","privkey":"..."}'
|
|
39
|
+
# or
|
|
40
|
+
export ATBASH_KEY_PATH="$HOME/.config/atbash/guard-client-key"
|
|
41
|
+
|
|
42
|
+
# optional
|
|
43
|
+
export ATBASH_ENDPOINT="https://api.atbash.io"
|
|
44
|
+
export ATBASH_ENFORCE_DECISION="true"
|
|
45
|
+
export ATBASH_DEBUG="false"
|
|
46
|
+
export ATBASH_TOOL_MAP_PATH="$HOME/.config/atbash/hermes-tool-map.json"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
4. Enable plugin in Hermes config.
|
|
50
|
+
|
|
51
|
+
## Tool classification
|
|
52
|
+
|
|
53
|
+
The plugin records previously unseen Hermes tool names in
|
|
54
|
+
`~/.config/atbash/hermes-tool-map.json`. The learned mappings are loaded when
|
|
55
|
+
each Hermes session starts, so tool classifications persist across restarts.
|
|
56
|
+
Set `ATBASH_TOOL_MAP_PATH` to override the location.
|
|
57
|
+
|
|
58
|
+
## Behavior
|
|
59
|
+
|
|
60
|
+
- `ALLOW` => tool proceeds
|
|
61
|
+
- `HOLD` => tool is paused and must be reviewed before retrying
|
|
62
|
+
- `BLOCK`/`DENY`/`REJECT` => tool is blocked with a user-facing message
|
|
63
|
+
- Errors talking to Atbash:
|
|
64
|
+
- if `ATBASH_ENFORCE_DECISION=true` (default): block fail-closed
|
|
65
|
+
- if `false`: allow fail-open
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
atbash_hermes_plugin/__init__.py
|
|
5
|
+
atbash_hermes_plugin.egg-info/PKG-INFO
|
|
6
|
+
atbash_hermes_plugin.egg-info/SOURCES.txt
|
|
7
|
+
atbash_hermes_plugin.egg-info/dependency_links.txt
|
|
8
|
+
atbash_hermes_plugin.egg-info/entry_points.txt
|
|
9
|
+
atbash_hermes_plugin.egg-info/requires.txt
|
|
10
|
+
atbash_hermes_plugin.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
atbash-sdk==0.1.3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
atbash_hermes_plugin
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "atbash-hermes-plugin"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Atbash safety plugin for Hermes Agent"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "atbash" }
|
|
13
|
+
]
|
|
14
|
+
license = "LicenseRef-Atbash-Proprietary"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
keywords = [
|
|
17
|
+
"atbash",
|
|
18
|
+
"hermes",
|
|
19
|
+
"hermes-agent",
|
|
20
|
+
"agent-safety",
|
|
21
|
+
"ai-safety",
|
|
22
|
+
"tool-guard",
|
|
23
|
+
"judge",
|
|
24
|
+
"policy"
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"atbash-sdk==0.1.3"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/Atbash-Ai/atbash-hermes-plugin"
|
|
32
|
+
Repository = "https://github.com/Atbash-Ai/atbash-hermes-plugin"
|
|
33
|
+
|
|
34
|
+
[project.entry-points."hermes_agent.plugins"]
|
|
35
|
+
atbash-hermes-plugin = "atbash_hermes_plugin"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
include = ["atbash_hermes_plugin*"]
|