@moltnet/guard 1.0.0

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.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # @moltnet/guard
2
+
3
+ **MoltGuard plugin for Clawdbot** — Security & observability for AI agents.
4
+
5
+ [![MoltGuard](https://guard.moltnet.ai/api/badge?label=moltguard&status=secure)](https://guard.moltnet.ai)
6
+
7
+ ## Features
8
+
9
+ - 🔍 **Action Logging** — Every tool call is logged with risk classification
10
+ - 🧠 **Mind Graph** — Visualize your agent's thought process in real-time
11
+ - ✋ **Intent Gating** — Require approval for high-risk actions
12
+ - 🎮 **Remote Control** — Pause, resume, or stop your agent remotely
13
+ - 📊 **Audit Dashboard** — Complete history with search, filters, and export
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ clawdbot plugins install @moltnet/guard
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ 1. **Get your token** at [guard.moltnet.ai](https://guard.moltnet.ai)
24
+
25
+ 2. **Add to your Clawdbot config** (`~/.clawdbot/config.json`):
26
+
27
+ ```json
28
+ {
29
+ "plugins": {
30
+ "entries": {
31
+ "moltguard": {
32
+ "enabled": true,
33
+ "config": {
34
+ "token": "mg_your_token_here",
35
+ "agentName": "my-assistant"
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ 3. **Restart Clawdbot**
44
+
45
+ ```bash
46
+ clawdbot gateway restart
47
+ ```
48
+
49
+ ## Configuration Options
50
+
51
+ | Option | Type | Default | Description |
52
+ |--------|------|---------|-------------|
53
+ | `token` | string | **required** | Your MoltGuard API token |
54
+ | `url` | string | `https://guard.moltnet.ai` | MoltGuard server URL |
55
+ | `agentName` | string | `clawdbot-<hostname>` | Display name in dashboard |
56
+ | `logThoughts` | boolean | `true` | Send reasoning traces to Mind Graph |
57
+ | `gateHighRisk` | boolean | `true` | Require approval for high-risk actions |
58
+ | `gateTools` | string[] | `["exec", "message", "write"]` | Tools that require approval |
59
+ | `pollCommands` | boolean | `true` | Enable remote control (pause/resume/stop) |
60
+ | `pollIntervalMs` | number | `5000` | Command poll interval in ms |
61
+
62
+ ## Risk Classification
63
+
64
+ The plugin automatically classifies tool calls by risk:
65
+
66
+ | Risk | Tools |
67
+ |------|-------|
68
+ | 🔴 **Critical** | `gateway`, `exec` (with `rm`, `sudo`) |
69
+ | 🟠 **High** | `exec`, `message`, `nodes` |
70
+ | 🟡 **Medium** | `write`, `edit`, `browser`, `cron`, `process` |
71
+ | 🟢 **Low** | `read`, `web_search`, `web_fetch`, `memory_*` |
72
+
73
+ ## How It Works
74
+
75
+ ```
76
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
77
+ │ Clawdbot │────▶│ MoltGuard │◀────│ Human │
78
+ │ (Agent) │ │ Plugin │ │ (Dashboard) │
79
+ └─────────────────┘ └────────┬────────┘ └─────────────────┘
80
+
81
+
82
+ ┌─────────────────┐
83
+ │ guard.moltnet │
84
+ │ .ai │
85
+ └─────────────────┘
86
+ ```
87
+
88
+ 1. Plugin intercepts all tool calls via `tool_result_persist` hook
89
+ 2. Actions are logged to MoltGuard with risk classification
90
+ 3. High-risk actions wait for approval (if `gateHighRisk` enabled)
91
+ 4. Human approves/rejects from dashboard or Telegram
92
+ 5. Plugin polls for remote control commands
93
+
94
+ ## Self-Hosting
95
+
96
+ If you're running your own MoltGuard instance:
97
+
98
+ ```json
99
+ {
100
+ "plugins": {
101
+ "entries": {
102
+ "moltguard": {
103
+ "enabled": true,
104
+ "config": {
105
+ "url": "https://your-moltguard.com",
106
+ "token": "your_token"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ## Links
115
+
116
+ - **Dashboard:** [guard.moltnet.ai](https://guard.moltnet.ai)
117
+ - **Docs:** [guard.moltnet.ai/docs](https://guard.moltnet.ai/docs)
118
+ - **GitHub:** [github.com/moltnet/guard](https://github.com/moltnet/guard)
119
+
120
+ ## License
121
+
122
+ MIT
@@ -0,0 +1,65 @@
1
+ {
2
+ "id": "moltguard",
3
+ "name": "MoltGuard",
4
+ "version": "1.0.0",
5
+ "description": "Security & observability for AI agents. See what your agent is thinking, approve risky actions, control it remotely.",
6
+ "homepage": "https://guard.moltnet.ai",
7
+ "author": "MoltNet <hello@moltnet.ai>",
8
+ "license": "MIT",
9
+ "configSchema": {
10
+ "type": "object",
11
+ "additionalProperties": false,
12
+ "properties": {
13
+ "url": {
14
+ "type": "string",
15
+ "default": "https://guard.moltnet.ai",
16
+ "description": "MoltGuard server URL"
17
+ },
18
+ "token": {
19
+ "type": "string",
20
+ "description": "Your MoltGuard API token (get one at guard.moltnet.ai)"
21
+ },
22
+ "agentName": {
23
+ "type": "string",
24
+ "description": "Display name for this agent in MoltGuard dashboard"
25
+ },
26
+ "logThoughts": {
27
+ "type": "boolean",
28
+ "default": true,
29
+ "description": "Send reasoning traces to Mind Graph"
30
+ },
31
+ "gateHighRisk": {
32
+ "type": "boolean",
33
+ "default": true,
34
+ "description": "Require approval for high-risk actions"
35
+ },
36
+ "gateTools": {
37
+ "type": "array",
38
+ "items": { "type": "string" },
39
+ "default": ["exec", "message", "write"],
40
+ "description": "Tool names that require approval when gateHighRisk is enabled"
41
+ },
42
+ "pollCommands": {
43
+ "type": "boolean",
44
+ "default": true,
45
+ "description": "Poll for remote control commands (pause/resume/stop)"
46
+ },
47
+ "pollIntervalMs": {
48
+ "type": "number",
49
+ "default": 5000,
50
+ "description": "How often to poll for commands (ms)"
51
+ }
52
+ },
53
+ "required": ["token"]
54
+ },
55
+ "uiHints": {
56
+ "url": { "label": "MoltGuard URL", "placeholder": "https://guard.moltnet.ai" },
57
+ "token": { "label": "API Token", "sensitive": true, "placeholder": "mg_xxxxxxxxxxxx" },
58
+ "agentName": { "label": "Agent Name", "placeholder": "my-assistant" },
59
+ "logThoughts": { "label": "Log Thoughts to Mind Graph" },
60
+ "gateHighRisk": { "label": "Gate High-Risk Actions" },
61
+ "gateTools": { "label": "Tools Requiring Approval" },
62
+ "pollCommands": { "label": "Enable Remote Control" },
63
+ "pollIntervalMs": { "label": "Poll Interval (ms)" }
64
+ }
65
+ }
package/index.ts ADDED
@@ -0,0 +1,338 @@
1
+ /**
2
+ * MoltGuard Plugin for Clawdbot
3
+ *
4
+ * Security & observability for AI agents.
5
+ * - Logs all tool calls to MoltGuard
6
+ * - Sends reasoning traces to Mind Graph
7
+ * - Gates high-risk actions (requires approval)
8
+ * - Polls for remote control commands
9
+ */
10
+
11
+ import type { PluginAPI, PluginRegisterFn } from 'clawdbot';
12
+
13
+ interface MoltGuardConfig {
14
+ url: string;
15
+ token: string;
16
+ agentName?: string;
17
+ logThoughts?: boolean;
18
+ gateHighRisk?: boolean;
19
+ gateTools?: string[];
20
+ pollCommands?: boolean;
21
+ pollIntervalMs?: number;
22
+ }
23
+
24
+ interface MoltGuardState {
25
+ agentId: string | null;
26
+ sessionId: string | null;
27
+ paused: boolean;
28
+ pollInterval: NodeJS.Timeout | null;
29
+ }
30
+
31
+ const state: MoltGuardState = {
32
+ agentId: null,
33
+ sessionId: null,
34
+ paused: false,
35
+ pollInterval: null,
36
+ };
37
+
38
+ // Risk classification for common tools
39
+ const TOOL_RISK: Record<string, 'low' | 'medium' | 'high' | 'critical'> = {
40
+ // High risk - external effects
41
+ 'exec': 'high',
42
+ 'message': 'high',
43
+ 'write': 'medium',
44
+ 'edit': 'medium',
45
+ 'browser': 'medium',
46
+ 'web_fetch': 'low',
47
+ 'web_search': 'low',
48
+ 'read': 'low',
49
+ 'memory_search': 'low',
50
+ 'memory_get': 'low',
51
+ 'tts': 'low',
52
+ 'image': 'low',
53
+ // Critical - system changes
54
+ 'gateway': 'critical',
55
+ 'cron': 'medium',
56
+ 'nodes': 'high',
57
+ 'canvas': 'low',
58
+ 'process': 'medium',
59
+ 'sessions_spawn': 'medium',
60
+ 'sessions_send': 'medium',
61
+ };
62
+
63
+ async function apiCall(
64
+ config: MoltGuardConfig,
65
+ endpoint: string,
66
+ method: 'GET' | 'POST' | 'PATCH' = 'POST',
67
+ body?: Record<string, unknown>
68
+ ): Promise<unknown> {
69
+ const url = `${config.url}/api${endpoint}`;
70
+ const headers: Record<string, string> = {
71
+ 'Content-Type': 'application/json',
72
+ 'X-API-Key': config.token,
73
+ };
74
+
75
+ const res = await fetch(url, {
76
+ method,
77
+ headers,
78
+ body: body ? JSON.stringify(body) : undefined,
79
+ });
80
+
81
+ if (!res.ok) {
82
+ const text = await res.text();
83
+ throw new Error(`MoltGuard API error: ${res.status} ${text}`);
84
+ }
85
+
86
+ return res.json();
87
+ }
88
+
89
+ async function registerAgent(config: MoltGuardConfig): Promise<string> {
90
+ const hostname = process.env.HOSTNAME || 'unknown';
91
+ const result = await apiCall(config, '/agents/register', 'POST', {
92
+ name: config.agentName || `clawdbot-${hostname}`,
93
+ description: `Clawdbot agent on ${hostname}`,
94
+ capabilities: ['tool_execution', 'reasoning', 'memory'],
95
+ }) as { id: string };
96
+ return result.id;
97
+ }
98
+
99
+ async function startSession(config: MoltGuardConfig, agentName: string): Promise<string> {
100
+ const result = await apiCall(config, '/sessions', 'POST', {
101
+ agent: agentName,
102
+ metadata: JSON.stringify({ startedAt: new Date().toISOString() }),
103
+ }) as { id: string };
104
+ return result.id;
105
+ }
106
+
107
+ async function logTrace(
108
+ config: MoltGuardConfig,
109
+ agentName: string,
110
+ sessionId: string | null,
111
+ type: string,
112
+ title: string,
113
+ content: string
114
+ ): Promise<void> {
115
+ await apiCall(config, '/traces', 'POST', {
116
+ agent: agentName,
117
+ sessionId,
118
+ type,
119
+ title,
120
+ content,
121
+ });
122
+ }
123
+
124
+ async function logAction(
125
+ config: MoltGuardConfig,
126
+ agentName: string,
127
+ sessionId: string | null,
128
+ toolName: string,
129
+ params: Record<string, unknown>,
130
+ risk: string,
131
+ status: 'pending' | 'executed' = 'executed'
132
+ ): Promise<{ id: string; status: string }> {
133
+ const result = await apiCall(config, '/actions', 'POST', {
134
+ agent: agentName,
135
+ sessionId,
136
+ type: `tool.${toolName}`,
137
+ description: summarizeToolCall(toolName, params),
138
+ details: JSON.stringify(params),
139
+ risk,
140
+ status,
141
+ reversible: toolName === 'write' || toolName === 'edit' ? 1 : 0,
142
+ }) as { id: string; status: string };
143
+ return result;
144
+ }
145
+
146
+ async function pollForDecision(
147
+ config: MoltGuardConfig,
148
+ actionId: string,
149
+ timeoutMs: number = 300000
150
+ ): Promise<'approved' | 'rejected' | 'timeout'> {
151
+ const startTime = Date.now();
152
+ const pollInterval = 2000;
153
+
154
+ while (Date.now() - startTime < timeoutMs) {
155
+ const result = await apiCall(config, `/actions/${actionId}/decision`, 'GET') as { status: string };
156
+
157
+ if (result.status === 'approved') return 'approved';
158
+ if (result.status === 'rejected' || result.status === 'timeout') return result.status as 'rejected' | 'timeout';
159
+
160
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
161
+ }
162
+
163
+ return 'timeout';
164
+ }
165
+
166
+ async function pollCommands(config: MoltGuardConfig, agentName: string): Promise<void> {
167
+ try {
168
+ const commands = await apiCall(config, `/control/${agentName}/pending`, 'GET') as Array<{
169
+ id: string;
170
+ command: string;
171
+ params?: string;
172
+ }>;
173
+
174
+ for (const cmd of commands) {
175
+ console.log(`[moltguard] Received command: ${cmd.command}`);
176
+
177
+ switch (cmd.command) {
178
+ case 'pause':
179
+ state.paused = true;
180
+ console.log('[moltguard] Agent PAUSED by remote control');
181
+ break;
182
+ case 'resume':
183
+ state.paused = false;
184
+ console.log('[moltguard] Agent RESUMED by remote control');
185
+ break;
186
+ case 'stop':
187
+ console.log('[moltguard] Agent STOPPED by remote control');
188
+ process.exit(0);
189
+ break;
190
+ }
191
+
192
+ // ACK the command
193
+ await apiCall(config, `/control/${agentName}/ack/${cmd.id}`, 'POST');
194
+ }
195
+ } catch (err) {
196
+ // Silent fail for polling
197
+ }
198
+ }
199
+
200
+ function summarizeToolCall(toolName: string, params: Record<string, unknown>): string {
201
+ switch (toolName) {
202
+ case 'exec':
203
+ return `Execute: ${String(params.command || '').slice(0, 100)}`;
204
+ case 'write':
205
+ return `Write file: ${params.path || params.file_path}`;
206
+ case 'edit':
207
+ return `Edit file: ${params.path || params.file_path}`;
208
+ case 'read':
209
+ return `Read file: ${params.path || params.file_path}`;
210
+ case 'message':
211
+ return `Send message: ${String(params.message || '').slice(0, 50)}...`;
212
+ case 'browser':
213
+ return `Browser: ${params.action} ${params.targetUrl || ''}`;
214
+ case 'web_fetch':
215
+ return `Fetch URL: ${params.url}`;
216
+ case 'web_search':
217
+ return `Search: ${params.query}`;
218
+ case 'gateway':
219
+ return `Gateway: ${params.action}`;
220
+ case 'cron':
221
+ return `Cron: ${params.action}`;
222
+ default:
223
+ return `${toolName}: ${JSON.stringify(params).slice(0, 80)}`;
224
+ }
225
+ }
226
+
227
+ function getToolRisk(toolName: string, params: Record<string, unknown>): 'low' | 'medium' | 'high' | 'critical' {
228
+ const baseRisk = TOOL_RISK[toolName] || 'medium';
229
+
230
+ // Elevate risk for certain patterns
231
+ if (toolName === 'exec') {
232
+ const cmd = String(params.command || '').toLowerCase();
233
+ if (cmd.includes('rm ') || cmd.includes('sudo') || cmd.includes('chmod')) {
234
+ return 'critical';
235
+ }
236
+ if (cmd.includes('curl') || cmd.includes('wget') || cmd.includes('ssh')) {
237
+ return 'high';
238
+ }
239
+ }
240
+
241
+ if (toolName === 'write' || toolName === 'edit') {
242
+ const path = String(params.path || params.file_path || '');
243
+ if (path.includes('.ssh') || path.includes('.env') || path.includes('config')) {
244
+ return 'high';
245
+ }
246
+ }
247
+
248
+ return baseRisk;
249
+ }
250
+
251
+ const register: PluginRegisterFn = (api: PluginAPI) => {
252
+ const config = api.config.plugins?.entries?.moltguard?.config as MoltGuardConfig | undefined;
253
+
254
+ if (!config?.token) {
255
+ console.log('[moltguard] No token configured. Get one at https://guard.moltnet.ai');
256
+ return;
257
+ }
258
+
259
+ const cfg: MoltGuardConfig = {
260
+ url: config.url || 'https://guard.moltnet.ai',
261
+ token: config.token,
262
+ agentName: config.agentName,
263
+ logThoughts: config.logThoughts ?? true,
264
+ gateHighRisk: config.gateHighRisk ?? true,
265
+ gateTools: config.gateTools ?? ['exec', 'message', 'write'],
266
+ pollCommands: config.pollCommands ?? true,
267
+ pollIntervalMs: config.pollIntervalMs ?? 5000,
268
+ };
269
+
270
+ const agentName = cfg.agentName || `clawdbot-${process.env.HOSTNAME || 'agent'}`;
271
+
272
+ // Register agent on startup
273
+ (async () => {
274
+ try {
275
+ state.agentId = await registerAgent(cfg);
276
+ state.sessionId = await startSession(cfg, agentName);
277
+ console.log(`[moltguard] Connected to ${cfg.url}`);
278
+ console.log(`[moltguard] Agent: ${agentName} | Session: ${state.sessionId}`);
279
+
280
+ // Start command polling
281
+ if (cfg.pollCommands) {
282
+ state.pollInterval = setInterval(() => {
283
+ pollCommands(cfg, agentName);
284
+ }, cfg.pollIntervalMs);
285
+ }
286
+ } catch (err) {
287
+ console.error('[moltguard] Failed to connect:', err instanceof Error ? err.message : String(err));
288
+ }
289
+ })();
290
+
291
+ // Hook: Intercept tool results before persistence
292
+ api.hooks?.register('tool_result_persist', (toolResult) => {
293
+ if (!toolResult || typeof toolResult !== 'object') return toolResult;
294
+
295
+ const { name, input } = toolResult as { name?: string; input?: Record<string, unknown> };
296
+ if (!name) return toolResult;
297
+
298
+ const risk = getToolRisk(name, input || {});
299
+ const shouldGate = cfg.gateHighRisk &&
300
+ (risk === 'high' || risk === 'critical') &&
301
+ cfg.gateTools?.includes(name);
302
+
303
+ // Log the action (async, don't block)
304
+ logAction(cfg, agentName, state.sessionId, name, input || {}, risk, shouldGate ? 'pending' : 'executed')
305
+ .catch(err => console.error('[moltguard] Failed to log action:', err));
306
+
307
+ return toolResult;
308
+ });
309
+
310
+ // Hook: Log agent bootstrap (session start)
311
+ api.hooks?.register('agent:bootstrap', (event) => {
312
+ if (cfg.logThoughts) {
313
+ logTrace(cfg, agentName, state.sessionId, 'session', 'Session Started', 'Agent bootstrap initiated')
314
+ .catch(() => {});
315
+ }
316
+ });
317
+
318
+ // Hook: Log commands
319
+ api.hooks?.register('command', (event) => {
320
+ if (cfg.logThoughts && event.action) {
321
+ logTrace(cfg, agentName, state.sessionId, 'command', `Command: /${event.action}`, `User issued /${event.action}`)
322
+ .catch(() => {});
323
+ }
324
+ });
325
+
326
+ // Cleanup on exit
327
+ process.on('beforeExit', () => {
328
+ if (state.pollInterval) {
329
+ clearInterval(state.pollInterval);
330
+ }
331
+ });
332
+
333
+ console.log('[moltguard] Plugin loaded');
334
+ };
335
+
336
+ export default register;
337
+ export const id = 'moltguard';
338
+ export const name = 'MoltGuard';
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@moltnet/guard",
3
+ "version": "1.0.0",
4
+ "description": "MoltGuard plugin for Clawdbot - Security & observability for AI agents",
5
+ "main": "index.ts",
6
+ "type": "module",
7
+ "author": "MoltNet <hello@moltnet.ai>",
8
+ "license": "MIT",
9
+ "homepage": "https://guard.moltnet.ai",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/moltnet/guard"
13
+ },
14
+ "keywords": [
15
+ "clawdbot",
16
+ "clawdbot-plugin",
17
+ "ai-agent",
18
+ "security",
19
+ "observability",
20
+ "audit",
21
+ "moltguard"
22
+ ],
23
+ "clawdbot": {
24
+ "extensions": ["./index.ts"]
25
+ },
26
+ "peerDependencies": {
27
+ "clawdbot": ">=0.1.0"
28
+ },
29
+ "files": [
30
+ "index.ts",
31
+ "clawdbot.plugin.json",
32
+ "README.md"
33
+ ]
34
+ }