@waratahlabs/canopy 0.5.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 +323 -0
- package/SKILL.md +97 -0
- package/demo/data/openclaw-demo.tc.json +397 -0
- package/demo/data/openclaw-nemoclaw-demo.tc.json +475 -0
- package/demo/index.html +556 -0
- package/dist/adapters/a2a.d.ts +82 -0
- package/dist/adapters/a2a.d.ts.map +1 -0
- package/dist/adapters/a2a.js +215 -0
- package/dist/adapters/a2a.js.map +1 -0
- package/dist/adapters/a2a.test.d.ts +2 -0
- package/dist/adapters/a2a.test.d.ts.map +1 -0
- package/dist/adapters/a2a.test.js +196 -0
- package/dist/adapters/a2a.test.js.map +1 -0
- package/dist/adapters/langfuse.d.ts +28 -0
- package/dist/adapters/langfuse.d.ts.map +1 -0
- package/dist/adapters/langfuse.js +192 -0
- package/dist/adapters/langfuse.js.map +1 -0
- package/dist/adapters/n8n.d.ts +4 -0
- package/dist/adapters/n8n.d.ts.map +1 -0
- package/dist/adapters/n8n.js +89 -0
- package/dist/adapters/n8n.js.map +1 -0
- package/dist/adapters/openclaw.d.ts +58 -0
- package/dist/adapters/openclaw.d.ts.map +1 -0
- package/dist/adapters/openclaw.js +275 -0
- package/dist/adapters/openclaw.js.map +1 -0
- package/dist/adapters/types.d.ts +33 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/aivss/atlas/coverage-matrix.d.ts +12 -0
- package/dist/aivss/atlas/coverage-matrix.d.ts.map +1 -0
- package/dist/aivss/atlas/coverage-matrix.js +272 -0
- package/dist/aivss/atlas/coverage-matrix.js.map +1 -0
- package/dist/aivss/atlas/loader.d.ts +28 -0
- package/dist/aivss/atlas/loader.d.ts.map +1 -0
- package/dist/aivss/atlas/loader.js +126 -0
- package/dist/aivss/atlas/loader.js.map +1 -0
- package/dist/aivss/atlas/loader.test.d.ts +11 -0
- package/dist/aivss/atlas/loader.test.d.ts.map +1 -0
- package/dist/aivss/atlas/loader.test.js +111 -0
- package/dist/aivss/atlas/loader.test.js.map +1 -0
- package/dist/aivss/atlas/resolve-path.d.ts +34 -0
- package/dist/aivss/atlas/resolve-path.d.ts.map +1 -0
- package/dist/aivss/atlas/resolve-path.js +58 -0
- package/dist/aivss/atlas/resolve-path.js.map +1 -0
- package/dist/aivss/batch/batch-builder.d.ts +11 -0
- package/dist/aivss/batch/batch-builder.d.ts.map +1 -0
- package/dist/aivss/batch/batch-builder.js +75 -0
- package/dist/aivss/batch/batch-builder.js.map +1 -0
- package/dist/aivss/index.d.ts +21 -0
- package/dist/aivss/index.d.ts.map +1 -0
- package/dist/aivss/index.js +19 -0
- package/dist/aivss/index.js.map +1 -0
- package/dist/aivss/knowledge-base/kb-client.d.ts +9 -0
- package/dist/aivss/knowledge-base/kb-client.d.ts.map +1 -0
- package/dist/aivss/knowledge-base/kb-client.js +139 -0
- package/dist/aivss/knowledge-base/kb-client.js.map +1 -0
- package/dist/aivss/knowledge-base/kb-types.d.ts +21 -0
- package/dist/aivss/knowledge-base/kb-types.d.ts.map +1 -0
- package/dist/aivss/knowledge-base/kb-types.js +2 -0
- package/dist/aivss/knowledge-base/kb-types.js.map +1 -0
- package/dist/aivss/knowledge-base/search-builder.d.ts +7 -0
- package/dist/aivss/knowledge-base/search-builder.d.ts.map +1 -0
- package/dist/aivss/knowledge-base/search-builder.js +119 -0
- package/dist/aivss/knowledge-base/search-builder.js.map +1 -0
- package/dist/aivss/metrics/aa.d.ts +3 -0
- package/dist/aivss/metrics/aa.d.ts.map +1 -0
- package/dist/aivss/metrics/aa.js +26 -0
- package/dist/aivss/metrics/aa.js.map +1 -0
- package/dist/aivss/metrics/ad.d.ts +3 -0
- package/dist/aivss/metrics/ad.d.ts.map +1 -0
- package/dist/aivss/metrics/ad.js +30 -0
- package/dist/aivss/metrics/ad.js.map +1 -0
- package/dist/aivss/metrics/cs.d.ts +3 -0
- package/dist/aivss/metrics/cs.d.ts.map +1 -0
- package/dist/aivss/metrics/cs.js +50 -0
- package/dist/aivss/metrics/cs.js.map +1 -0
- package/dist/aivss/metrics/dc.d.ts +3 -0
- package/dist/aivss/metrics/dc.d.ts.map +1 -0
- package/dist/aivss/metrics/dc.js +30 -0
- package/dist/aivss/metrics/dc.js.map +1 -0
- package/dist/aivss/metrics/ds.d.ts +3 -0
- package/dist/aivss/metrics/ds.d.ts.map +1 -0
- package/dist/aivss/metrics/ds.js +26 -0
- package/dist/aivss/metrics/ds.js.map +1 -0
- package/dist/aivss/metrics/ei.d.ts +3 -0
- package/dist/aivss/metrics/ei.d.ts.map +1 -0
- package/dist/aivss/metrics/ei.js +30 -0
- package/dist/aivss/metrics/ei.js.map +1 -0
- package/dist/aivss/metrics/gv.d.ts +3 -0
- package/dist/aivss/metrics/gv.d.ts.map +1 -0
- package/dist/aivss/metrics/gv.js +34 -0
- package/dist/aivss/metrics/gv.js.map +1 -0
- package/dist/aivss/metrics/ll.d.ts +3 -0
- package/dist/aivss/metrics/ll.d.ts.map +1 -0
- package/dist/aivss/metrics/ll.js +30 -0
- package/dist/aivss/metrics/ll.js.map +1 -0
- package/dist/aivss/metrics/mr.d.ts +3 -0
- package/dist/aivss/metrics/mr.d.ts.map +1 -0
- package/dist/aivss/metrics/mr.js +26 -0
- package/dist/aivss/metrics/mr.js.map +1 -0
- package/dist/aivss/metrics/shared.d.ts +22 -0
- package/dist/aivss/metrics/shared.d.ts.map +1 -0
- package/dist/aivss/metrics/shared.js +70 -0
- package/dist/aivss/metrics/shared.js.map +1 -0
- package/dist/aivss/prompts/base-system-prompt.d.ts +2 -0
- package/dist/aivss/prompts/base-system-prompt.d.ts.map +1 -0
- package/dist/aivss/prompts/base-system-prompt.js +31 -0
- package/dist/aivss/prompts/base-system-prompt.js.map +1 -0
- package/dist/aivss/prompts/prompt-composer.d.ts +13 -0
- package/dist/aivss/prompts/prompt-composer.d.ts.map +1 -0
- package/dist/aivss/prompts/prompt-composer.js +21 -0
- package/dist/aivss/prompts/prompt-composer.js.map +1 -0
- package/dist/aivss/scoring/a2a-corpus.test.d.ts +28 -0
- package/dist/aivss/scoring/a2a-corpus.test.d.ts.map +1 -0
- package/dist/aivss/scoring/a2a-corpus.test.js +109 -0
- package/dist/aivss/scoring/a2a-corpus.test.js.map +1 -0
- package/dist/aivss/scoring/determinism.test.d.ts +16 -0
- package/dist/aivss/scoring/determinism.test.d.ts.map +1 -0
- package/dist/aivss/scoring/determinism.test.js +169 -0
- package/dist/aivss/scoring/determinism.test.js.map +1 -0
- package/dist/aivss/scoring/heuristic-scorer.d.ts +21 -0
- package/dist/aivss/scoring/heuristic-scorer.d.ts.map +1 -0
- package/dist/aivss/scoring/heuristic-scorer.js +299 -0
- package/dist/aivss/scoring/heuristic-scorer.js.map +1 -0
- package/dist/aivss/scoring/heuristic-scorer.test.d.ts +8 -0
- package/dist/aivss/scoring/heuristic-scorer.test.d.ts.map +1 -0
- package/dist/aivss/scoring/heuristic-scorer.test.js +150 -0
- package/dist/aivss/scoring/heuristic-scorer.test.js.map +1 -0
- package/dist/aivss/scoring/scoring-mode.d.ts +48 -0
- package/dist/aivss/scoring/scoring-mode.d.ts.map +1 -0
- package/dist/aivss/scoring/scoring-mode.js +64 -0
- package/dist/aivss/scoring/scoring-mode.js.map +1 -0
- package/dist/aivss/scoring/v4-engine.d.ts +15 -0
- package/dist/aivss/scoring/v4-engine.d.ts.map +1 -0
- package/dist/aivss/scoring/v4-engine.js +84 -0
- package/dist/aivss/scoring/v4-engine.js.map +1 -0
- package/dist/aivss/scoring/v4-engine.test.d.ts +16 -0
- package/dist/aivss/scoring/v4-engine.test.d.ts.map +1 -0
- package/dist/aivss/scoring/v4-engine.test.js +156 -0
- package/dist/aivss/scoring/v4-engine.test.js.map +1 -0
- package/dist/aivss/types.d.ts +92 -0
- package/dist/aivss/types.d.ts.map +1 -0
- package/dist/aivss/types.js +25 -0
- package/dist/aivss/types.js.map +1 -0
- package/dist/baseline/baseline.test.d.ts +2 -0
- package/dist/baseline/baseline.test.d.ts.map +1 -0
- package/dist/baseline/baseline.test.js +219 -0
- package/dist/baseline/baseline.test.js.map +1 -0
- package/dist/baseline/diff.d.ts +42 -0
- package/dist/baseline/diff.d.ts.map +1 -0
- package/dist/baseline/diff.js +124 -0
- package/dist/baseline/diff.js.map +1 -0
- package/dist/baseline/store.d.ts +51 -0
- package/dist/baseline/store.d.ts.map +1 -0
- package/dist/baseline/store.js +92 -0
- package/dist/baseline/store.js.map +1 -0
- package/dist/batch/anthropic-batch-runner.d.ts +50 -0
- package/dist/batch/anthropic-batch-runner.d.ts.map +1 -0
- package/dist/batch/anthropic-batch-runner.js +91 -0
- package/dist/batch/anthropic-batch-runner.js.map +1 -0
- package/dist/batch/bedrock-batch-runner.d.ts +90 -0
- package/dist/batch/bedrock-batch-runner.d.ts.map +1 -0
- package/dist/batch/bedrock-batch-runner.js +199 -0
- package/dist/batch/bedrock-batch-runner.js.map +1 -0
- package/dist/batch/providers/anthropic.d.ts +55 -0
- package/dist/batch/providers/anthropic.d.ts.map +1 -0
- package/dist/batch/providers/anthropic.js +103 -0
- package/dist/batch/providers/anthropic.js.map +1 -0
- package/dist/batch/providers/bedrock.d.ts +31 -0
- package/dist/batch/providers/bedrock.d.ts.map +1 -0
- package/dist/batch/providers/bedrock.js +42 -0
- package/dist/batch/providers/bedrock.js.map +1 -0
- package/dist/batch/providers/harness.d.ts +36 -0
- package/dist/batch/providers/harness.d.ts.map +1 -0
- package/dist/batch/providers/harness.js +65 -0
- package/dist/batch/providers/harness.js.map +1 -0
- package/dist/batch/providers/harness.test.d.ts +7 -0
- package/dist/batch/providers/harness.test.d.ts.map +1 -0
- package/dist/batch/providers/harness.test.js +84 -0
- package/dist/batch/providers/harness.test.js.map +1 -0
- package/dist/batch/providers/index.d.ts +82 -0
- package/dist/batch/providers/index.d.ts.map +1 -0
- package/dist/batch/providers/index.js +82 -0
- package/dist/batch/providers/index.js.map +1 -0
- package/dist/batch/providers/ollama.d.ts +68 -0
- package/dist/batch/providers/ollama.d.ts.map +1 -0
- package/dist/batch/providers/ollama.js +108 -0
- package/dist/batch/providers/ollama.js.map +1 -0
- package/dist/batch/response-parser.d.ts +45 -0
- package/dist/batch/response-parser.d.ts.map +1 -0
- package/dist/batch/response-parser.js +161 -0
- package/dist/batch/response-parser.js.map +1 -0
- package/dist/batch/types.d.ts +87 -0
- package/dist/batch/types.d.ts.map +1 -0
- package/dist/batch/types.js +8 -0
- package/dist/batch/types.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1037 -0
- package/dist/cli.js.map +1 -0
- package/dist/examples/a2a-assessment.d.ts +20 -0
- package/dist/examples/a2a-assessment.d.ts.map +1 -0
- package/dist/examples/a2a-assessment.js +115 -0
- package/dist/examples/a2a-assessment.js.map +1 -0
- package/dist/examples/batch-assessment.d.ts +18 -0
- package/dist/examples/batch-assessment.d.ts.map +1 -0
- package/dist/examples/batch-assessment.js +112 -0
- package/dist/examples/batch-assessment.js.map +1 -0
- package/dist/examples/batch-submission-flow.d.ts +2 -0
- package/dist/examples/batch-submission-flow.d.ts.map +1 -0
- package/dist/examples/batch-submission-flow.js +279 -0
- package/dist/examples/batch-submission-flow.js.map +1 -0
- package/dist/examples/demo-agent-definition.json +58 -0
- package/dist/examples/langfuse-assessment.d.ts +2 -0
- package/dist/examples/langfuse-assessment.d.ts.map +1 -0
- package/dist/examples/langfuse-assessment.js +46 -0
- package/dist/examples/langfuse-assessment.js.map +1 -0
- package/dist/examples/model-eval.d.ts +25 -0
- package/dist/examples/model-eval.d.ts.map +1 -0
- package/dist/examples/model-eval.js +94 -0
- package/dist/examples/model-eval.js.map +1 -0
- package/dist/examples/openclaw-assessment.d.ts +17 -0
- package/dist/examples/openclaw-assessment.d.ts.map +1 -0
- package/dist/examples/openclaw-assessment.js +79 -0
- package/dist/examples/openclaw-assessment.js.map +1 -0
- package/dist/examples/simple-assessment.d.ts +2 -0
- package/dist/examples/simple-assessment.d.ts.map +1 -0
- package/dist/examples/simple-assessment.js +146 -0
- package/dist/examples/simple-assessment.js.map +1 -0
- package/dist/examples/threat-model-demo.d.ts +2 -0
- package/dist/examples/threat-model-demo.d.ts.map +1 -0
- package/dist/examples/threat-model-demo.js +103 -0
- package/dist/examples/threat-model-demo.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +155 -0
- package/dist/index.js.map +1 -0
- package/dist/output/likec4-generator.d.ts +43 -0
- package/dist/output/likec4-generator.d.ts.map +1 -0
- package/dist/output/likec4-generator.js +287 -0
- package/dist/output/likec4-generator.js.map +1 -0
- package/dist/output/slot-filler.d.ts +6 -0
- package/dist/output/slot-filler.d.ts.map +1 -0
- package/dist/output/slot-filler.js +71 -0
- package/dist/output/slot-filler.js.map +1 -0
- package/dist/output/tc-assembler.d.ts +30 -0
- package/dist/output/tc-assembler.d.ts.map +1 -0
- package/dist/output/tc-assembler.js +227 -0
- package/dist/output/tc-assembler.js.map +1 -0
- package/dist/output/tc-types.d.ts +41 -0
- package/dist/output/tc-types.d.ts.map +1 -0
- package/dist/output/tc-types.js +5 -0
- package/dist/output/tc-types.js.map +1 -0
- package/dist/output/tc-validator.test.d.ts +9 -0
- package/dist/output/tc-validator.test.d.ts.map +1 -0
- package/dist/output/tc-validator.test.js +194 -0
- package/dist/output/tc-validator.test.js.map +1 -0
- package/dist/providers/anthropic.d.ts +9 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +30 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/bedrock.d.ts +9 -0
- package/dist/providers/bedrock.d.ts.map +1 -0
- package/dist/providers/bedrock.js +38 -0
- package/dist/providers/bedrock.js.map +1 -0
- package/dist/providers/index.d.ts +6 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +14 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/types.d.ts +12 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +5 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/references/acsc-risks.d.ts +18 -0
- package/dist/references/acsc-risks.d.ts.map +1 -0
- package/dist/references/acsc-risks.js +232 -0
- package/dist/references/acsc-risks.js.map +1 -0
- package/dist/references/aicm.d.ts +41 -0
- package/dist/references/aicm.d.ts.map +1 -0
- package/dist/references/aicm.js +447 -0
- package/dist/references/aicm.js.map +1 -0
- package/dist/references/dstg-meaid.d.ts +59 -0
- package/dist/references/dstg-meaid.d.ts.map +1 -0
- package/dist/references/dstg-meaid.js +352 -0
- package/dist/references/dstg-meaid.js.map +1 -0
- package/dist/references/index.d.ts +20 -0
- package/dist/references/index.d.ts.map +1 -0
- package/dist/references/index.js +31 -0
- package/dist/references/index.js.map +1 -0
- package/dist/references/nemoclaw.d.ts +48 -0
- package/dist/references/nemoclaw.d.ts.map +1 -0
- package/dist/references/nemoclaw.js +140 -0
- package/dist/references/nemoclaw.js.map +1 -0
- package/dist/references/nist-ai-rmf.d.ts +31 -0
- package/dist/references/nist-ai-rmf.d.ts.map +1 -0
- package/dist/references/nist-ai-rmf.js +627 -0
- package/dist/references/nist-ai-rmf.js.map +1 -0
- package/dist/references/owasp-agentic-top10.d.ts +50 -0
- package/dist/references/owasp-agentic-top10.d.ts.map +1 -0
- package/dist/references/owasp-agentic-top10.js +202 -0
- package/dist/references/owasp-agentic-top10.js.map +1 -0
- package/dist/references/registry-parity.test.d.ts +17 -0
- package/dist/references/registry-parity.test.d.ts.map +1 -0
- package/dist/references/registry-parity.test.js +87 -0
- package/dist/references/registry-parity.test.js.map +1 -0
- package/dist/references/registry.d.ts +90 -0
- package/dist/references/registry.d.ts.map +1 -0
- package/dist/references/registry.js +29 -0
- package/dist/references/registry.js.map +1 -0
- package/dist/tools/model-eval.d.ts +97 -0
- package/dist/tools/model-eval.d.ts.map +1 -0
- package/dist/tools/model-eval.js +307 -0
- package/dist/tools/model-eval.js.map +1 -0
- package/package.json +105 -0
- package/schemas/threat-composer-v1.schema.json +966 -0
- package/vendor/atlas-release/ATLAS-2026.07.yaml +17877 -0
- package/vendor/atlas-release/README.md +41 -0
package/demo/index.html
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Canopy — AI Threat Model Explorer</title>
|
|
7
|
+
<meta name="description" content="Interactive threat model for OpenClaw personal AI assistant. Generated by Canopy using OWASP AIVSS V4, MITRE ATLAS, and ASD/ACSC frameworks.">
|
|
8
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
--bg: #0f0f0f;
|
|
11
|
+
--surface: #1a1a1a;
|
|
12
|
+
--border: #2a2a2a;
|
|
13
|
+
--text: #e0e0e0;
|
|
14
|
+
--muted: #888;
|
|
15
|
+
--accent: #3b82f6;
|
|
16
|
+
--critical: #ef4444;
|
|
17
|
+
--high: #f97316;
|
|
18
|
+
--medium: #eab308;
|
|
19
|
+
--low: #22c55e;
|
|
20
|
+
--none: #6b7280;
|
|
21
|
+
--font-mono: "SF Mono", "Fira Code", "Cascadia Code", monospace;
|
|
22
|
+
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
23
|
+
}
|
|
24
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
25
|
+
body { background: var(--bg); color: var(--text); font-family: var(--font-sans); font-size: 14px; line-height: 1.6; }
|
|
26
|
+
|
|
27
|
+
header { border-bottom: 1px solid var(--border); padding: 20px 32px; display: flex; align-items: center; gap: 16px; }
|
|
28
|
+
.logo { font-size: 20px; font-weight: 700; letter-spacing: -0.5px; color: #fff; }
|
|
29
|
+
.logo span { color: var(--accent); }
|
|
30
|
+
.tagline { color: var(--muted); font-size: 13px; }
|
|
31
|
+
.header-meta { margin-left: auto; display: flex; gap: 12px; align-items: center; }
|
|
32
|
+
.badge { background: var(--surface); border: 1px solid var(--border); border-radius: 4px; padding: 3px 8px; font-size: 11px; color: var(--muted); font-family: var(--font-mono); }
|
|
33
|
+
|
|
34
|
+
/* View toggle */
|
|
35
|
+
.view-toggle { display: flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; margin-bottom: 20px; }
|
|
36
|
+
.view-tab { flex: 1; padding: 8px 16px; border: none; background: var(--surface); color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 500; transition: background 0.15s, color 0.15s; text-align: center; }
|
|
37
|
+
.view-tab:not(:last-child) { border-right: 1px solid var(--border); }
|
|
38
|
+
.view-tab.active { background: #1e3a5f; color: #93c5fd; }
|
|
39
|
+
.view-tab .tab-badge { display: inline-block; margin-left: 6px; background: #052040; border: 1px solid #1e3a5f; border-radius: 3px; padding: 0 5px; font-size: 10px; font-family: var(--font-mono); color: var(--muted); vertical-align: middle; }
|
|
40
|
+
.view-tab.active .tab-badge { background: #0f2a4a; color: #60a5fa; }
|
|
41
|
+
|
|
42
|
+
.layout { display: grid; grid-template-columns: 280px 1fr; min-height: calc(100vh - 61px); }
|
|
43
|
+
|
|
44
|
+
aside { border-right: 1px solid var(--border); padding: 20px 0; }
|
|
45
|
+
.sidebar-section { padding: 0 20px 16px; border-bottom: 1px solid var(--border); margin-bottom: 16px; }
|
|
46
|
+
.sidebar-section:last-child { border-bottom: none; }
|
|
47
|
+
.sidebar-label { font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; }
|
|
48
|
+
.filter-btn { display: flex; align-items: center; gap: 8px; width: 100%; padding: 6px 8px; border-radius: 4px; border: none; background: transparent; color: var(--text); cursor: pointer; font-size: 13px; text-align: left; transition: background 0.1s; }
|
|
49
|
+
.filter-btn:hover, .filter-btn.active { background: var(--surface); }
|
|
50
|
+
.filter-btn .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
51
|
+
.filter-btn .count { margin-left: auto; color: var(--muted); font-size: 11px; font-family: var(--font-mono); }
|
|
52
|
+
|
|
53
|
+
main { padding: 24px 32px; }
|
|
54
|
+
.system-info { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 16px 20px; margin-bottom: 24px; display: flex; gap: 32px; flex-wrap: wrap; }
|
|
55
|
+
.info-item { }
|
|
56
|
+
.info-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 2px; }
|
|
57
|
+
.info-value { font-size: 13px; font-family: var(--font-mono); }
|
|
58
|
+
|
|
59
|
+
.threat-list { display: flex; flex-direction: column; gap: 12px; }
|
|
60
|
+
.threat-card { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; cursor: pointer; transition: border-color 0.15s; }
|
|
61
|
+
.threat-card:hover { border-color: #444; }
|
|
62
|
+
.threat-card.expanded { border-color: var(--accent); }
|
|
63
|
+
.threat-header { padding: 14px 16px; display: flex; align-items: flex-start; gap: 12px; }
|
|
64
|
+
.sev-pill { flex-shrink: 0; padding: 2px 8px; border-radius: 3px; font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; margin-top: 2px; }
|
|
65
|
+
.sev-Critical { background: #7f1d1d; color: var(--critical); }
|
|
66
|
+
.sev-High { background: #431407; color: var(--high); }
|
|
67
|
+
.sev-Medium { background: #422006; color: var(--medium); }
|
|
68
|
+
.sev-Low { background: #052e16; color: var(--low); }
|
|
69
|
+
.threat-title { flex: 1; font-size: 14px; font-weight: 500; line-height: 1.4; }
|
|
70
|
+
.threat-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
|
|
71
|
+
.tag { background: #252525; border: 1px solid var(--border); border-radius: 3px; padding: 1px 6px; font-size: 10px; color: var(--muted); font-family: var(--font-mono); }
|
|
72
|
+
.tag.atlas { color: #60a5fa; border-color: #1e3a5f; background: #0f2040; }
|
|
73
|
+
.tag.metric { color: #a78bfa; border-color: #2e1f5e; background: #1a1030; }
|
|
74
|
+
.tag.acsc { color: #34d399; border-color: #064e3b; background: #042f23; }
|
|
75
|
+
.tag.mitigated { color: #4ade80; border-color: #14532d; background: #052e16; }
|
|
76
|
+
.tag.residual { color: #f97316; border-color: #431407; background: #1f0d03; }
|
|
77
|
+
.tag.partial { color: #eab308; border-color: #422006; background: #1a0f02; }
|
|
78
|
+
|
|
79
|
+
/* Severity delta arrow */
|
|
80
|
+
.sev-delta { display: flex; align-items: center; gap: 6px; flex-shrink: 0; margin-top: 2px; }
|
|
81
|
+
.sev-delta .arrow { color: var(--muted); font-size: 12px; }
|
|
82
|
+
.delta-reduced .arrow { color: #4ade80; }
|
|
83
|
+
.delta-unchanged .arrow { color: var(--muted); }
|
|
84
|
+
|
|
85
|
+
.threat-body { padding: 0 16px 16px; border-top: 1px solid var(--border); display: none; }
|
|
86
|
+
.threat-card.expanded .threat-body { display: block; }
|
|
87
|
+
.threat-section { margin-top: 14px; }
|
|
88
|
+
.threat-section-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin-bottom: 6px; }
|
|
89
|
+
.threat-section-content { font-size: 13px; color: #ccc; line-height: 1.6; }
|
|
90
|
+
|
|
91
|
+
/* NeMo Claw control box */
|
|
92
|
+
.nemo-control-box { margin-top: 14px; background: #0a1a0a; border: 1px solid #1a3a1a; border-radius: 4px; padding: 10px 14px; }
|
|
93
|
+
.nemo-control-label { font-size: 10px; font-weight: 600; text-transform: uppercase; color: #4ade80; letter-spacing: 0.08em; margin-bottom: 4px; }
|
|
94
|
+
.nemo-control-text { font-size: 12px; color: #a0c8a0; font-family: var(--font-mono); line-height: 1.5; }
|
|
95
|
+
.nemo-control-residual { background: #1a0a00; border-color: #3a1800; }
|
|
96
|
+
.nemo-control-residual .nemo-control-label { color: #f97316; }
|
|
97
|
+
.nemo-control-residual .nemo-control-text { color: #c8a080; }
|
|
98
|
+
|
|
99
|
+
.mitigation-list { margin-top: 24px; }
|
|
100
|
+
.mitigation-card { background: #0f1f10; border: 1px solid #1a3a1a; border-radius: 6px; padding: 12px 16px; margin-bottom: 8px; }
|
|
101
|
+
.mitigation-label { font-size: 10px; font-weight: 600; text-transform: uppercase; color: #4ade80; letter-spacing: 0.08em; margin-bottom: 6px; }
|
|
102
|
+
.mitigation-text { font-size: 13px; color: #ccc; }
|
|
103
|
+
|
|
104
|
+
/* Comparison summary banner */
|
|
105
|
+
.comparison-banner { background: #0a1a0a; border: 1px solid #1a3a1a; border-radius: 6px; padding: 14px 20px; margin-bottom: 20px; display: flex; gap: 32px; flex-wrap: wrap; align-items: center; }
|
|
106
|
+
.comparison-stat { text-align: center; }
|
|
107
|
+
.comparison-stat .stat-num { font-size: 24px; font-weight: 700; font-family: var(--font-mono); }
|
|
108
|
+
.comparison-stat .stat-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 2px; }
|
|
109
|
+
.stat-reduced { color: #4ade80; }
|
|
110
|
+
.stat-unchanged { color: var(--muted); }
|
|
111
|
+
.stat-mitigated { color: #60a5fa; }
|
|
112
|
+
.comparison-note { font-size: 12px; color: var(--muted); line-height: 1.6; max-width: 400px; }
|
|
113
|
+
|
|
114
|
+
.empty { color: var(--muted); text-align: center; padding: 48px; }
|
|
115
|
+
|
|
116
|
+
.framework-strip { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; }
|
|
117
|
+
.fw-chip { display: flex; align-items: center; gap: 6px; background: var(--surface); border: 1px solid var(--border); border-radius: 4px; padding: 4px 10px; font-size: 11px; color: var(--muted); }
|
|
118
|
+
.fw-dot { width: 6px; height: 6px; border-radius: 50%; }
|
|
119
|
+
|
|
120
|
+
footer { border-top: 1px solid var(--border); padding: 16px 32px; display: flex; gap: 16px; align-items: center; }
|
|
121
|
+
footer a { color: var(--muted); text-decoration: none; font-size: 12px; }
|
|
122
|
+
footer a:hover { color: var(--text); }
|
|
123
|
+
.footer-sep { color: var(--border); }
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
|
|
128
|
+
<header>
|
|
129
|
+
<div>
|
|
130
|
+
<div class="logo">🌿 <span>Canopy</span></div>
|
|
131
|
+
<div class="tagline">Multi-framework AI threat modelling</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="header-meta">
|
|
134
|
+
<span class="badge">AIVSS V4</span>
|
|
135
|
+
<span class="badge">ATLAS 5.6.0</span>
|
|
136
|
+
<span class="badge">ASD/ACSC</span>
|
|
137
|
+
</div>
|
|
138
|
+
</header>
|
|
139
|
+
|
|
140
|
+
<div class="layout">
|
|
141
|
+
<aside>
|
|
142
|
+
<div class="sidebar-section">
|
|
143
|
+
<div class="sidebar-label">System</div>
|
|
144
|
+
<button class="filter-btn active" data-filter="all">
|
|
145
|
+
All threats
|
|
146
|
+
<span class="count" id="count-all">—</span>
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="sidebar-section">
|
|
150
|
+
<div class="sidebar-label">Severity</div>
|
|
151
|
+
<button class="filter-btn" data-filter="sev-Critical">
|
|
152
|
+
<span class="dot" style="background:var(--critical)"></span> Critical
|
|
153
|
+
<span class="count" id="count-Critical">0</span>
|
|
154
|
+
</button>
|
|
155
|
+
<button class="filter-btn" data-filter="sev-High">
|
|
156
|
+
<span class="dot" style="background:var(--high)"></span> High
|
|
157
|
+
<span class="count" id="count-High">0</span>
|
|
158
|
+
</button>
|
|
159
|
+
<button class="filter-btn" data-filter="sev-Medium">
|
|
160
|
+
<span class="dot" style="background:var(--medium)"></span> Medium
|
|
161
|
+
<span class="count" id="count-Medium">0</span>
|
|
162
|
+
</button>
|
|
163
|
+
<button class="filter-btn" data-filter="sev-Low">
|
|
164
|
+
<span class="dot" style="background:var(--low)"></span> Low
|
|
165
|
+
<span class="count" id="count-Low">0</span>
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="sidebar-section">
|
|
169
|
+
<div class="sidebar-label">AIVSS Metric</div>
|
|
170
|
+
<div id="metric-filters"></div>
|
|
171
|
+
</div>
|
|
172
|
+
<!-- NeMo Claw filter panel (only shown in comparison view) -->
|
|
173
|
+
<div class="sidebar-section" id="nemo-filter-section" style="display:none">
|
|
174
|
+
<div class="sidebar-label">NeMo Claw</div>
|
|
175
|
+
<button class="filter-btn" data-filter="nemo-mitigated">
|
|
176
|
+
<span class="dot" style="background:var(--low)"></span> Mitigated
|
|
177
|
+
<span class="count" id="count-mitigated">0</span>
|
|
178
|
+
</button>
|
|
179
|
+
<button class="filter-btn" data-filter="nemo-partial">
|
|
180
|
+
<span class="dot" style="background:var(--medium)"></span> Partial
|
|
181
|
+
<span class="count" id="count-partial">0</span>
|
|
182
|
+
</button>
|
|
183
|
+
<button class="filter-btn" data-filter="nemo-unchanged">
|
|
184
|
+
<span class="dot" style="background:var(--muted)"></span> Unchanged
|
|
185
|
+
<span class="count" id="count-unchanged">0</span>
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
</aside>
|
|
189
|
+
|
|
190
|
+
<main>
|
|
191
|
+
<div class="view-toggle">
|
|
192
|
+
<button class="view-tab active" id="tab-baseline" onclick="switchView('baseline')">
|
|
193
|
+
OpenClaw — Baseline
|
|
194
|
+
<span class="tab-badge" id="tab-baseline-count">—</span>
|
|
195
|
+
</button>
|
|
196
|
+
<button class="view-tab" id="tab-nemoclaw" onclick="switchView('nemoclaw')">
|
|
197
|
+
OpenClaw + NeMo Claw
|
|
198
|
+
<span class="tab-badge" id="tab-nemoclaw-count">—</span>
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div class="framework-strip">
|
|
203
|
+
<div class="fw-chip"><span class="fw-dot" style="background:#3b82f6"></span> OWASP AIVSS V4 — 9 metrics, 39 sub-categories</div>
|
|
204
|
+
<div class="fw-chip"><span class="fw-dot" style="background:#a78bfa"></span> MITRE ATLAS 5.6.0 — adversary TTPs</div>
|
|
205
|
+
<div class="fw-chip"><span class="fw-dot" style="background:#34d399"></span> ASD/ACSC Five Eyes</div>
|
|
206
|
+
<div class="fw-chip" id="nemo-chip" style="display:none"><span class="fw-dot" style="background:#4ade80"></span> NeMo Claw — kernel-level sandbox (alpha)</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<!-- Comparison stats banner (only in NeMo view) -->
|
|
210
|
+
<div class="comparison-banner" id="comparison-banner" style="display:none"></div>
|
|
211
|
+
|
|
212
|
+
<div class="system-info" id="system-info"></div>
|
|
213
|
+
|
|
214
|
+
<div class="threat-list" id="threat-list"></div>
|
|
215
|
+
|
|
216
|
+
<div class="mitigation-list" id="mitigation-list"></div>
|
|
217
|
+
</main>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<footer>
|
|
221
|
+
<a href="https://github.com/waratahlabs/canopy">@waratahlabs/canopy</a>
|
|
222
|
+
<span class="footer-sep">·</span>
|
|
223
|
+
<a href="https://aivss.owasp.org">OWASP AIVSS</a>
|
|
224
|
+
<span class="footer-sep">·</span>
|
|
225
|
+
<a href="https://atlas.mitre.org">MITRE ATLAS</a>
|
|
226
|
+
<span class="footer-sep">·</span>
|
|
227
|
+
<a href="https://www.cyber.gov.au/business-government/secure-design/artificial-intelligence/careful-adoption-of-agentic-ai-services">ASD/ACSC</a>
|
|
228
|
+
<span class="footer-sep">·</span>
|
|
229
|
+
<a href="https://www.secondtalent.com/resources/nemoclaw-vs-openclaw/">NeMo Claw vs OpenClaw</a>
|
|
230
|
+
<span class="footer-sep">·</span>
|
|
231
|
+
<a href="https://github.com/awslabs/threat-composer">AWS Threat Composer</a>
|
|
232
|
+
</footer>
|
|
233
|
+
|
|
234
|
+
<script>
|
|
235
|
+
const METRIC_NAMES = {
|
|
236
|
+
MR: "Model Robustness",
|
|
237
|
+
DS: "Data Security",
|
|
238
|
+
EI: "Ethical Implications",
|
|
239
|
+
DC: "Deployment Config",
|
|
240
|
+
AD: "Agent Delegation",
|
|
241
|
+
AA: "Agentic Autonomy",
|
|
242
|
+
LL: "Lifecycle & Lineage",
|
|
243
|
+
GV: "Governance",
|
|
244
|
+
CS: "Supply Chain"
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const SEV_ORDER = { Critical: 4, High: 3, Medium: 2, Low: 1, None: 0 };
|
|
248
|
+
|
|
249
|
+
let currentView = "baseline";
|
|
250
|
+
let baselineData = null;
|
|
251
|
+
let nemoclawData = null;
|
|
252
|
+
let activeFilter = "all";
|
|
253
|
+
|
|
254
|
+
function getMeta(threat, key) {
|
|
255
|
+
return threat.metadata?.find(m => m.key === key)?.value ?? "";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function activeData() {
|
|
259
|
+
return currentView === "baseline" ? baselineData : nemoclawData;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function renderSystemInfo(data) {
|
|
263
|
+
const el = document.getElementById("system-info");
|
|
264
|
+
const appName = data.applicationInfo?.name ?? "Unknown";
|
|
265
|
+
const threatCount = data.threats?.length ?? 0;
|
|
266
|
+
const mitigationCount = data.mitigations?.length ?? 0;
|
|
267
|
+
const version = data.metadata?.find(m => m.key === "canopy-version")?.value ?? "—";
|
|
268
|
+
const aivss = data.metadata?.find(m => m.key === "aivss-version")?.value ?? "—";
|
|
269
|
+
const generatedAt = data.metadata?.find(m => m.key === "generated-at")?.value ?? "";
|
|
270
|
+
const date = generatedAt ? new Date(generatedAt).toLocaleDateString("en-AU", { dateStyle: "medium" }) : "—";
|
|
271
|
+
const isolationLayers = data.metadata?.find(m => m.key === "isolation-layers")?.value ?? null;
|
|
272
|
+
|
|
273
|
+
el.innerHTML = `
|
|
274
|
+
<div class="info-item"><div class="info-label">System</div><div class="info-value">${appName}</div></div>
|
|
275
|
+
<div class="info-item"><div class="info-label">Threats</div><div class="info-value">${threatCount}</div></div>
|
|
276
|
+
<div class="info-item"><div class="info-label">Mitigations</div><div class="info-value">${mitigationCount}</div></div>
|
|
277
|
+
<div class="info-item"><div class="info-label">Canopy</div><div class="info-value">v${version}</div></div>
|
|
278
|
+
<div class="info-item"><div class="info-label">AIVSS</div><div class="info-value">v${aivss}</div></div>
|
|
279
|
+
<div class="info-item"><div class="info-label">Generated</div><div class="info-value">${date}</div></div>
|
|
280
|
+
${isolationLayers ? `<div class="info-item"><div class="info-label">Isolation</div><div class="info-value" style="color:#4ade80">${isolationLayers}</div></div>` : ""}
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function buildSevPill(sev) {
|
|
285
|
+
return `<span class="sev-pill sev-${sev}">${sev}</span>`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function buildSevDelta(baseline, current) {
|
|
289
|
+
const bOrd = SEV_ORDER[baseline] ?? 2;
|
|
290
|
+
const cOrd = SEV_ORDER[current] ?? 2;
|
|
291
|
+
const deltaClass = cOrd < bOrd ? "delta-reduced" : "delta-unchanged";
|
|
292
|
+
const arrow = cOrd < bOrd ? "↓" : "→";
|
|
293
|
+
return `
|
|
294
|
+
<div class="sev-delta ${deltaClass}">
|
|
295
|
+
${buildSevPill(baseline)}
|
|
296
|
+
<span class="arrow">${arrow}</span>
|
|
297
|
+
${buildSevPill(current)}
|
|
298
|
+
</div>
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function renderThreat(t) {
|
|
303
|
+
const sev = getMeta(t, "aivss-severity") || "Medium";
|
|
304
|
+
const metric = getMeta(t, "aivss-metric");
|
|
305
|
+
const atlasId = getMeta(t, "atlas-techniqueId");
|
|
306
|
+
const atlasName = getMeta(t, "atlas-techniqueName");
|
|
307
|
+
const tactic = getMeta(t, "atlas-tactic");
|
|
308
|
+
const acscFamily = getMeta(t, "acsc-riskFamily");
|
|
309
|
+
|
|
310
|
+
// NeMo Claw fields
|
|
311
|
+
const nemoMitigated = getMeta(t, "nemoclaw-mitigated");
|
|
312
|
+
const nemoControl = getMeta(t, "nemoclaw-control");
|
|
313
|
+
const baselineSev = getMeta(t, "baseline-severity");
|
|
314
|
+
const severityDelta = getMeta(t, "severity-delta");
|
|
315
|
+
const isNemoView = currentView === "nemoclaw";
|
|
316
|
+
|
|
317
|
+
// NeMo Claw status tag
|
|
318
|
+
let nemoTag = "";
|
|
319
|
+
if (isNemoView && nemoMitigated) {
|
|
320
|
+
if (nemoMitigated === "true") nemoTag = `<span class="tag mitigated">NeMo: mitigated</span>`;
|
|
321
|
+
else if (nemoMitigated === "partial") nemoTag = `<span class="tag partial">NeMo: partial</span>`;
|
|
322
|
+
else if (nemoMitigated === "false") nemoTag = `<span class="tag residual">NeMo: residual risk</span>`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const tags = [
|
|
326
|
+
metric ? `<span class="tag metric">${metric} — ${METRIC_NAMES[metric] ?? metric}</span>` : "",
|
|
327
|
+
atlasId ? `<span class="tag atlas">${atlasId} ${atlasName}</span>` : "",
|
|
328
|
+
tactic ? `<span class="tag">${tactic}</span>` : "",
|
|
329
|
+
acscFamily ? `<span class="tag acsc">ACSC: ${acscFamily}</span>` : "",
|
|
330
|
+
nemoTag,
|
|
331
|
+
].filter(Boolean).join("");
|
|
332
|
+
|
|
333
|
+
// Header: baseline view shows simple pill; NeMo view shows delta if severity changed
|
|
334
|
+
const headerSev = (isNemoView && baselineSev && baselineSev !== sev)
|
|
335
|
+
? buildSevDelta(baselineSev, sev)
|
|
336
|
+
: buildSevPill(sev);
|
|
337
|
+
|
|
338
|
+
// NeMo control callout in body
|
|
339
|
+
let nemoControlHtml = "";
|
|
340
|
+
if (isNemoView && nemoControl && nemoControl !== "None — injection occurs inside sandboxed process") {
|
|
341
|
+
const isResidual = nemoMitigated === "false";
|
|
342
|
+
nemoControlHtml = `
|
|
343
|
+
<div class="nemo-control-box ${isResidual ? 'nemo-control-residual' : ''}">
|
|
344
|
+
<div class="nemo-control-label">${isResidual ? "Residual risk — NeMo Claw does not mitigate" : "NeMo Claw control"}</div>
|
|
345
|
+
<div class="nemo-control-text">${nemoControl}</div>
|
|
346
|
+
</div>
|
|
347
|
+
`;
|
|
348
|
+
} else if (isNemoView && nemoMitigated === "false") {
|
|
349
|
+
nemoControlHtml = `
|
|
350
|
+
<div class="nemo-control-box nemo-control-residual">
|
|
351
|
+
<div class="nemo-control-label">Residual risk — NeMo Claw does not mitigate</div>
|
|
352
|
+
<div class="nemo-control-text">${nemoControl || "This attack vector operates inside the sandbox boundary and is not addressed by kernel-level isolation."}</div>
|
|
353
|
+
</div>
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return `
|
|
358
|
+
<div class="threat-card" data-id="${t.id}" data-sev="${sev}" data-metric="${metric}" data-nemo="${nemoMitigated}" onclick="toggleCard(this)">
|
|
359
|
+
<div class="threat-header">
|
|
360
|
+
${headerSev}
|
|
361
|
+
<div style="flex:1">
|
|
362
|
+
<div class="threat-title">${t.threatAction ?? t.statement ?? "Unnamed threat"}</div>
|
|
363
|
+
<div class="threat-tags">${tags}</div>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
<div class="threat-body">
|
|
367
|
+
${nemoControlHtml}
|
|
368
|
+
${t.threatSource ? `<div class="threat-section"><div class="threat-section-label">Threat source</div><div class="threat-section-content">${t.threatSource}</div></div>` : ""}
|
|
369
|
+
${t.prerequisites ? `<div class="threat-section"><div class="threat-section-label">Prerequisites</div><div class="threat-section-content">${t.prerequisites}</div></div>` : ""}
|
|
370
|
+
${t.threatImpact ? `<div class="threat-section"><div class="threat-section-label">Impact</div><div class="threat-section-content">${t.threatImpact}</div></div>` : ""}
|
|
371
|
+
${t.statement ? `<div class="threat-section"><div class="threat-section-label">Full statement</div><div class="threat-section-content">${t.statement}</div></div>` : ""}
|
|
372
|
+
${t.impactedAssets?.length ? `<div class="threat-section"><div class="threat-section-label">Impacted assets</div><div class="threat-tags">${t.impactedAssets.map(a => `<span class="tag">${a}</span>`).join("")}</div></div>` : ""}
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function renderThreats(threats) {
|
|
379
|
+
const list = document.getElementById("threat-list");
|
|
380
|
+
if (!threats.length) {
|
|
381
|
+
list.innerHTML = '<div class="empty">No threats match the current filter.</div>';
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
list.innerHTML = threats.map(renderThreat).join("");
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function renderMitigations(mitigations) {
|
|
388
|
+
const list = document.getElementById("mitigation-list");
|
|
389
|
+
if (!mitigations.length) { list.innerHTML = ""; return; }
|
|
390
|
+
list.innerHTML = `
|
|
391
|
+
<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:var(--muted);margin-bottom:12px;">Mitigations (${mitigations.length})</div>
|
|
392
|
+
${mitigations.map(m => `
|
|
393
|
+
<div class="mitigation-card">
|
|
394
|
+
<div class="mitigation-label">Mitigation ${m.numericId}</div>
|
|
395
|
+
<div class="mitigation-text">${m.content}</div>
|
|
396
|
+
</div>
|
|
397
|
+
`).join("")}
|
|
398
|
+
`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function renderComparisonBanner(threats) {
|
|
402
|
+
const banner = document.getElementById("comparison-banner");
|
|
403
|
+
if (currentView !== "nemoclaw") { banner.style.display = "none"; return; }
|
|
404
|
+
|
|
405
|
+
let reduced = 0, unchanged = 0, partial = 0, fullyMitigated = 0;
|
|
406
|
+
for (const t of threats) {
|
|
407
|
+
const m = getMeta(t, "nemoclaw-mitigated");
|
|
408
|
+
const delta = getMeta(t, "severity-delta");
|
|
409
|
+
if (m === "true") fullyMitigated++;
|
|
410
|
+
else if (m === "partial") partial++;
|
|
411
|
+
else unchanged++;
|
|
412
|
+
if (delta === "reduced") reduced++;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
banner.style.display = "flex";
|
|
416
|
+
banner.innerHTML = `
|
|
417
|
+
<div class="comparison-stat">
|
|
418
|
+
<div class="stat-num stat-reduced">${reduced}</div>
|
|
419
|
+
<div class="stat-label">Severity reduced</div>
|
|
420
|
+
</div>
|
|
421
|
+
<div class="comparison-stat">
|
|
422
|
+
<div class="stat-num stat-unchanged">${unchanged}</div>
|
|
423
|
+
<div class="stat-label">Unchanged</div>
|
|
424
|
+
</div>
|
|
425
|
+
<div class="comparison-stat">
|
|
426
|
+
<div class="stat-num stat-mitigated">${fullyMitigated}</div>
|
|
427
|
+
<div class="stat-label">Fully mitigated</div>
|
|
428
|
+
</div>
|
|
429
|
+
<div class="comparison-stat">
|
|
430
|
+
<div class="stat-num" style="color:var(--medium)">${partial}</div>
|
|
431
|
+
<div class="stat-label">Partially mitigated</div>
|
|
432
|
+
</div>
|
|
433
|
+
<div class="comparison-note">
|
|
434
|
+
NeMo Claw's kernel-level sandbox (Landlock + seccomp + network namespaces) addresses host and engine layers
|
|
435
|
+
directly. L1 Orchestration and L2 Application threats persist — the sandbox confines the blast radius but
|
|
436
|
+
does not prevent injection or cron persistence.
|
|
437
|
+
</div>
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function renderFilters(threats) {
|
|
442
|
+
const counts = { all: threats.length, Critical: 0, High: 0, Medium: 0, Low: 0, None: 0 };
|
|
443
|
+
const metricCounts = {};
|
|
444
|
+
const nemoCounts = { mitigated: 0, partial: 0, unchanged: 0 };
|
|
445
|
+
|
|
446
|
+
for (const t of threats) {
|
|
447
|
+
const sev = getMeta(t, "aivss-severity") || "Medium";
|
|
448
|
+
if (sev in counts) counts[sev]++;
|
|
449
|
+
const metric = getMeta(t, "aivss-metric");
|
|
450
|
+
if (metric) metricCounts[metric] = (metricCounts[metric] ?? 0) + 1;
|
|
451
|
+
const nemo = getMeta(t, "nemoclaw-mitigated");
|
|
452
|
+
if (nemo === "true") nemoCounts.mitigated++;
|
|
453
|
+
else if (nemo === "partial") nemoCounts.partial++;
|
|
454
|
+
else if (nemo === "false") nemoCounts.unchanged++;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
document.getElementById("count-all").textContent = counts.all;
|
|
458
|
+
for (const sev of ["Critical", "High", "Medium", "Low"]) {
|
|
459
|
+
document.getElementById(`count-${sev}`).textContent = counts[sev];
|
|
460
|
+
}
|
|
461
|
+
document.getElementById("count-mitigated").textContent = nemoCounts.mitigated;
|
|
462
|
+
document.getElementById("count-partial").textContent = nemoCounts.partial;
|
|
463
|
+
document.getElementById("count-unchanged").textContent = nemoCounts.unchanged;
|
|
464
|
+
|
|
465
|
+
const mf = document.getElementById("metric-filters");
|
|
466
|
+
mf.innerHTML = Object.entries(metricCounts).sort().map(([m, c]) => `
|
|
467
|
+
<button class="filter-btn" data-filter="metric-${m}">
|
|
468
|
+
<span style="font-family:var(--font-mono);font-size:11px;color:var(--accent)">${m}</span>
|
|
469
|
+
${METRIC_NAMES[m] ?? m}
|
|
470
|
+
<span class="count">${c}</span>
|
|
471
|
+
</button>
|
|
472
|
+
`).join("");
|
|
473
|
+
mf.querySelectorAll(".filter-btn").forEach(btn => btn.addEventListener("click", () => applyFilter(btn)));
|
|
474
|
+
|
|
475
|
+
// Show/hide NeMo filter panel
|
|
476
|
+
const nemoSection = document.getElementById("nemo-filter-section");
|
|
477
|
+
nemoSection.style.display = currentView === "nemoclaw" ? "block" : "none";
|
|
478
|
+
document.getElementById("nemo-chip").style.display = currentView === "nemoclaw" ? "flex" : "none";
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function applyFilter(btn) {
|
|
482
|
+
document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
|
|
483
|
+
btn.classList.add("active");
|
|
484
|
+
activeFilter = btn.dataset.filter;
|
|
485
|
+
const data = activeData();
|
|
486
|
+
if (!data) return;
|
|
487
|
+
const filtered = filterThreats(data.threats ?? [], activeFilter);
|
|
488
|
+
renderThreats(filtered);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function filterThreats(threats, filter) {
|
|
492
|
+
if (filter === "all") return threats;
|
|
493
|
+
if (filter.startsWith("sev-")) {
|
|
494
|
+
const sev = filter.slice(4);
|
|
495
|
+
return threats.filter(t => (getMeta(t, "aivss-severity") || "Medium") === sev);
|
|
496
|
+
}
|
|
497
|
+
if (filter.startsWith("metric-")) {
|
|
498
|
+
const m = filter.slice(7);
|
|
499
|
+
return threats.filter(t => getMeta(t, "aivss-metric") === m);
|
|
500
|
+
}
|
|
501
|
+
if (filter === "nemo-mitigated") return threats.filter(t => getMeta(t, "nemoclaw-mitigated") === "true");
|
|
502
|
+
if (filter === "nemo-partial") return threats.filter(t => getMeta(t, "nemoclaw-mitigated") === "partial");
|
|
503
|
+
if (filter === "nemo-unchanged") return threats.filter(t => getMeta(t, "nemoclaw-mitigated") === "false");
|
|
504
|
+
return threats;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function toggleCard(el) {
|
|
508
|
+
el.classList.toggle("expanded");
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function switchView(view) {
|
|
512
|
+
currentView = view;
|
|
513
|
+
activeFilter = "all";
|
|
514
|
+
|
|
515
|
+
document.getElementById("tab-baseline").classList.toggle("active", view === "baseline");
|
|
516
|
+
document.getElementById("tab-nemoclaw").classList.toggle("active", view === "nemoclaw");
|
|
517
|
+
|
|
518
|
+
const data = activeData();
|
|
519
|
+
if (!data) return;
|
|
520
|
+
|
|
521
|
+
const threats = data.threats ?? [];
|
|
522
|
+
renderSystemInfo(data);
|
|
523
|
+
renderFilters(threats);
|
|
524
|
+
renderComparisonBanner(threats);
|
|
525
|
+
renderThreats(threats);
|
|
526
|
+
renderMitigations(data.mitigations ?? []);
|
|
527
|
+
|
|
528
|
+
// Reset filter buttons to "all"
|
|
529
|
+
document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
|
|
530
|
+
document.querySelector(".filter-btn[data-filter='all']")?.classList.add("active");
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
document.querySelectorAll(".filter-btn[data-filter]").forEach(btn => {
|
|
534
|
+
btn.addEventListener("click", () => applyFilter(btn));
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
// Load both datasets in parallel
|
|
538
|
+
Promise.all([
|
|
539
|
+
fetch("data/openclaw-demo.tc.json").then(r => r.json()),
|
|
540
|
+
fetch("data/openclaw-nemoclaw-demo.tc.json").then(r => r.json()),
|
|
541
|
+
]).then(([baseline, nemoclaw]) => {
|
|
542
|
+
baselineData = baseline;
|
|
543
|
+
nemoclawData = nemoclaw;
|
|
544
|
+
|
|
545
|
+
document.getElementById("tab-baseline-count").textContent = baseline.threats?.length ?? 0;
|
|
546
|
+
document.getElementById("tab-nemoclaw-count").textContent = nemoclaw.threats?.length ?? 0;
|
|
547
|
+
|
|
548
|
+
// Render baseline by default
|
|
549
|
+
switchView("baseline");
|
|
550
|
+
}).catch(err => {
|
|
551
|
+
document.getElementById("threat-list").innerHTML =
|
|
552
|
+
`<div class="empty">Failed to load demo data: ${err.message}</div>`;
|
|
553
|
+
});
|
|
554
|
+
</script>
|
|
555
|
+
</body>
|
|
556
|
+
</html>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A (Agent-to-Agent protocol) adapter.
|
|
3
|
+
*
|
|
4
|
+
* Fetches Agent Cards from `/.well-known/agent.json`, walks the delegation
|
|
5
|
+
* graph up to `maxDepth` hops, and produces AgentDefinition[] + HandoffEdge[]
|
|
6
|
+
* ready for assessWorkflow().
|
|
7
|
+
*
|
|
8
|
+
* Declared surface vs actual capability delta is surfaced via the
|
|
9
|
+
* UNDECLARED_CAPABILITY_RISK finding injected into every agent's systemPrompt
|
|
10
|
+
* as a structured annotation — the assembler picks it up and promotes it to a
|
|
11
|
+
* mandatory High finding.
|
|
12
|
+
*
|
|
13
|
+
* Spec reference: https://google.github.io/A2A/specification/
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentDefinition } from "../aivss/types.js";
|
|
16
|
+
import type { AdapterResult } from "./types.js";
|
|
17
|
+
export interface A2ASkill {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
tags?: string[];
|
|
22
|
+
inputModes?: string[];
|
|
23
|
+
outputModes?: string[];
|
|
24
|
+
/** Sub-agents this skill can delegate to (extension field, not in base spec). */
|
|
25
|
+
delegatesTo?: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface A2ACapabilities {
|
|
28
|
+
streaming?: boolean;
|
|
29
|
+
pushNotifications?: boolean;
|
|
30
|
+
stateTransitionHistory?: boolean;
|
|
31
|
+
extendedAgentCard?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface A2AProvider {
|
|
34
|
+
organization: string;
|
|
35
|
+
url?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface A2ASupportedInterface {
|
|
38
|
+
url: string;
|
|
39
|
+
protocolBinding: string;
|
|
40
|
+
}
|
|
41
|
+
export interface A2AAgentCard {
|
|
42
|
+
name: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
url: string;
|
|
45
|
+
version?: string;
|
|
46
|
+
protocolVersion?: string;
|
|
47
|
+
provider?: A2AProvider;
|
|
48
|
+
capabilities?: A2ACapabilities;
|
|
49
|
+
skills: A2ASkill[];
|
|
50
|
+
defaultInputModes?: string[];
|
|
51
|
+
defaultOutputModes?: string[];
|
|
52
|
+
supportedInterfaces?: A2ASupportedInterface[];
|
|
53
|
+
/** Non-spec extension: explicit delegation targets. */
|
|
54
|
+
delegatesTo?: string[];
|
|
55
|
+
}
|
|
56
|
+
export interface A2AWalkOptions {
|
|
57
|
+
/** Starting agent URLs (one or more entry points). */
|
|
58
|
+
urls: string[];
|
|
59
|
+
/** Maximum delegation hops to follow. Default: 3. */
|
|
60
|
+
maxDepth?: number;
|
|
61
|
+
/** Fetch timeout per card in ms. Default: 8000. */
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
/** Called for each fetched card — useful for progress logging. */
|
|
64
|
+
onCard?: (url: string, card: A2AAgentCard | null) => void;
|
|
65
|
+
}
|
|
66
|
+
export interface A2AWalkResult extends AdapterResult {
|
|
67
|
+
/** Cards that were reachable but returned null (timeout, 404, non-JSON). */
|
|
68
|
+
unreachable: string[];
|
|
69
|
+
/** Total hops walked. */
|
|
70
|
+
depth: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Walk an A2A delegation graph starting from one or more entry-point URLs.
|
|
74
|
+
* Returns AgentDefinition[] + HandoffEdge[] for assessWorkflow().
|
|
75
|
+
*/
|
|
76
|
+
export declare function walkA2AGraph(options: A2AWalkOptions): Promise<A2AWalkResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Fetch a single Agent Card and return an AgentDefinition.
|
|
79
|
+
* Useful when you have one URL and don't need graph traversal.
|
|
80
|
+
*/
|
|
81
|
+
export declare function fetchA2AAgent(url: string, timeoutMs?: number): Promise<AgentDefinition | null>;
|
|
82
|
+
//# sourceMappingURL=a2a.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a2a.d.ts","sourceRoot":"","sources":["../../src/adapters/a2a.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAA2B,MAAM,mBAAmB,CAAC;AAClF,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,YAAY,CAAC;AAI7D,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,mBAAmB,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAC9C,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AA+HD,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD,4EAA4E;IAC5E,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAgFlF;AAID;;;GAGG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAIlG"}
|