@denial-web/clawguard 0.1.2 → 0.1.3
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/package.json +1 -1
- package/scripts/capture-demo.js +2 -1
- package/web/app.js +51 -0
- package/web/index.html +11 -0
- package/web/styles.css +39 -2
package/package.json
CHANGED
package/scripts/capture-demo.js
CHANGED
|
@@ -89,7 +89,8 @@ async function main() {
|
|
|
89
89
|
await pause(250);
|
|
90
90
|
await dependencyRisk.click();
|
|
91
91
|
await page.getByRole("heading", { name: "Dependency Risk" }).waitFor();
|
|
92
|
-
await page.
|
|
92
|
+
await page.getByRole("heading", { name: "Block", exact: true }).waitFor();
|
|
93
|
+
await page.getByRole("heading", { name: "Install blocked" }).waitFor();
|
|
93
94
|
await pause(700);
|
|
94
95
|
|
|
95
96
|
await page.screenshot({
|
package/web/app.js
CHANGED
|
@@ -37,6 +37,9 @@ const elements = {
|
|
|
37
37
|
level: document.querySelector("#level"),
|
|
38
38
|
decision: document.querySelector("#decision"),
|
|
39
39
|
reason: document.querySelector("#reason"),
|
|
40
|
+
installVerdict: document.querySelector("#install-verdict"),
|
|
41
|
+
installMessage: document.querySelector("#install-message"),
|
|
42
|
+
installCommand: document.querySelector("#install-command"),
|
|
40
43
|
actions: document.querySelector("#actions"),
|
|
41
44
|
critical: document.querySelector("#critical-count"),
|
|
42
45
|
high: document.querySelector("#high-count"),
|
|
@@ -235,9 +238,36 @@ function renderResult(result) {
|
|
|
235
238
|
elements.downloadHtml.textContent = "Download HTML";
|
|
236
239
|
elements.downloadHtml.disabled = false;
|
|
237
240
|
|
|
241
|
+
renderInstallGate(result);
|
|
238
242
|
renderFindings(scan.findings ?? []);
|
|
239
243
|
}
|
|
240
244
|
|
|
245
|
+
function renderInstallGate(result) {
|
|
246
|
+
const scan = result.scan;
|
|
247
|
+
const policy = scan.policy ?? {};
|
|
248
|
+
const decision = policy.decision ?? "allow";
|
|
249
|
+
const target = installTargetFor(result);
|
|
250
|
+
const installName = safeInstallName(result.displayTarget ?? "skill");
|
|
251
|
+
const command = `npx @denial-web/clawguard install ${target} --to ./.agents/skills --name ${installName} --policy ${policy.preset ?? elements.policy.value}`;
|
|
252
|
+
|
|
253
|
+
elements.installCommand.textContent = command;
|
|
254
|
+
|
|
255
|
+
if (decision === "allow") {
|
|
256
|
+
elements.installVerdict.textContent = "Install allowed";
|
|
257
|
+
elements.installMessage.textContent = "ClawGuard would copy this skill into the destination after the policy gate passes.";
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (decision === "block") {
|
|
262
|
+
elements.installVerdict.textContent = "Install blocked";
|
|
263
|
+
elements.installMessage.textContent = "ClawGuard would stop before copying files. Review the findings before trusting this skill.";
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
elements.installVerdict.textContent = "Install paused";
|
|
268
|
+
elements.installMessage.textContent = "ClawGuard would require review, sandboxing, or approval before copying files.";
|
|
269
|
+
}
|
|
270
|
+
|
|
241
271
|
function renderFindings(findings) {
|
|
242
272
|
if (findings.length === 0) {
|
|
243
273
|
elements.findings.className = "findings empty-state";
|
|
@@ -342,6 +372,27 @@ function safeFilename(value) {
|
|
|
342
372
|
.slice(0, 80) || "scan";
|
|
343
373
|
}
|
|
344
374
|
|
|
375
|
+
function safeInstallName(value) {
|
|
376
|
+
return String(value || "skill")
|
|
377
|
+
.toLowerCase()
|
|
378
|
+
.replace(/[^a-z0-9_.-]/g, "-")
|
|
379
|
+
.replace(/-+/g, "-")
|
|
380
|
+
.replace(/^-|-$/g, "")
|
|
381
|
+
.slice(0, 60) || "skill";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function installTargetFor(result) {
|
|
385
|
+
if (result.source === "example" && result.example?.path) {
|
|
386
|
+
return result.example.path;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (result.source === "folder") {
|
|
390
|
+
return "./uploaded-skill";
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return "./pasted-skill";
|
|
394
|
+
}
|
|
395
|
+
|
|
345
396
|
async function fetchJson(url, options = {}) {
|
|
346
397
|
const response = await fetch(url, {
|
|
347
398
|
headers: {
|
package/web/index.html
CHANGED
|
@@ -75,6 +75,17 @@
|
|
|
75
75
|
</div>
|
|
76
76
|
</section>
|
|
77
77
|
|
|
78
|
+
<section class="install-panel" aria-label="Pre-install gate">
|
|
79
|
+
<div>
|
|
80
|
+
<p class="eyebrow">Pre-Install Gate</p>
|
|
81
|
+
<h2 id="install-verdict">Waiting for scan</h2>
|
|
82
|
+
<p id="install-message">Run a scan to see whether ClawGuard would install, pause, or block before files are trusted.</p>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="command-box">
|
|
85
|
+
<code id="install-command">npx @denial-web/clawguard install ./skill --to ./.agents/skills --policy governed</code>
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
|
|
78
89
|
<section class="metrics" aria-label="Finding summary">
|
|
79
90
|
<div><span>Critical</span><strong id="critical-count">0</strong></div>
|
|
80
91
|
<div><span>High</span><strong id="high-count">0</strong></div>
|
package/web/styles.css
CHANGED
|
@@ -264,7 +264,7 @@ input[type="file"] {
|
|
|
264
264
|
align-items: stretch;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
.score-ring, .decision, .metrics div, .metadata-grid div, .finding-card, .empty-state {
|
|
267
|
+
.score-ring, .decision, .install-panel, .metrics div, .metadata-grid div, .finding-card, .empty-state {
|
|
268
268
|
border: 1px solid var(--line);
|
|
269
269
|
border-radius: 8px;
|
|
270
270
|
background: var(--panel);
|
|
@@ -302,6 +302,43 @@ input[type="file"] {
|
|
|
302
302
|
text-transform: uppercase;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
.install-panel {
|
|
306
|
+
display: grid;
|
|
307
|
+
grid-template-columns: minmax(220px, 0.8fr) minmax(0, 1.2fr);
|
|
308
|
+
gap: 14px;
|
|
309
|
+
align-items: stretch;
|
|
310
|
+
padding: 16px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.install-panel h2 {
|
|
314
|
+
margin-top: 5px;
|
|
315
|
+
font-size: 24px;
|
|
316
|
+
text-transform: uppercase;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#install-message {
|
|
320
|
+
margin-top: 6px;
|
|
321
|
+
color: var(--muted);
|
|
322
|
+
line-height: 1.4;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.command-box {
|
|
326
|
+
min-width: 0;
|
|
327
|
+
display: grid;
|
|
328
|
+
align-items: center;
|
|
329
|
+
border: 1px solid var(--line);
|
|
330
|
+
border-radius: 8px;
|
|
331
|
+
background: var(--field);
|
|
332
|
+
padding: 12px;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.command-box code {
|
|
336
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
337
|
+
font-size: 13px;
|
|
338
|
+
line-height: 1.45;
|
|
339
|
+
overflow-wrap: anywhere;
|
|
340
|
+
}
|
|
341
|
+
|
|
305
342
|
.action-tags {
|
|
306
343
|
display: flex;
|
|
307
344
|
flex-wrap: wrap;
|
|
@@ -422,7 +459,7 @@ input[type="file"] {
|
|
|
422
459
|
}
|
|
423
460
|
|
|
424
461
|
@media (max-width: 900px) {
|
|
425
|
-
.workbench, .score-panel {
|
|
462
|
+
.workbench, .score-panel, .install-panel {
|
|
426
463
|
grid-template-columns: 1fr;
|
|
427
464
|
}
|
|
428
465
|
|