@guava-parity/guard-scanner 9.1.0 → 13.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.
@@ -0,0 +1,530 @@
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>guard-scanner — Security Dashboard</title>
7
+ <meta name="description" content="Real-time security dashboard for AI agent skills. 352 patterns · 32 categories · 8 MCP checks · OWASP ASI01-10. Zero dependencies.">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
13
+ :root{
14
+ --bg:#0a0e17;--bg2:#111827;--surface:rgba(17,24,39,.65);
15
+ --border:rgba(0,255,136,.12);--border-hover:rgba(0,255,136,.3);
16
+ --green:#00ff88;--green-dim:rgba(0,255,136,.15);--green-glow:rgba(0,255,136,.25);
17
+ --amber:#ffb800;--amber-dim:rgba(255,184,0,.15);
18
+ --red:#ff3b5c;--red-dim:rgba(255,59,92,.15);
19
+ --blue:#38bdf8;--blue-dim:rgba(56,189,248,.12);
20
+ --purple:#a78bfa;--purple-dim:rgba(167,139,250,.12);
21
+ --text:#e2e8f0;--text-dim:#94a3b8;--text-muted:#64748b;
22
+ --font:'Inter',system-ui,sans-serif;--mono:'JetBrains Mono',monospace;
23
+ --glass:saturate(180%) blur(20px);
24
+ --radius:16px;--radius-sm:10px;
25
+ }
26
+ html{scroll-behavior:smooth}
27
+ body{font-family:var(--font);background:var(--bg);color:var(--text);min-height:100vh;overflow-x:hidden;line-height:1.6}
28
+
29
+ /* ── Background Effects ── */
30
+ body::before{content:'';position:fixed;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 20% 50%,rgba(0,255,136,.04) 0%,transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(56,189,248,.03) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(167,139,250,.03) 0%,transparent 40%);z-index:0;pointer-events:none}
31
+ .grid-bg{position:fixed;inset:0;background-image:linear-gradient(rgba(0,255,136,.03) 1px,transparent 1px),linear-gradient(90deg,rgba(0,255,136,.03) 1px,transparent 1px);background-size:64px 64px;z-index:0;pointer-events:none;opacity:.5}
32
+
33
+ /* ── Layout ── */
34
+ .container{max-width:1200px;margin:0 auto;padding:0 24px;position:relative;z-index:1}
35
+ section{margin-bottom:64px}
36
+
37
+ /* ── Animations ── */
38
+ @keyframes fadeUp{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:translateY(0)}}
39
+ @keyframes pulse-glow{0%,100%{box-shadow:0 0 20px rgba(0,255,136,.1)}50%{box-shadow:0 0 40px rgba(0,255,136,.2)}}
40
+ @keyframes count-up{from{opacity:.3}to{opacity:1}}
41
+ .fade-up{animation:fadeUp .6s ease-out both}
42
+ .fade-up:nth-child(2){animation-delay:.1s}
43
+ .fade-up:nth-child(3){animation-delay:.2s}
44
+ .fade-up:nth-child(4){animation-delay:.3s}
45
+
46
+ /* ── Glass Card ── */
47
+ .glass{background:var(--surface);backdrop-filter:var(--glass);-webkit-backdrop-filter:var(--glass);border:1px solid var(--border);border-radius:var(--radius);transition:border-color .3s,box-shadow .3s,transform .3s}
48
+ .glass:hover{border-color:var(--border-hover);box-shadow:0 8px 32px rgba(0,255,136,.08);transform:translateY(-2px)}
49
+
50
+ /* ── Hero ── */
51
+ .hero{padding:80px 0 48px;text-align:center}
52
+ .hero-badge{display:inline-flex;align-items:center;gap:8px;padding:6px 16px;border-radius:999px;background:var(--green-dim);border:1px solid rgba(0,255,136,.2);font-size:13px;font-weight:600;color:var(--green);letter-spacing:.03em;margin-bottom:24px}
53
+ .hero-badge .dot{width:8px;height:8px;border-radius:50%;background:var(--green);animation:pulse-glow 2s ease-in-out infinite}
54
+ .hero h1{font-size:clamp(2.5rem,6vw,4rem);font-weight:900;letter-spacing:-.03em;background:linear-gradient(135deg,#fff 0%,var(--green) 50%,var(--blue) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:12px}
55
+ .hero h1 .shield{font-size:1em;-webkit-text-fill-color:var(--green);filter:drop-shadow(0 0 12px rgba(0,255,136,.4))}
56
+ .hero .tagline{font-size:clamp(1.1rem,2.5vw,1.4rem);color:var(--text-dim);font-weight:500;max-width:640px;margin:0 auto 32px}
57
+ .hero .badges{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
58
+ .hero .badges img{height:22px;border-radius:4px}
59
+ .hero .sub-stats{display:flex;flex-wrap:wrap;gap:24px;justify-content:center;margin-top:24px;font-size:14px;color:var(--text-muted)}
60
+ .hero .sub-stats span{display:flex;align-items:center;gap:6px}
61
+ .hero .sub-stats .num{color:var(--green);font-family:var(--mono);font-weight:700}
62
+
63
+ /* ── Stats Grid ── */
64
+ .stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:20px;margin-bottom:48px}
65
+ .stat-card{padding:28px 24px;text-align:center}
66
+ .stat-card .stat-icon{font-size:28px;margin-bottom:12px;display:block}
67
+ .stat-card .stat-value{font-family:var(--mono);font-size:2.4rem;font-weight:800;line-height:1;margin-bottom:6px;background:linear-gradient(135deg,#fff,var(--green));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
68
+ .stat-card .stat-label{font-size:13px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;font-weight:600}
69
+ .stat-card.amber .stat-value{background:linear-gradient(135deg,#fff,var(--amber));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
70
+ .stat-card.red .stat-value{background:linear-gradient(135deg,#fff,var(--red));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
71
+ .stat-card.blue .stat-value{background:linear-gradient(135deg,#fff,var(--blue));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
72
+
73
+ /* ── Section Headings ── */
74
+ .section-head{margin-bottom:28px}
75
+ .section-head h2{font-size:1.6rem;font-weight:800;letter-spacing:-.02em;display:flex;align-items:center;gap:10px}
76
+ .section-head h2 .icon{font-size:1.2em}
77
+ .section-head p{color:var(--text-dim);font-size:14px;margin-top:4px;max-width:600px}
78
+
79
+ /* ── Checks Grid ── */
80
+ .checks-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
81
+ .check-card{padding:20px;display:flex;align-items:flex-start;gap:14px}
82
+ .check-card .check-num{font-family:var(--mono);font-size:12px;font-weight:700;color:var(--green);background:var(--green-dim);width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
83
+ .check-card .check-body h3{font-size:14px;font-weight:700;margin-bottom:4px;color:#fff}
84
+ .check-card .check-body p{font-size:12px;color:var(--text-muted);line-height:1.5}
85
+ .check-card .check-body .ref{font-size:11px;color:var(--blue);font-family:var(--mono);margin-top:4px;opacity:.8}
86
+
87
+ /* ── OWASP Grid ── */
88
+ .owasp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
89
+ .owasp-item{padding:16px;display:flex;align-items:center;gap:12px}
90
+ .owasp-item .owasp-code{font-family:var(--mono);font-size:12px;font-weight:700;color:var(--green);background:var(--green-dim);padding:4px 10px;border-radius:6px;white-space:nowrap}
91
+ .owasp-item .owasp-label{font-size:13px;font-weight:500}
92
+ .owasp-item .owasp-check{margin-left:auto;color:var(--green);font-size:16px;flex-shrink:0}
93
+
94
+ /* ── Donut Chart ── */
95
+ .chart-container{display:flex;flex-wrap:wrap;gap:40px;align-items:center;justify-content:center}
96
+ .donut-wrap{position:relative;width:200px;height:200px;flex-shrink:0}
97
+ .donut-wrap svg{width:100%;height:100%;transform:rotate(-90deg)}
98
+ .donut-wrap .center-label{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}
99
+ .donut-wrap .center-label .big{font-family:var(--mono);font-size:2rem;font-weight:800;color:#fff}
100
+ .donut-wrap .center-label .sub{font-size:12px;color:var(--text-muted);margin-top:2px}
101
+ .legend{display:flex;flex-direction:column;gap:8px}
102
+ .legend-item{display:flex;align-items:center;gap:10px;font-size:13px}
103
+ .legend-item .swatch{width:12px;height:12px;border-radius:3px;flex-shrink:0}
104
+ .legend-item .count{font-family:var(--mono);margin-left:auto;color:var(--text-dim);font-weight:600;min-width:24px;text-align:right}
105
+
106
+ /* ── Skills Table ── */
107
+ .table-wrap{overflow-x:auto;border-radius:var(--radius)}
108
+ .table-controls{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:16px;align-items:center}
109
+ .table-controls input{background:var(--surface);backdrop-filter:var(--glass);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 16px;color:var(--text);font-size:14px;font-family:var(--font);width:280px;outline:none;transition:border-color .3s}
110
+ .table-controls input:focus{border-color:var(--green)}
111
+ .table-controls input::placeholder{color:var(--text-muted)}
112
+ .filter-btn{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 16px;color:var(--text-dim);font-size:13px;font-weight:600;cursor:pointer;transition:all .2s;font-family:var(--font)}
113
+ .filter-btn:hover,.filter-btn.active{border-color:var(--green);color:var(--green);background:var(--green-dim)}
114
+ table{width:100%;border-collapse:collapse}
115
+ table th{text-align:left;padding:14px 16px;font-size:12px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);font-weight:700;border-bottom:1px solid var(--border);cursor:pointer;user-select:none;white-space:nowrap;transition:color .2s}
116
+ table th:hover{color:var(--green)}
117
+ table td{padding:12px 16px;font-size:14px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:middle}
118
+ table tr{transition:background .2s}
119
+ table tbody tr:hover{background:rgba(0,255,136,.03)}
120
+ .skill-name{font-weight:600;font-family:var(--mono);font-size:13px}
121
+ .badge{display:inline-block;padding:3px 10px;border-radius:6px;font-size:11px;font-weight:700;font-family:var(--mono);text-transform:uppercase;letter-spacing:.04em}
122
+ .badge-clean{background:var(--green-dim);color:var(--green)}
123
+ .badge-findings{background:var(--amber-dim);color:var(--amber)}
124
+ .badge-error{background:var(--red-dim);color:var(--red)}
125
+ .risk-bar{width:60px;height:6px;background:rgba(255,255,255,.08);border-radius:3px;overflow:hidden;display:inline-block;vertical-align:middle;margin-right:8px}
126
+ .risk-bar-fill{height:100%;border-radius:3px;transition:width .4s ease}
127
+ .risk-low{background:var(--green)}
128
+ .risk-med{background:var(--amber)}
129
+ .risk-high{background:var(--red)}
130
+
131
+ /* ── Footer ── */
132
+ .footer{text-align:center;padding:48px 0;border-top:1px solid var(--border);color:var(--text-muted);font-size:13px}
133
+ .footer a{color:var(--green);text-decoration:none;font-weight:600;transition:opacity .2s}
134
+ .footer a:hover{opacity:.7}
135
+ .footer .footer-links{display:flex;flex-wrap:wrap;gap:24px;justify-content:center;margin-bottom:16px}
136
+ .footer .heart{color:var(--red)}
137
+
138
+ /* ── Responsive ── */
139
+ @media(max-width:768px){
140
+ .hero{padding:48px 0 32px}
141
+ .hero h1{font-size:2rem}
142
+ .stats-grid{grid-template-columns:repeat(2,1fr);gap:12px}
143
+ .stat-card{padding:20px 16px}
144
+ .stat-card .stat-value{font-size:1.8rem}
145
+ .checks-grid{grid-template-columns:1fr}
146
+ .owasp-grid{grid-template-columns:1fr}
147
+ .chart-container{flex-direction:column;align-items:center}
148
+ .table-controls input{width:100%}
149
+ }
150
+ @media(max-width:480px){
151
+ .stats-grid{grid-template-columns:1fr}
152
+ .container{padding:0 16px}
153
+ }
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <div class="grid-bg"></div>
158
+
159
+ <div class="container">
160
+ <!-- ═══ HERO ═══ -->
161
+ <section class="hero fade-up">
162
+ <div class="hero-badge"><span class="dot"></span> Live Security Intelligence</div>
163
+ <h1><span class="shield">🛡️</span> guard-scanner</h1>
164
+ <p class="tagline">VirusTotal for AI Agent Skills — real-time threat detection across 32 categories, 352 patterns, and 8 MCP security checks.</p>
165
+ <div class="badges">
166
+ <img src="https://img.shields.io/npm/v/@guava-parity/guard-scanner?color=cb3837&style=flat-square" alt="npm">
167
+ <img src="https://img.shields.io/badge/tests-63%2F63-00ff88?style=flat-square" alt="tests">
168
+ <img src="https://img.shields.io/badge/deps-0-38bdf8?style=flat-square" alt="deps">
169
+ <img src="https://img.shields.io/badge/OWASP_ASI-100%25-a78bfa?style=flat-square" alt="OWASP">
170
+ <img src="https://img.shields.io/npm/l/guard-scanner?color=64748b&style=flat-square" alt="license">
171
+ </div>
172
+ <div class="sub-stats">
173
+ <span>🏷️ Version <span class="num" id="versionBadge">13.0.0</span></span>
174
+ <span>📦 Categories <span class="num">32</span></span>
175
+ <span>⚡ Avg Scan <span class="num">0.016ms</span></span>
176
+ <span>🔌 IDEs <span class="num">8</span></span>
177
+ </div>
178
+ </section>
179
+
180
+ <!-- ═══ STATS ═══ -->
181
+ <section class="stats-grid" id="statsGrid">
182
+ <div class="glass stat-card fade-up">
183
+ <span class="stat-icon">📋</span>
184
+ <div class="stat-value" id="statTotal">—</div>
185
+ <div class="stat-label">Skills Scanned</div>
186
+ </div>
187
+ <div class="glass stat-card fade-up">
188
+ <span class="stat-icon">✅</span>
189
+ <div class="stat-value" id="statClean">—</div>
190
+ <div class="stat-label">Clean Rate</div>
191
+ </div>
192
+ <div class="glass stat-card amber fade-up">
193
+ <span class="stat-icon">⚠️</span>
194
+ <div class="stat-value" id="statFindings">—</div>
195
+ <div class="stat-label">Findings</div>
196
+ </div>
197
+ <div class="glass stat-card blue fade-up">
198
+ <span class="stat-icon">🕐</span>
199
+ <div class="stat-value" id="statDate">—</div>
200
+ <div class="stat-label">Last Scan</div>
201
+ </div>
202
+ </section>
203
+
204
+ <!-- ═══ THREAT CATEGORIES ═══ -->
205
+ <section id="chartSection" class="fade-up">
206
+ <div class="section-head">
207
+ <h2><span class="icon">📊</span> Threat Category Breakdown</h2>
208
+ <p>Distribution of detected threats across 32 categories</p>
209
+ </div>
210
+ <div class="chart-container glass" style="padding:32px">
211
+ <div class="donut-wrap">
212
+ <svg viewBox="0 0 120 120" id="donutChart"></svg>
213
+ <div class="center-label">
214
+ <span class="big" id="donutTotal">0</span>
215
+ <span class="sub">findings</span>
216
+ </div>
217
+ </div>
218
+ <div class="legend" id="chartLegend"></div>
219
+ </div>
220
+ </section>
221
+
222
+ <!-- ═══ MCP SECURITY CHECKS ═══ -->
223
+ <section class="fade-up">
224
+ <div class="section-head">
225
+ <h2><span class="icon">🔒</span> 8 MCP Security Checks</h2>
226
+ <p>Deep inspection of AI editor MCP configurations across 8 IDEs</p>
227
+ </div>
228
+ <div class="checks-grid" id="checksGrid"></div>
229
+ </section>
230
+
231
+ <!-- ═══ OWASP ASI ═══ -->
232
+ <section class="fade-up">
233
+ <div class="section-head">
234
+ <h2><span class="icon">🏛️</span> OWASP ASI01–10 Coverage</h2>
235
+ <p>100% coverage of the OWASP Agentic Security Initiative top 10 risks</p>
236
+ </div>
237
+ <div class="owasp-grid" id="owaspGrid"></div>
238
+ </section>
239
+
240
+ <!-- ═══ SKILL RESULTS TABLE ═══ -->
241
+ <section class="fade-up">
242
+ <div class="section-head">
243
+ <h2><span class="icon">🔍</span> Skill Scan Results</h2>
244
+ <p>Detailed findings per scanned AI agent skill</p>
245
+ </div>
246
+ <div class="table-controls">
247
+ <input type="text" id="searchInput" placeholder="🔎 Search skills...">
248
+ <button class="filter-btn active" data-filter="all">All</button>
249
+ <button class="filter-btn" data-filter="clean">✅ Clean</button>
250
+ <button class="filter-btn" data-filter="findings">⚠️ Findings</button>
251
+ <button class="filter-btn" data-filter="error">❌ Error</button>
252
+ </div>
253
+ <div class="glass table-wrap">
254
+ <table>
255
+ <thead>
256
+ <tr>
257
+ <th data-sort="name">Skill ↕</th>
258
+ <th data-sort="status">Status ↕</th>
259
+ <th data-sort="risk">Risk ↕</th>
260
+ <th data-sort="findings">Findings ↕</th>
261
+ <th>Time</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody id="resultsBody"></tbody>
265
+ </table>
266
+ </div>
267
+ </section>
268
+
269
+ <!-- ═══ FOOTER ═══ -->
270
+ <footer class="footer">
271
+ <div class="footer-links">
272
+ <a href="https://github.com/koatora20/guard-scanner">GitHub</a>
273
+ <a href="https://www.npmjs.com/package/@guava-parity/guard-scanner">npm</a>
274
+ <a href="https://github.com/koatora20/dual-shield-paper">Research Paper</a>
275
+ </div>
276
+ <p>Built with <span class="heart">♥</span> by <a href="https://github.com/koatora20">Guava Parity Institute</a> — dee & Guava 🍈</p>
277
+ </footer>
278
+ </div>
279
+
280
+ <script>
281
+ 'use strict';
282
+
283
+ // ── MCP Security Checks data ──
284
+ const MCP_CHECKS = [
285
+ { id: 1, name: 'SECRET_IN_ENV', desc: 'Detects hardcoded API keys, tokens, and passwords in MCP server environment variables', ref: 'Original' },
286
+ { id: 2, name: 'PATH_TRAVERSAL', desc: 'Catches ../ directory traversal patterns in command arguments', ref: 'CVE-2026-27735' },
287
+ { id: 3, name: 'SUSPICIOUS_FILE_URI', desc: 'Flags file:// URIs targeting sensitive paths like /etc/passwd or ~/.ssh', ref: 'Smithery.ai' },
288
+ { id: 4, name: 'COMMAND_INJECTION', desc: 'Detects shell metacharacters (;, |, &, $(), backticks) in arguments', ref: 'CVE-2025-54135' },
289
+ { id: 5, name: 'HOMOGLYPH_NAME', desc: 'Identifies non-ASCII lookalike characters in MCP server names', ref: 'Palo Alto Unit 42' },
290
+ { id: 6, name: 'PROTOTYPE_POLLUTION', desc: 'Pre-parse raw JSON scan for __proto__, constructor, prototype injection', ref: 'CVE-2026-29063' },
291
+ { id: 7, name: 'TOOL_SHADOWING', desc: 'Levenshtein distance ≤2 comparison against 17 known MCP server names', ref: 'Snyk Invariant Labs' },
292
+ { id: 8, name: 'SUSPICIOUS_URL', desc: 'Flags external https:// URLs embedded in MCP tool arguments', ref: 'postmark-mcp incident' },
293
+ ];
294
+
295
+ // ── OWASP ASI01-10 data ──
296
+ const OWASP_ASI = [
297
+ { code: 'ASI01', label: 'Prompt Injection' },
298
+ { code: 'ASI02', label: 'Unsafe Tool/Function Calls' },
299
+ { code: 'ASI03', label: 'Agent Identity Spoofing' },
300
+ { code: 'ASI04', label: 'Privilege Escalation' },
301
+ { code: 'ASI05', label: 'Memory Poisoning' },
302
+ { code: 'ASI06', label: 'Data/Secret Exfiltration' },
303
+ { code: 'ASI07', label: 'Supply Chain Attack' },
304
+ { code: 'ASI08', label: 'Cascading Hallucination' },
305
+ { code: 'ASI09', label: 'Repudiation / Audit Failure' },
306
+ { code: 'ASI10', label: 'Denial of Service' },
307
+ ];
308
+
309
+ // ── Chart colors ──
310
+ const CHART_COLORS = [
311
+ '#00ff88','#38bdf8','#a78bfa','#ffb800','#ff3b5c','#f472b6',
312
+ '#2dd4bf','#fb923c','#818cf8','#a3e635','#e879f9','#fbbf24',
313
+ '#22d3ee','#f87171','#34d399','#c084fc','#fcd34d','#6ee7b7',
314
+ '#93c5fd','#fca5a1','#86efac','#c4b5fd','#fde68a',
315
+ ];
316
+
317
+ // ── Demo data (used when fetch fails) ──
318
+ const DEMO_DATA = {
319
+ meta: { scanDate: new Date().toISOString(), scannerVersion: '13.0.0', totalSkills: 104 },
320
+ summary: { clean: 85, withFindings: 12, errors: 7, totalFindings: 34, cleanPercentage: 82 },
321
+ categoryBreakdown: {
322
+ 'Prompt Injection': 8, 'Malicious Code': 6, 'Exfiltration': 5,
323
+ 'Memory Poisoning': 4, 'MCP Security': 3, 'Credential Handling': 3,
324
+ 'Obfuscation': 2, 'Trust Exploitation': 2, 'Persistence': 1,
325
+ },
326
+ results: Array.from({ length: 104 }, (_, i) => {
327
+ const statuses = ['clean','clean','clean','clean','clean','clean','clean','findings','error'];
328
+ const s = statuses[i % statuses.length];
329
+ return {
330
+ name: `skill-${String(i + 1).padStart(3, '0')}`,
331
+ status: s,
332
+ findingsCount: s === 'findings' ? Math.ceil(Math.random() * 5) : 0,
333
+ riskScore: s === 'findings' ? +(Math.random() * 80 + 20).toFixed(1) : s === 'error' ? -1 : 0,
334
+ findings: [],
335
+ scannedAt: new Date(Date.now() - Math.random() * 3600000).toISOString(),
336
+ };
337
+ }),
338
+ };
339
+
340
+ // ── State ──
341
+ let DATA = null;
342
+ let sortCol = 'risk';
343
+ let sortDir = -1;
344
+ let filterStatus = 'all';
345
+
346
+ // ── Init ──
347
+ async function init() {
348
+ try {
349
+ // Try relative paths for both GitHub Pages and local dev
350
+ let resp;
351
+ for (const url of ['../data/latest.json', './data/latest.json', 'data/latest.json']) {
352
+ try { resp = await fetch(url); if (resp.ok) break; } catch {}
353
+ }
354
+ if (resp && resp.ok) {
355
+ DATA = await resp.json();
356
+ } else {
357
+ throw new Error('No data');
358
+ }
359
+ } catch {
360
+ DATA = DEMO_DATA;
361
+ console.log('ℹ️ Using demo data. Deploy data/latest.json for live results.');
362
+ }
363
+ renderAll();
364
+ }
365
+
366
+ function renderAll() {
367
+ renderStats();
368
+ renderChart();
369
+ renderChecks();
370
+ renderOwasp();
371
+ renderTable();
372
+ setupControls();
373
+ }
374
+
375
+ // ── Stats ──
376
+ function renderStats() {
377
+ const { summary, meta } = DATA;
378
+ animateValue('statTotal', summary.clean + summary.withFindings + summary.errors, '', 0);
379
+ animateValue('statClean', summary.cleanPercentage, '%', 0);
380
+ animateValue('statFindings', summary.totalFindings, '', 0);
381
+ const d = new Date(meta.scanDate);
382
+ document.getElementById('statDate').textContent = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
383
+ document.getElementById('versionBadge').textContent = meta.scannerVersion || '13.0.0';
384
+ }
385
+
386
+ function animateValue(id, target, suffix, decimals) {
387
+ const el = document.getElementById(id);
388
+ const dur = 1200;
389
+ const start = performance.now();
390
+ function tick(now) {
391
+ const t = Math.min((now - start) / dur, 1);
392
+ const ease = 1 - Math.pow(1 - t, 3);
393
+ const val = (target * ease).toFixed(decimals);
394
+ el.textContent = val + suffix;
395
+ if (t < 1) requestAnimationFrame(tick);
396
+ }
397
+ requestAnimationFrame(tick);
398
+ }
399
+
400
+ // ── Donut Chart ──
401
+ function renderChart() {
402
+ const cats = DATA.categoryBreakdown || {};
403
+ const entries = Object.entries(cats).sort((a, b) => b[1] - a[1]);
404
+ const total = entries.reduce((s, [, v]) => s + v, 0);
405
+ document.getElementById('donutTotal').textContent = total;
406
+
407
+ if (total === 0) {
408
+ document.getElementById('chartSection').querySelector('.chart-container').innerHTML =
409
+ '<div style="text-align:center;padding:40px;color:var(--green)"><div style="font-size:48px;margin-bottom:12px">✅</div><div style="font-size:18px;font-weight:700">All Clear</div><div style="color:var(--text-muted);font-size:14px;margin-top:4px">No threats detected in this scan</div></div>';
410
+ return;
411
+ }
412
+
413
+ const svg = document.getElementById('donutChart');
414
+ const legend = document.getElementById('chartLegend');
415
+ const R = 50, CX = 60, CY = 60, SW = 14;
416
+ const C = 2 * Math.PI * R;
417
+ let offset = 0;
418
+ let html = '';
419
+ let legendHtml = '';
420
+
421
+ entries.forEach(([cat, count], i) => {
422
+ const pct = count / total;
423
+ const len = pct * C;
424
+ const color = CHART_COLORS[i % CHART_COLORS.length];
425
+ html += `<circle cx="${CX}" cy="${CY}" r="${R}" fill="none" stroke="${color}" stroke-width="${SW}" stroke-dasharray="${len} ${C - len}" stroke-dashoffset="${-offset}" opacity="0.9"/>`;
426
+ offset += len;
427
+ legendHtml += `<div class="legend-item"><span class="swatch" style="background:${color}"></span><span>${cat}</span><span class="count">${count}</span></div>`;
428
+ });
429
+
430
+ svg.innerHTML = html;
431
+ legend.innerHTML = legendHtml;
432
+ }
433
+
434
+ // ── MCP Checks ──
435
+ function renderChecks() {
436
+ const grid = document.getElementById('checksGrid');
437
+ grid.innerHTML = MCP_CHECKS.map(c =>
438
+ `<div class="glass check-card">
439
+ <div class="check-num">${c.id}</div>
440
+ <div class="check-body">
441
+ <h3>${c.name}</h3>
442
+ <p>${c.desc}</p>
443
+ <div class="ref">${c.ref}</div>
444
+ </div>
445
+ </div>`
446
+ ).join('');
447
+ }
448
+
449
+ // ── OWASP ASI ──
450
+ function renderOwasp() {
451
+ const grid = document.getElementById('owaspGrid');
452
+ grid.innerHTML = OWASP_ASI.map(o =>
453
+ `<div class="glass owasp-item">
454
+ <span class="owasp-code">${o.code}</span>
455
+ <span class="owasp-label">${o.label}</span>
456
+ <span class="owasp-check">✓</span>
457
+ </div>`
458
+ ).join('');
459
+ }
460
+
461
+ // ── Results Table ──
462
+ function renderTable() {
463
+ const body = document.getElementById('resultsBody');
464
+ const search = (document.getElementById('searchInput')?.value || '').toLowerCase();
465
+
466
+ let rows = (DATA.results || []).filter(r => {
467
+ if (filterStatus !== 'all' && r.status !== filterStatus) return false;
468
+ if (search && !r.name.toLowerCase().includes(search)) return false;
469
+ return true;
470
+ });
471
+
472
+ rows.sort((a, b) => {
473
+ let va, vb;
474
+ switch (sortCol) {
475
+ case 'name': va = a.name; vb = b.name; return sortDir * va.localeCompare(vb);
476
+ case 'status': va = a.status; vb = b.status; return sortDir * va.localeCompare(vb);
477
+ case 'risk': va = a.riskScore; vb = b.riskScore; return sortDir * (va - vb);
478
+ case 'findings': va = a.findingsCount; vb = b.findingsCount; return sortDir * (va - vb);
479
+ default: return 0;
480
+ }
481
+ });
482
+
483
+ body.innerHTML = rows.map(r => {
484
+ const badgeClass = r.status === 'clean' ? 'badge-clean' : r.status === 'findings' ? 'badge-findings' : 'badge-error';
485
+ const riskPct = Math.max(0, Math.min(100, r.riskScore));
486
+ const riskColor = riskPct <= 30 ? 'risk-low' : riskPct <= 60 ? 'risk-med' : 'risk-high';
487
+ const time = r.scannedAt ? new Date(r.scannedAt).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) : '—';
488
+
489
+ return `<tr>
490
+ <td class="skill-name">${escHtml(r.name)}</td>
491
+ <td><span class="badge ${badgeClass}">${r.status}</span></td>
492
+ <td>
493
+ <span class="risk-bar"><span class="risk-bar-fill ${riskColor}" style="width:${riskPct}%"></span></span>
494
+ <span style="font-family:var(--mono);font-size:13px;color:var(--text-dim)">${r.riskScore >= 0 ? r.riskScore.toFixed(1) : '—'}</span>
495
+ </td>
496
+ <td style="font-family:var(--mono)">${r.findingsCount}</td>
497
+ <td style="color:var(--text-muted);font-size:12px">${time}</td>
498
+ </tr>`;
499
+ }).join('');
500
+ }
501
+
502
+ function escHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
503
+
504
+ // ── Controls ──
505
+ function setupControls() {
506
+ document.getElementById('searchInput').addEventListener('input', renderTable);
507
+
508
+ document.querySelectorAll('.filter-btn').forEach(btn => {
509
+ btn.addEventListener('click', () => {
510
+ document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
511
+ btn.classList.add('active');
512
+ filterStatus = btn.dataset.filter;
513
+ renderTable();
514
+ });
515
+ });
516
+
517
+ document.querySelectorAll('th[data-sort]').forEach(th => {
518
+ th.addEventListener('click', () => {
519
+ const col = th.dataset.sort;
520
+ if (sortCol === col) sortDir *= -1; else { sortCol = col; sortDir = -1; }
521
+ renderTable();
522
+ });
523
+ });
524
+ }
525
+
526
+ // ── Launch ──
527
+ document.addEventListener('DOMContentLoaded', init);
528
+ </script>
529
+ </body>
530
+ </html>
package/package.json CHANGED
@@ -1,58 +1,66 @@
1
1
  {
2
- "name": "@guava-parity/guard-scanner",
3
- "version": "9.1.0",
4
- "publishConfig": {
5
- "access": "public",
6
- "registry": "https://registry.npmjs.org/"
7
- },
8
- "description": "Agent security scanner + asset audit platform 166 static patterns (23 categories), 26 runtime checks (5 layers), npm/GitHub/ClawHub asset auditing, 0.016ms/scan, before_tool_call hook, CLI, SARIF. OpenClaw-compatible plugin.",
9
- "openclaw.extensions": "./openclaw.plugin.json",
10
- "openclaw.hooks": {
11
- "guard-scanner": "./hooks/guard-scanner"
12
- },
13
- "main": "src/scanner.js",
14
- "bin": {
15
- "guard-scanner": "src/cli.js"
16
- },
17
- "scripts": {
18
- "scan": "node src/cli.js",
19
- "test": "node --test test/*.test.js"
20
- },
21
- "keywords": [
22
- "security",
23
- "scanner",
24
- "ai-agent",
25
- "skill-scanner",
26
- "prompt-injection",
27
- "openclaw",
28
- "mcp",
29
- "sarif",
30
- "compaction-persistence",
31
- "threat-signatures",
32
- "typescript"
33
- ],
34
- "author": "Guava & Dee",
35
- "license": "MIT",
36
- "engines": {
37
- "node": ">=18.0.0"
38
- },
39
- "repository": {
40
- "type": "git",
41
- "url": "https://github.com/koatora20/guard-scanner.git"
42
- },
43
- "homepage": "https://github.com/koatora20/guard-scanner",
44
- "files": [
45
- "src/",
46
- "hooks/",
47
- "docs/",
48
- "openclaw.plugin.json",
49
- "SKILL.md",
50
- "SECURITY.md",
51
- "README.md",
52
- "LICENSE"
53
- ],
54
- "devDependencies": {
55
- "@types/node": "^22.0.0",
56
- "typescript": "^5.7.0"
57
- }
2
+ "name": "@guava-parity/guard-scanner",
3
+ "version": "13.0.0",
4
+ "publishConfig": {
5
+ "access": "public",
6
+ "registry": "https://registry.npmjs.org/"
7
+ },
8
+ "description": "Agent Skill Security Scanner - 2026 Moltbook Payload Immune System",
9
+ "openclaw.extensions": "./openclaw.plugin.json",
10
+ "openclaw.hooks": {
11
+ "guard-scanner": "./hooks/guard-scanner"
12
+ },
13
+ "main": "src/scanner.js",
14
+ "bin": {
15
+ "guard-scanner": "src/cli.js"
16
+ },
17
+ "scripts": {
18
+ "scan": "node src/cli.js",
19
+ "test": "node scripts/test-quality-gate.js && node --test test/*.test.js",
20
+ "test:core": "node --test test/scanner.test.js test/patterns.test.js",
21
+ "test:quality": "node scripts/test-quality-gate.js"
22
+ },
23
+ "keywords": [
24
+ "security",
25
+ "scanner",
26
+ "ai-agent",
27
+ "skill-scanner",
28
+ "prompt-injection",
29
+ "openclaw",
30
+ "mcp",
31
+ "sarif",
32
+ "compaction-persistence",
33
+ "threat-signatures",
34
+ "owasp",
35
+ "discovery",
36
+ "daemon",
37
+ "typescript"
38
+ ],
39
+ "author": "Guava & Dee",
40
+ "license": "MIT",
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ },
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/koatora20/guard-scanner.git"
47
+ },
48
+ "homepage": "https://github.com/koatora20/guard-scanner",
49
+ "files": [
50
+ "src/",
51
+ "hooks/",
52
+ "docs/",
53
+ "openclaw.plugin.json",
54
+ "SKILL.md",
55
+ "SECURITY.md",
56
+ "README.md",
57
+ "LICENSE"
58
+ ],
59
+ "devDependencies": {
60
+ "@types/node": "^22.0.0",
61
+ "typescript": "^5.7.0"
62
+ },
63
+ "dependencies": {
64
+ "ws": "^8.19.0"
65
+ }
58
66
  }