@plusplus7/clawclamp 0.1.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/LICENSE +21 -0
- package/README.md +94 -0
- package/README.zh-CN.md +94 -0
- package/RELEASE.md +34 -0
- package/assets/app.js +362 -0
- package/assets/index.html +125 -0
- package/assets/styles.css +432 -0
- package/index.ts +45 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +17 -0
- package/src/audit.ts +94 -0
- package/src/cedarling.ts +46 -0
- package/src/config.ts +249 -0
- package/src/grants.ts +183 -0
- package/src/guard.test.ts +69 -0
- package/src/guard.ts +342 -0
- package/src/http.ts +433 -0
- package/src/mode.ts +48 -0
- package/src/policy-store.ts +186 -0
- package/src/policy.ts +74 -0
- package/src/storage.ts +23 -0
- package/src/types.ts +63 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>OpenClaw Clawclamp 审计控制台</title>
|
|
7
|
+
<link rel="stylesheet" href="/plugins/clawclamp/assets/styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="page">
|
|
11
|
+
<header class="header">
|
|
12
|
+
<div>
|
|
13
|
+
<p class="eyebrow">OpenClaw - Clawclamp</p>
|
|
14
|
+
<h1>工具授权与审计</h1>
|
|
15
|
+
<p class="subhead">
|
|
16
|
+
基于 Cedar 的策略决策,支持短期授权与灰度放行。
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="header-actions">
|
|
20
|
+
<button id="refresh" class="btn ghost">刷新</button>
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
|
|
24
|
+
<section class="card policy-shell">
|
|
25
|
+
<div class="policy-head">
|
|
26
|
+
<div>
|
|
27
|
+
<h2>Cedar 策略</h2>
|
|
28
|
+
<div class="note">查看与维护当前策略集(policyStoreUri 配置时为只读)。</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="policy-badge">Policy Lab</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="policy-grid">
|
|
33
|
+
<div class="policy-list-wrap">
|
|
34
|
+
<div class="label">策略列表</div>
|
|
35
|
+
<div class="policy-list" id="policy-list"></div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="policy-editor">
|
|
38
|
+
<div class="policy-form">
|
|
39
|
+
<label>
|
|
40
|
+
Policy ID
|
|
41
|
+
<input id="policy-id" placeholder="policy-id" />
|
|
42
|
+
</label>
|
|
43
|
+
<label>
|
|
44
|
+
Policy 内容
|
|
45
|
+
<textarea id="policy-content" rows="12" placeholder="permit(principal, action, resource) when { ... }"></textarea>
|
|
46
|
+
</label>
|
|
47
|
+
<div class="policy-actions">
|
|
48
|
+
<button id="policy-create" class="btn primary">新增</button>
|
|
49
|
+
<button id="policy-update" class="btn">保存</button>
|
|
50
|
+
<button id="policy-delete" class="btn warn">删除</button>
|
|
51
|
+
</div>
|
|
52
|
+
<div id="policy-status" class="note"></div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
|
|
58
|
+
<section class="grid">
|
|
59
|
+
<div class="card">
|
|
60
|
+
<h2>模式</h2>
|
|
61
|
+
<div class="mode-row">
|
|
62
|
+
<div>
|
|
63
|
+
<div class="label">当前模式</div>
|
|
64
|
+
<div id="mode" class="value">加载中...</div>
|
|
65
|
+
<div id="mode-note" class="note"></div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="mode-actions">
|
|
68
|
+
<button id="mode-enforce" class="btn">强制</button>
|
|
69
|
+
<button id="mode-gray" class="btn warn">灰度</button>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="card">
|
|
75
|
+
<h2>短期授权</h2>
|
|
76
|
+
<form id="grant-form" class="grant-form">
|
|
77
|
+
<label>
|
|
78
|
+
工具名称
|
|
79
|
+
<input id="grant-tool" placeholder="exec(或 *)" required />
|
|
80
|
+
</label>
|
|
81
|
+
<label>
|
|
82
|
+
TTL(秒)
|
|
83
|
+
<input id="grant-ttl" type="number" min="60" step="60" />
|
|
84
|
+
</label>
|
|
85
|
+
<label>
|
|
86
|
+
备注
|
|
87
|
+
<input id="grant-note" placeholder="原因 / 工单" />
|
|
88
|
+
</label>
|
|
89
|
+
<button class="btn primary" type="submit">创建授权</button>
|
|
90
|
+
<div id="grant-hint" class="note"></div>
|
|
91
|
+
</form>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div class="card">
|
|
95
|
+
<h2>当前授权</h2>
|
|
96
|
+
<div id="grants" class="list"></div>
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
|
|
100
|
+
<section class="card">
|
|
101
|
+
<h2>审计日志</h2>
|
|
102
|
+
<div class="note">最近的工具调用记录(允许、拒绝、灰度放行)。</div>
|
|
103
|
+
<div class="toolbar">
|
|
104
|
+
<label class="toolbar-item">
|
|
105
|
+
每页
|
|
106
|
+
<select id="audit-page-size">
|
|
107
|
+
<option value="20">20</option>
|
|
108
|
+
<option value="50" selected>50</option>
|
|
109
|
+
<option value="100">100</option>
|
|
110
|
+
<option value="200">200</option>
|
|
111
|
+
</select>
|
|
112
|
+
</label>
|
|
113
|
+
<div class="toolbar-item pagination">
|
|
114
|
+
<button id="audit-prev" class="btn">上一页</button>
|
|
115
|
+
<span id="audit-page-info" class="note">第 1 页</span>
|
|
116
|
+
<button id="audit-next" class="btn">下一页</button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="table" id="audit"></div>
|
|
120
|
+
</section>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<script type="module" src="/plugins/clawclamp/assets/app.js"></script>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
--bg: #f2f3f5;
|
|
4
|
+
--panel: #ffffff;
|
|
5
|
+
--ink: #0f1115;
|
|
6
|
+
--muted: #5f6774;
|
|
7
|
+
--accent: #00a3ff;
|
|
8
|
+
--accent-2: #7c8cff;
|
|
9
|
+
--warn: #ff5b4d;
|
|
10
|
+
--border: #d7dbe2;
|
|
11
|
+
--shadow: 0 10px 30px rgba(4, 8, 16, 0.08);
|
|
12
|
+
--radius: 10px;
|
|
13
|
+
--mono: "JetBrains Mono", "IBM Plex Mono", "SFMono-Regular", "Menlo", "Consolas", monospace;
|
|
14
|
+
--sans: "Space Grotesk", "IBM Plex Sans", "Segoe UI", sans-serif;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* {
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
body {
|
|
22
|
+
margin: 0;
|
|
23
|
+
font-family: var(--mono);
|
|
24
|
+
background: radial-gradient(circle at top, #ffffff 0%, var(--bg) 45%, #e7eaef 100%);
|
|
25
|
+
color: var(--ink);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.page {
|
|
29
|
+
max-width: 1120px;
|
|
30
|
+
margin: 0 auto;
|
|
31
|
+
padding: 20px 18px 32px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.header {
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: flex-start;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
gap: 16px;
|
|
39
|
+
padding: 16px 18px;
|
|
40
|
+
background: var(--panel);
|
|
41
|
+
border-radius: var(--radius);
|
|
42
|
+
box-shadow: var(--shadow);
|
|
43
|
+
border: 1px solid var(--border);
|
|
44
|
+
margin-bottom: 16px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.eyebrow {
|
|
48
|
+
margin: 0 0 8px;
|
|
49
|
+
text-transform: uppercase;
|
|
50
|
+
letter-spacing: 0.2em;
|
|
51
|
+
font-size: 0.7rem;
|
|
52
|
+
color: var(--muted);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
h1 {
|
|
56
|
+
margin: 0 0 4px;
|
|
57
|
+
font-size: 1.6rem;
|
|
58
|
+
letter-spacing: 0.02em;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.subhead {
|
|
62
|
+
margin: 0;
|
|
63
|
+
color: var(--muted);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.header-actions {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
gap: 12px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.grid {
|
|
73
|
+
display: grid;
|
|
74
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
75
|
+
gap: 12px;
|
|
76
|
+
margin-bottom: 16px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.card {
|
|
80
|
+
background: var(--panel);
|
|
81
|
+
border-radius: var(--radius);
|
|
82
|
+
padding: 14px;
|
|
83
|
+
border: 1px solid var(--border);
|
|
84
|
+
box-shadow: var(--shadow);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.policy-shell {
|
|
88
|
+
margin-bottom: 16px;
|
|
89
|
+
background:
|
|
90
|
+
linear-gradient(135deg, rgba(0, 163, 255, 0.1), rgba(124, 140, 255, 0.06)),
|
|
91
|
+
var(--panel);
|
|
92
|
+
border-color: #b9d7ea;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.policy-head {
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: space-between;
|
|
98
|
+
align-items: flex-start;
|
|
99
|
+
gap: 12px;
|
|
100
|
+
margin-bottom: 12px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.policy-badge {
|
|
104
|
+
padding: 6px 10px;
|
|
105
|
+
border: 1px solid #a7d7ff;
|
|
106
|
+
border-radius: 999px;
|
|
107
|
+
font-size: 0.72rem;
|
|
108
|
+
letter-spacing: 0.08em;
|
|
109
|
+
text-transform: uppercase;
|
|
110
|
+
color: #03527d;
|
|
111
|
+
background: rgba(255, 255, 255, 0.8);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
h2 {
|
|
115
|
+
margin: 0 0 8px;
|
|
116
|
+
font-size: 1rem;
|
|
117
|
+
letter-spacing: 0.02em;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.mode-row {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: space-between;
|
|
124
|
+
gap: 12px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.mode-actions {
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-direction: column;
|
|
130
|
+
gap: 6px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.label {
|
|
134
|
+
font-size: 0.8rem;
|
|
135
|
+
color: var(--muted);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.value {
|
|
139
|
+
font-size: 1.1rem;
|
|
140
|
+
font-weight: 600;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.note {
|
|
144
|
+
font-size: 0.85rem;
|
|
145
|
+
color: var(--muted);
|
|
146
|
+
margin-top: 6px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.toolbar {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: space-between;
|
|
153
|
+
gap: 10px;
|
|
154
|
+
margin-top: 10px;
|
|
155
|
+
flex-wrap: wrap;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.toolbar-item {
|
|
159
|
+
display: flex;
|
|
160
|
+
align-items: center;
|
|
161
|
+
gap: 8px;
|
|
162
|
+
font-size: 0.82rem;
|
|
163
|
+
color: var(--muted);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.toolbar-item select {
|
|
167
|
+
min-width: 76px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.pagination {
|
|
171
|
+
margin-left: auto;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.btn {
|
|
175
|
+
padding: 8px 12px;
|
|
176
|
+
border-radius: 8px;
|
|
177
|
+
border: 1px solid var(--border);
|
|
178
|
+
background: #fff;
|
|
179
|
+
cursor: pointer;
|
|
180
|
+
font-weight: 600;
|
|
181
|
+
font-family: var(--mono);
|
|
182
|
+
font-size: 0.82rem;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.btn.primary {
|
|
186
|
+
background: var(--accent);
|
|
187
|
+
color: #fff;
|
|
188
|
+
border-color: var(--accent);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.btn.warn {
|
|
192
|
+
background: var(--warn);
|
|
193
|
+
color: #fff;
|
|
194
|
+
border-color: var(--warn);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.btn.mini {
|
|
198
|
+
padding: 6px 8px;
|
|
199
|
+
border-radius: 6px;
|
|
200
|
+
font-size: 0.75rem;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.btn.ghost {
|
|
204
|
+
background: transparent;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.btn:disabled {
|
|
208
|
+
opacity: 0.5;
|
|
209
|
+
cursor: not-allowed;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.grant-form {
|
|
213
|
+
display: flex;
|
|
214
|
+
flex-direction: column;
|
|
215
|
+
gap: 12px;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.grant-form label {
|
|
219
|
+
display: flex;
|
|
220
|
+
flex-direction: column;
|
|
221
|
+
gap: 6px;
|
|
222
|
+
font-size: 0.85rem;
|
|
223
|
+
color: var(--muted);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
input,
|
|
227
|
+
select {
|
|
228
|
+
border: 1px solid var(--border);
|
|
229
|
+
border-radius: 8px;
|
|
230
|
+
padding: 8px 10px;
|
|
231
|
+
font-size: 0.85rem;
|
|
232
|
+
font-family: var(--mono);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.list {
|
|
236
|
+
display: grid;
|
|
237
|
+
gap: 10px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.list-item {
|
|
241
|
+
display: flex;
|
|
242
|
+
justify-content: space-between;
|
|
243
|
+
align-items: center;
|
|
244
|
+
padding: 10px;
|
|
245
|
+
border-radius: 8px;
|
|
246
|
+
border: 1px solid var(--border);
|
|
247
|
+
background: #f6f7f9;
|
|
248
|
+
font-size: 0.8rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.list-item strong {
|
|
252
|
+
font-family: var(--mono);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.table {
|
|
256
|
+
display: grid;
|
|
257
|
+
gap: 6px;
|
|
258
|
+
margin-top: 10px;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.table-row {
|
|
262
|
+
display: grid;
|
|
263
|
+
grid-template-columns: 110px 110px 90px 70px 1fr 120px;
|
|
264
|
+
gap: 10px;
|
|
265
|
+
align-items: center;
|
|
266
|
+
padding: 8px 10px;
|
|
267
|
+
border-radius: 8px;
|
|
268
|
+
border: 1px solid var(--border);
|
|
269
|
+
background: #fdfdfd;
|
|
270
|
+
font-size: 0.78rem;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.table-row.header {
|
|
274
|
+
background: #eef2f7;
|
|
275
|
+
font-weight: 700;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.table-row .mono {
|
|
279
|
+
font-family: var(--mono);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.actions {
|
|
283
|
+
display: flex;
|
|
284
|
+
gap: 6px;
|
|
285
|
+
flex-wrap: wrap;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.audit-detail {
|
|
289
|
+
grid-column: 1 / -1;
|
|
290
|
+
border-top: 1px dashed var(--border);
|
|
291
|
+
margin-top: 6px;
|
|
292
|
+
padding-top: 6px;
|
|
293
|
+
font-size: 0.72rem;
|
|
294
|
+
color: var(--muted);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.audit-detail pre {
|
|
298
|
+
margin: 6px 0 0;
|
|
299
|
+
padding: 8px;
|
|
300
|
+
border-radius: 6px;
|
|
301
|
+
border: 1px solid var(--border);
|
|
302
|
+
background: #f3f5f9;
|
|
303
|
+
font-size: 0.72rem;
|
|
304
|
+
line-height: 1.35;
|
|
305
|
+
overflow: auto;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.policy-grid {
|
|
309
|
+
display: grid;
|
|
310
|
+
grid-template-columns: 260px 1fr;
|
|
311
|
+
gap: 12px;
|
|
312
|
+
margin-top: 10px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.policy-list-wrap {
|
|
316
|
+
display: grid;
|
|
317
|
+
gap: 8px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.policy-list {
|
|
321
|
+
display: grid;
|
|
322
|
+
gap: 6px;
|
|
323
|
+
align-content: start;
|
|
324
|
+
max-height: 420px;
|
|
325
|
+
overflow: auto;
|
|
326
|
+
padding: 8px;
|
|
327
|
+
border: 1px solid var(--border);
|
|
328
|
+
border-radius: 10px;
|
|
329
|
+
background: rgba(255, 255, 255, 0.7);
|
|
330
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.policy-item {
|
|
334
|
+
padding: 8px 10px;
|
|
335
|
+
border-radius: 8px;
|
|
336
|
+
border: 1px solid #d6e3f0;
|
|
337
|
+
background: linear-gradient(180deg, #ffffff, #f5f9fd);
|
|
338
|
+
font-family: var(--mono);
|
|
339
|
+
font-size: 0.75rem;
|
|
340
|
+
text-align: left;
|
|
341
|
+
cursor: pointer;
|
|
342
|
+
transition: transform 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.policy-item:hover {
|
|
346
|
+
transform: translateY(-1px);
|
|
347
|
+
border-color: #8ecdf6;
|
|
348
|
+
background: linear-gradient(180deg, #ffffff, #eef8ff);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.policy-editor {
|
|
352
|
+
display: block;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.policy-form {
|
|
356
|
+
display: grid;
|
|
357
|
+
gap: 10px;
|
|
358
|
+
padding: 12px;
|
|
359
|
+
border: 1px solid #d6e3f0;
|
|
360
|
+
border-radius: 12px;
|
|
361
|
+
background: rgba(255, 255, 255, 0.86);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.policy-form label {
|
|
365
|
+
display: grid;
|
|
366
|
+
gap: 6px;
|
|
367
|
+
font-size: 0.8rem;
|
|
368
|
+
color: var(--muted);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.policy-form textarea {
|
|
372
|
+
border: 1px solid var(--border);
|
|
373
|
+
border-radius: 8px;
|
|
374
|
+
padding: 8px 10px;
|
|
375
|
+
font-size: 0.8rem;
|
|
376
|
+
font-family: var(--mono);
|
|
377
|
+
min-height: 260px;
|
|
378
|
+
resize: vertical;
|
|
379
|
+
background: #fbfdff;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.policy-actions {
|
|
383
|
+
display: flex;
|
|
384
|
+
gap: 6px;
|
|
385
|
+
flex-wrap: wrap;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@media (max-width: 900px) {
|
|
389
|
+
.policy-grid {
|
|
390
|
+
grid-template-columns: 1fr;
|
|
391
|
+
}
|
|
392
|
+
.policy-editor {
|
|
393
|
+
grid-template-columns: 1fr;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.badge {
|
|
398
|
+
padding: 3px 6px;
|
|
399
|
+
border-radius: 999px;
|
|
400
|
+
font-size: 0.68rem;
|
|
401
|
+
text-transform: uppercase;
|
|
402
|
+
font-weight: 700;
|
|
403
|
+
justify-self: start;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.badge.allow {
|
|
407
|
+
background: rgba(0, 163, 255, 0.15);
|
|
408
|
+
color: #0086cc;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.badge.deny {
|
|
412
|
+
background: rgba(255, 91, 77, 0.15);
|
|
413
|
+
color: #d9392c;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.badge.gray {
|
|
417
|
+
background: rgba(124, 140, 255, 0.2);
|
|
418
|
+
color: #4f5bff;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@media (max-width: 900px) {
|
|
422
|
+
.table-row {
|
|
423
|
+
grid-template-columns: 100px 1fr;
|
|
424
|
+
grid-template-rows: auto auto auto;
|
|
425
|
+
}
|
|
426
|
+
.table-row > :nth-child(3),
|
|
427
|
+
.table-row > :nth-child(4),
|
|
428
|
+
.table-row > :nth-child(5),
|
|
429
|
+
.table-row > :nth-child(6) {
|
|
430
|
+
grid-column: 1 / -1;
|
|
431
|
+
}
|
|
432
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
4
|
+
import { resolveClawClampConfig, clawClampConfigSchema } from "./src/config.js";
|
|
5
|
+
import { createClawClampHttpHandler } from "./src/http.js";
|
|
6
|
+
import { createClawClampService } from "./src/guard.js";
|
|
7
|
+
|
|
8
|
+
const plugin = {
|
|
9
|
+
id: "clawclamp",
|
|
10
|
+
name: "Clawclamp",
|
|
11
|
+
description: "Cedar-based authorization guard with tool audit logging.",
|
|
12
|
+
configSchema: clawClampConfigSchema,
|
|
13
|
+
register(api: OpenClawPluginApi) {
|
|
14
|
+
const config = resolveClawClampConfig(api.pluginConfig);
|
|
15
|
+
const stateDir = api.runtime.state.resolveStateDir();
|
|
16
|
+
const service = createClawClampService({ api, config, stateDir });
|
|
17
|
+
|
|
18
|
+
api.on(
|
|
19
|
+
"before_tool_call",
|
|
20
|
+
async (event, ctx) => service.handleBeforeToolCall(event, ctx),
|
|
21
|
+
{ priority: -10 },
|
|
22
|
+
);
|
|
23
|
+
api.on("after_tool_call", async (event, ctx) => service.handleAfterToolCall(event, ctx));
|
|
24
|
+
|
|
25
|
+
const assetsDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "assets");
|
|
26
|
+
const gatewayToken =
|
|
27
|
+
typeof api.config.gateway?.auth?.token === "string" ? api.config.gateway.auth.token : undefined;
|
|
28
|
+
api.registerHttpRoute({
|
|
29
|
+
path: "/plugins/clawclamp",
|
|
30
|
+
auth: "plugin",
|
|
31
|
+
match: "prefix",
|
|
32
|
+
handler: createClawClampHttpHandler({
|
|
33
|
+
stateDir,
|
|
34
|
+
config,
|
|
35
|
+
assetsDir,
|
|
36
|
+
gatewayToken,
|
|
37
|
+
onPolicyUpdate: async () => {
|
|
38
|
+
await service.resetCedarling();
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plusplus7/clawclamp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenClaw Cedar authorization guard with audit UI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@janssenproject/cedarling_wasm": "1.7.0"
|
|
8
|
+
},
|
|
9
|
+
"openclaw": {
|
|
10
|
+
"extensions": [
|
|
11
|
+
"./index.ts"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"vitest": "^4.0.18"
|
|
16
|
+
}
|
|
17
|
+
}
|