clawclamp 0.1.3 → 0.1.4

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.
Binary file
package/index.js ADDED
@@ -0,0 +1,224 @@
1
+ import { createRequire } from "node:module";
2
+ import { join } from "path";
3
+ import { existsSync, mkdirSync, readFileSync } from "fs";
4
+ import { Hono } from "hono";
5
+ import { html } from "hono/html";
6
+ //#region \0rolldown/runtime.js
7
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
8
+ //#endregion
9
+ //#region src/cedar-engine.ts
10
+ var CedarEngine = class {
11
+ constructor(config) {
12
+ this.mode = "monitor";
13
+ this.mode = config.mode || "monitor";
14
+ console.log(`[CedarEngine] Initialized in ${this.mode} mode`);
15
+ }
16
+ setMode(mode) {
17
+ this.mode = mode;
18
+ console.log(`[CedarEngine] Switched to ${this.mode} mode`);
19
+ }
20
+ getMode() {
21
+ return this.mode;
22
+ }
23
+ async authorize(params) {
24
+ console.log(`[CedarEngine] Evaluating: ${params.principal} -> ${params.action} on ${params.resource}`);
25
+ let decision = "Allow";
26
+ const diagnostics = [];
27
+ if (params.action.includes("exec") && params.resource.includes("rm")) {
28
+ if (params.principal !== "User::\"admin\"") {
29
+ decision = "Deny";
30
+ diagnostics.push("Dangerous command 'rm' is restricted to admin.");
31
+ }
32
+ }
33
+ return {
34
+ decision,
35
+ diagnostics
36
+ };
37
+ }
38
+ };
39
+ //#endregion
40
+ //#region src/audit-store.ts
41
+ var AuditStore = class {
42
+ constructor(workspaceDir) {
43
+ this.entries = [];
44
+ const auditDir = join(workspaceDir, ".openclaw", "audit");
45
+ if (!existsSync(auditDir)) mkdirSync(auditDir, { recursive: true });
46
+ this.logFile = join(auditDir, "cedar-audit.jsonl");
47
+ this.load();
48
+ }
49
+ load() {
50
+ if (existsSync(this.logFile)) try {
51
+ this.entries = readFileSync(this.logFile, "utf-8").split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
52
+ } catch (e) {
53
+ console.error("Failed to load audit logs:", e);
54
+ }
55
+ }
56
+ append(entry) {
57
+ this.entries.push(entry);
58
+ try {
59
+ const line = JSON.stringify(entry) + "\n";
60
+ __require("fs").appendFileSync(this.logFile, line);
61
+ } catch (e) {
62
+ console.error("Failed to write audit log:", e);
63
+ }
64
+ }
65
+ getAll() {
66
+ return this.entries;
67
+ }
68
+ getRecent(limit = 50) {
69
+ return this.entries.slice(-limit);
70
+ }
71
+ };
72
+ //#endregion
73
+ //#region src/server.ts
74
+ const createServer = (engine, store) => {
75
+ const app = new Hono();
76
+ app.get("/", (c) => {
77
+ const logs = store.getRecent(50);
78
+ const mode = engine.getMode();
79
+ return c.html(html`
80
+ <!DOCTYPE html>
81
+ <html>
82
+ <head>
83
+ <title>OpenClaw ClawClamp Audit</title>
84
+ <style>
85
+ body { font-family: sans-serif; padding: 20px; }
86
+ table { width: 100%; border-collapse: collapse; margin-top: 20px; }
87
+ th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
88
+ th { background-color: #f2f2f2; }
89
+ .allow { color: green; }
90
+ .deny { color: red; }
91
+ .blocked { font-weight: bold; color: darkred; }
92
+ .monitored { color: orange; }
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <h1>ClawClamp Policy Audit</h1>
97
+ <p>Current Mode: <strong>${mode.toUpperCase()}</strong></p>
98
+ <form action="/mode" method="post">
99
+ <select name="mode">
100
+ <option value="monitor" ${mode === "monitor" ? "selected" : ""}>Monitor (Log Only)</option>
101
+ <option value="enforce" ${mode === "enforce" ? "selected" : ""}>Enforce (Block)</option>
102
+ </select>
103
+ <button type="submit">Update Mode</button>
104
+ </form>
105
+
106
+ <h2>Recent Tool Executions</h2>
107
+ <table>
108
+ <thead>
109
+ <tr>
110
+ <th>Time</th>
111
+ <th>Run ID</th>
112
+ <th>Principal</th>
113
+ <th>Action</th>
114
+ <th>Resource</th>
115
+ <th>Decision</th>
116
+ <th>Result</th>
117
+ </tr>
118
+ </thead>
119
+ <tbody>
120
+ ${logs.map((log) => html`
121
+ <tr>
122
+ <td>${new Date(log.timestamp).toLocaleTimeString()}</td>
123
+ <td>${log.runId || "-"}</td>
124
+ <td>${log.principal}</td>
125
+ <td>${log.action}</td>
126
+ <td>${log.resource}</td>
127
+ <td class="${log.decision.toLowerCase()}">${log.decision}</td>
128
+ <td class="${log.blocked ? "blocked" : "monitored"}">
129
+ ${log.blocked ? "BLOCKED" : "ALLOWED"}
130
+ </td>
131
+ </tr>
132
+ `)}
133
+ </tbody>
134
+ </table>
135
+ </body>
136
+ </html>
137
+ `);
138
+ });
139
+ app.post("/mode", async (c) => {
140
+ const mode = (await c.req.parseBody())["mode"];
141
+ if (mode === "monitor" || mode === "enforce") engine.setMode(mode);
142
+ return c.redirect("/");
143
+ });
144
+ app.get("/api/logs", (c) => {
145
+ return c.json(store.getAll());
146
+ });
147
+ return app;
148
+ };
149
+ //#endregion
150
+ //#region index.ts
151
+ const plugin = {
152
+ id: "clawclamp",
153
+ name: "ClawClamp Policy Audit",
154
+ description: "Cedarling-based permission control with audit logging and dry-run mode.",
155
+ register(api) {
156
+ const engine = new CedarEngine({
157
+ mode: api.pluginConfig?.mode || "monitor",
158
+ policyStoreUrl: api.pluginConfig?.policyStoreUrl
159
+ });
160
+ const auditStore = new AuditStore(api.config.workspaceDir || process.cwd());
161
+ api.on("before_tool_call", async (event, ctx) => {
162
+ const { toolName, params, runId } = event;
163
+ const { agentId, sessionId } = ctx;
164
+ const principal = `User::"${agentId || "unknown"}"`;
165
+ const action = `Action::"tool:${toolName}"`;
166
+ const resource = `Resource::"global"`;
167
+ const result = await engine.authorize({
168
+ principal,
169
+ action,
170
+ resource,
171
+ context: {
172
+ sessionId,
173
+ runId,
174
+ params,
175
+ timestamp: Date.now()
176
+ }
177
+ });
178
+ const shouldBlock = result.decision === "Deny" && engine.getMode() === "enforce";
179
+ auditStore.append({
180
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
181
+ runId,
182
+ principal,
183
+ action,
184
+ resource,
185
+ decision: result.decision,
186
+ mode: engine.getMode(),
187
+ diagnostics: result.diagnostics,
188
+ blocked: shouldBlock
189
+ });
190
+ if (shouldBlock) return {
191
+ block: true,
192
+ blockReason: `Cedar Policy Denied: ${result.diagnostics.join("; ")}`
193
+ };
194
+ });
195
+ api.registerHttpRoute({
196
+ path: "/",
197
+ auth: "plugin",
198
+ match: "prefix",
199
+ handler: async (req, res) => {
200
+ if (req.method === "GET" && (req.url === "/" || req.url === "")) {
201
+ const html = await (await createServer(engine, auditStore).request("/")).text();
202
+ res.writeHead(200, { "Content-Type": "text/html" });
203
+ res.end(html);
204
+ return true;
205
+ }
206
+ if (req.method === "POST" && req.url === "/mode") {
207
+ let body = "";
208
+ req.on("data", (chunk) => body += chunk);
209
+ req.on("end", () => {
210
+ const mode = new URLSearchParams(body).get("mode");
211
+ if (mode === "enforce" || mode === "monitor") engine.setMode(mode);
212
+ res.writeHead(302, { "Location": "./" });
213
+ res.end();
214
+ });
215
+ return true;
216
+ }
217
+ return false;
218
+ }
219
+ });
220
+ api.logger.info("Cedar Audit Plugin Activated");
221
+ }
222
+ };
223
+ //#endregion
224
+ export { plugin as default, plugin };
package/package.json CHANGED
@@ -2,13 +2,13 @@
2
2
  "id": "clawclamp",
3
3
  "name": "clawclamp",
4
4
  "description": "Cedarling-based permission control with audit logging and dry-run mode.",
5
- "version": "0.1.3",
5
+ "version": "0.1.4",
6
6
  "type": "module",
7
- "main": "dist/index.js",
8
- "types": "dist/index.d.ts",
7
+ "main": "index.js",
8
+ "types": "index.d.ts",
9
9
  "license": "MIT",
10
10
  "scripts": {
11
- "build": "tsdown && mv dist/index.mjs dist/index.js",
11
+ "build": "tsdown && mv index.mjs index.js",
12
12
  "dev": "tsdown --watch",
13
13
  "typecheck": "tsc --noEmit"
14
14
  },
@@ -21,6 +21,6 @@
21
21
  "@types/node": "^22.10.2"
22
22
  },
23
23
  "openclaw": {
24
- "extensions": ["dist/index.js"]
24
+ "extensions": ["index.js"]
25
25
  }
26
26
  }
package/tsdown.config.ts CHANGED
@@ -5,6 +5,7 @@ export default defineConfig({
5
5
  entry: ["./index.ts"],
6
6
  format: "esm",
7
7
  target: "node18",
8
- clean: true,
8
+ clean: false,
9
+ outDir: ".",
9
10
  dts: false,
10
11
  });