@guava-parity/guard-scanner 13.0.0 → 15.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.
Files changed (79) hide show
  1. package/README.md +42 -253
  2. package/SECURITY.md +12 -4
  3. package/SKILL.md +121 -59
  4. package/dist/openclaw-plugin.mjs +41 -0
  5. package/docs/EVIDENCE_DRIVEN.md +182 -0
  6. package/docs/banner.png +0 -0
  7. package/docs/data/corpus-metrics.json +11 -0
  8. package/docs/data/latest.json +25837 -2481
  9. package/docs/generated/npm-audit-20260312.json +96 -0
  10. package/docs/generated/openclaw-upstream-status.json +25 -0
  11. package/docs/glossary.md +46 -0
  12. package/docs/index.html +1085 -496
  13. package/docs/logo.png +0 -0
  14. package/docs/openclaw-compatibility-audit.md +44 -0
  15. package/docs/openclaw-continuous-compatibility-plan.md +36 -0
  16. package/docs/rules/a2a-contagion.md +68 -0
  17. package/docs/rules/advanced-exfil.md +52 -0
  18. package/docs/rules/agent-protocol.md +108 -0
  19. package/docs/rules/api-abuse.md +68 -0
  20. package/docs/rules/autonomous-risk.md +92 -0
  21. package/docs/rules/config-impact.md +132 -0
  22. package/docs/rules/credential-handling.md +100 -0
  23. package/docs/rules/cve-patterns.md +332 -0
  24. package/docs/rules/data-exposure.md +84 -0
  25. package/docs/rules/exfiltration.md +36 -0
  26. package/docs/rules/financial-access.md +84 -0
  27. package/docs/rules/identity-hijack.md +140 -0
  28. package/docs/rules/inference-manipulation.md +60 -0
  29. package/docs/rules/leaky-skills.md +52 -0
  30. package/docs/rules/malicious-code.md +108 -0
  31. package/docs/rules/mcp-security.md +148 -0
  32. package/docs/rules/memory-poisoning.md +84 -0
  33. package/docs/rules/model-poisoning.md +44 -0
  34. package/docs/rules/obfuscation.md +60 -0
  35. package/docs/rules/persistence.md +108 -0
  36. package/docs/rules/pii-exposure.md +116 -0
  37. package/docs/rules/prompt-injection.md +148 -0
  38. package/docs/rules/prompt-worm.md +44 -0
  39. package/docs/rules/safeguard-bypass.md +44 -0
  40. package/docs/rules/sandbox-escape.md +100 -0
  41. package/docs/rules/secret-detection.md +44 -0
  42. package/docs/rules/supply-chain-v2.md +92 -0
  43. package/docs/rules/suspicious-download.md +60 -0
  44. package/docs/rules/trust-boundary.md +76 -0
  45. package/docs/rules/trust-exploitation.md +92 -0
  46. package/docs/rules/unverifiable-deps.md +84 -0
  47. package/docs/rules/vdb-injection.md +84 -0
  48. package/docs/security-vulnerability-report-20260312.md +53 -0
  49. package/docs/spec/PRD_V2_ARCHITECTURE.md +55 -0
  50. package/docs/spec/capabilities.json +42 -0
  51. package/docs/spec/finding.schema.json +104 -0
  52. package/docs/spec/integration-manifest.md +39 -0
  53. package/docs/spec/sbom.json +33 -0
  54. package/docs/threat-model.md +65 -0
  55. package/docs/v13-architecture-manifest.md +55 -0
  56. package/hooks/context.js +305 -0
  57. package/hooks/guard-scanner/plugin.ts +24 -1
  58. package/openclaw-plugin.mts +91 -0
  59. package/openclaw.plugin.json +30 -53
  60. package/package.json +23 -8
  61. package/src/cli.js +174 -34
  62. package/src/core/content-loader.js +42 -0
  63. package/src/core/inventory.js +73 -0
  64. package/src/core/report-adapters.js +171 -0
  65. package/src/core/risk-engine.js +93 -0
  66. package/src/core/rule-registry.js +73 -0
  67. package/src/core/semantic-validators.js +85 -0
  68. package/src/finding-schema.js +191 -0
  69. package/src/hooks/context.ts +49 -0
  70. package/src/html-template.js +2 -2
  71. package/src/mcp-server.js +24 -73
  72. package/src/openclaw-upstream.js +128 -0
  73. package/src/patterns.js +371 -353
  74. package/src/policy-engine.js +32 -0
  75. package/src/runtime-guard.js +40 -2
  76. package/src/scanner.js +101 -216
  77. package/src/skill-crawler.js +254 -0
  78. package/src/threat-model.js +50 -0
  79. package/src/validation-layer.js +39 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * guard-scanner — HTML Report Template
3
3
  * Dark Glassmorphism + Conic-gradient Risk Gauges
4
- * Zero dependencies. Pure CSS animations.
4
+ * Lightweight HTML report template. Pure CSS animations.
5
5
  */
6
6
 
7
7
  'use strict';
@@ -229,7 +229,7 @@ ${total > 0 ? `<div class="ag">
229
229
  </div>
230
230
 
231
231
  <div class="ft">
232
- guard-scanner v${version} &mdash; Zero dependencies. Zero compromises. 🛡️<br>
232
+ guard-scanner v${version} &mdash; Lightweight runtime footprint (1 dependency: ws). 🛡️<br>
233
233
  Built by <a href="https://github.com/koatora20">Guava 🍈 &amp; Dee</a>
234
234
  </div>
235
235
  </div>
package/src/mcp-server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * guard-scanner MCP Server — Zero-dependency stdio JSON-RPC 2.0
3
+ * guard-scanner MCP Server — Lightweight stdio JSON-RPC 2.0
4
4
  *
5
5
  * @security-manifest
6
6
  * env-read: [VT_API_KEY (optional, for audit vt-scan)]
@@ -15,7 +15,7 @@
15
15
  * Implements: initialize, tools/list, tools/call, notifications
16
16
  *
17
17
  * Tools:
18
- * scan_skill — Scan a directory for security threats (166 patterns)
18
+ * scan_skill — Scan a directory for security threats
19
19
  * scan_text — Scan a code/text snippet inline
20
20
  * check_tool_call — Runtime check before a tool call (26 checks, 5 layers)
21
21
  * audit_assets — Audit npm/GitHub/ClawHub assets for exposure
@@ -30,6 +30,7 @@ const { AssetAuditor, AUDIT_VERSION } = require('./asset-auditor.js');
30
30
  const fs = require('fs');
31
31
  const path = require('path');
32
32
  const os = require('os');
33
+ const CAPABILITIES = require('../docs/spec/capabilities.json');
33
34
 
34
35
  // ── MCP Protocol Constants ──
35
36
 
@@ -45,6 +46,8 @@ const SERVER_CAPABILITIES = {
45
46
  tools: {},
46
47
  };
47
48
 
49
+ const STATIC_SUMMARY = `${CAPABILITIES.static_pattern_count} threat patterns across ${CAPABILITIES.threat_category_count} categories`;
50
+
48
51
  // ── Async Task Store (run_async / status / result / cancel) ──
49
52
 
50
53
  const TASK_DIR = process.env.GUARD_SCANNER_TASK_DIR || path.join(os.homedir(), '.openclaw', 'guard-scanner', 'tasks');
@@ -121,7 +124,7 @@ function runTaskAsync(taskId, toolName, toolArgs) {
121
124
  const TOOLS = [
122
125
  {
123
126
  name: 'scan_skill',
124
- description: 'Scan a directory for agent security threats. Detects prompt injection, data exfiltration, credential theft, reverse shells, and 166+ threat patterns across 23 categories. Returns risk score, verdict, and detailed findings.',
127
+ description: `Scan a directory for agent security threats. Detects prompt injection, data exfiltration, credential theft, reverse shells, and ${STATIC_SUMMARY}. Returns risk score, verdict, and detailed findings.`,
125
128
  inputSchema: {
126
129
  type: 'object',
127
130
  properties: {
@@ -216,8 +219,8 @@ const TOOLS = [
216
219
  },
217
220
  },
218
221
  {
219
- name: 'run_async',
220
- description: 'Run a supported guard-scanner tool asynchronously. Returns taskId immediately; use task_status/task_result to retrieve output.',
222
+ name: 'experimental.run_async',
223
+ description: '[Experimental] Run a supported guard-scanner tool asynchronously. Returns taskId immediately; use experimental.task_status/experimental.task_result to retrieve output.',
221
224
  inputSchema: {
222
225
  type: 'object',
223
226
  properties: {
@@ -228,8 +231,8 @@ const TOOLS = [
228
231
  },
229
232
  },
230
233
  {
231
- name: 'task_status',
232
- description: 'Get async task status by taskId.',
234
+ name: 'experimental.task_status',
235
+ description: '[Experimental] Get async task status by taskId.',
233
236
  inputSchema: {
234
237
  type: 'object',
235
238
  properties: { taskId: { type: 'string' } },
@@ -237,8 +240,8 @@ const TOOLS = [
237
240
  },
238
241
  },
239
242
  {
240
- name: 'task_result',
241
- description: 'Get async task final result by taskId.',
243
+ name: 'experimental.task_result',
244
+ description: '[Experimental] Get async task final result by taskId.',
242
245
  inputSchema: {
243
246
  type: 'object',
244
247
  properties: { taskId: { type: 'string' } },
@@ -246,33 +249,18 @@ const TOOLS = [
246
249
  },
247
250
  },
248
251
  {
249
- name: 'task_cancel',
250
- description: 'Cancel async task by taskId (best-effort).',
252
+ name: 'experimental.task_cancel',
253
+ description: '[Experimental] Cancel async task by taskId (best-effort).',
251
254
  inputSchema: {
252
255
  type: 'object',
253
256
  properties: { taskId: { type: 'string' }, reason: { type: 'string' } },
254
257
  required: ['taskId'],
255
258
  },
256
259
  },
257
- {
258
- name: 'cron_glm5_config',
259
- description: 'Build a safe OpenClaw cron config and CLI command using model zai/glm-5 for MCP/cron automation.',
260
- inputSchema: {
261
- type: 'object',
262
- properties: {
263
- name: { type: 'string' },
264
- cron: { type: 'string', description: '5-field cron expr (e.g. 0 7 * * *)' },
265
- tz: { type: 'string', default: 'Asia/Tokyo' },
266
- message: { type: 'string' },
267
- channel: { type: 'string', default: 'last' },
268
- to: { type: 'string' },
269
- wake: { type: 'string', enum: ['now', 'next-heartbeat'], default: 'next-heartbeat' }
270
- },
271
- required: ['name', 'cron', 'message'],
272
- },
273
- },
274
260
  ];
275
261
 
262
+ // NOTE: cron_glm5_config was removed in v14.0.0 — not guard-scanner's responsibility
263
+
276
264
  // ── Tool Handlers ──
277
265
 
278
266
  function handleScanSkill({ path: scanPath, verbose = false, strict = false }) {
@@ -357,10 +345,11 @@ function handleCheckToolCall({ tool, args, mode = 'enforce' }) {
357
345
  if (args === undefined) return errorResult('args is required');
358
346
 
359
347
  const result = scanToolCall(tool, args, { mode, auditLog: true });
348
+ const runtimeCheckCount = getCheckStats().total;
360
349
 
361
350
  if (result.detections.length === 0) {
362
351
  return successResult(
363
- `✅ Tool call "${tool}" passed all 26 runtime checks.\nMode: ${mode}`
352
+ `✅ Tool call "${tool}" passed all ${runtimeCheckCount} runtime checks.\nMode: ${mode}`
364
353
  );
365
354
  }
366
355
 
@@ -416,7 +405,7 @@ function handleGetStats() {
416
405
  return successResult(
417
406
  `🛡️ guard-scanner v${VERSION}\n\n` +
418
407
  `Static Analysis:\n` +
419
- ` • 166 threat patterns across 23 categories\n` +
408
+ ` • ${STATIC_SUMMARY}\n` +
420
409
  ` • Entropy-based secret detection\n` +
421
410
  ` • Data flow analysis (JS)\n` +
422
411
  ` • Cross-file reference checking\n` +
@@ -495,43 +484,7 @@ function handleTaskCancel({ taskId, reason = 'user cancel' }) {
495
484
  return successResult(`taskId=${taskId}\nstate=cancelled`);
496
485
  }
497
486
 
498
- function handleCronGlm5Config({ name, cron, tz = 'Asia/Tokyo', message, channel = 'last', to, wake = 'next-heartbeat' }) {
499
- if (!name || !cron || !message) return errorResult('name, cron, message are required');
500
- const parts = String(cron).trim().split(/\s+/);
501
- if (parts.length !== 5) return errorResult('cron must be 5 fields (minute hour day month weekday)');
502
-
503
- const payload = {
504
- name,
505
- schedule: { kind: 'cron', expr: cron, tz },
506
- sessionTarget: 'isolated',
507
- wakeMode: wake,
508
- payload: { kind: 'agentTurn', message, model: 'zai/glm-5' },
509
- delivery: {
510
- mode: 'announce',
511
- channel,
512
- ...(to ? { to } : {}),
513
- bestEffort: true,
514
- },
515
- };
516
-
517
- const cli = [
518
- 'openclaw cron add',
519
- `--name ${JSON.stringify(name)}`,
520
- `--cron ${JSON.stringify(cron)}`,
521
- `--tz ${JSON.stringify(tz)}`,
522
- '--session isolated',
523
- `--message ${JSON.stringify(message)}`,
524
- '--model "zai/glm-5"',
525
- `--wake ${wake}`,
526
- '--announce',
527
- `--channel ${channel}`,
528
- ...(to ? [`--to ${JSON.stringify(to)}`] : []),
529
- ].join(' \\\n ');
530
-
531
- return successResult(
532
- `cron_glm5_config_ready\n\nCLI:\n${cli}\n\nJSON:\n${JSON.stringify(payload, null, 2)}`
533
- );
534
- }
487
+ // handleCronGlm5Config removed in v14.0.0 not guard-scanner's responsibility
535
488
 
536
489
  // ── Result helpers ──
537
490
 
@@ -670,16 +623,14 @@ class MCPServer {
670
623
  return await handleAuditAssets(args);
671
624
  case 'get_stats':
672
625
  return handleGetStats();
673
- case 'run_async':
626
+ case 'experimental.run_async':
674
627
  return handleRunAsync(args);
675
- case 'task_status':
628
+ case 'experimental.task_status':
676
629
  return handleTaskStatus(args);
677
- case 'task_result':
630
+ case 'experimental.task_result':
678
631
  return handleTaskResult(args);
679
- case 'task_cancel':
632
+ case 'experimental.task_cancel':
680
633
  return handleTaskCancel(args);
681
- case 'cron_glm5_config':
682
- return handleCronGlm5Config(args);
683
634
  default:
684
635
  return errorResult(`Unknown tool: ${name}`);
685
636
  }
@@ -0,0 +1,128 @@
1
+ const https = require('node:https');
2
+
3
+ function parseVersion(version) {
4
+ const [stable, prerelease = ''] = String(version).split('-', 2);
5
+ const parts = stable.split('.').map((value) => Number.parseInt(value, 10));
6
+ while (parts.length < 3) parts.push(0);
7
+
8
+ return {
9
+ raw: version,
10
+ parts,
11
+ prerelease,
12
+ };
13
+ }
14
+
15
+ function compareOpenClawVersions(left, right) {
16
+ const a = parseVersion(left);
17
+ const b = parseVersion(right);
18
+
19
+ for (let index = 0; index < 3; index += 1) {
20
+ if (a.parts[index] > b.parts[index]) return 1;
21
+ if (a.parts[index] < b.parts[index]) return -1;
22
+ }
23
+
24
+ if (!a.prerelease && b.prerelease) return 1;
25
+ if (a.prerelease && !b.prerelease) return -1;
26
+ if (a.prerelease > b.prerelease) return 1;
27
+ if (a.prerelease < b.prerelease) return -1;
28
+ return 0;
29
+ }
30
+
31
+ function evaluateOpenClawBaseline({ pinnedVersion, latestVersion, latestPublishedAt, source }) {
32
+ const comparison = compareOpenClawVersions(pinnedVersion, latestVersion);
33
+
34
+ return {
35
+ pinnedVersion,
36
+ latestVersion,
37
+ latestPublishedAt,
38
+ source,
39
+ upToDate: comparison === 0,
40
+ ahead: comparison > 0,
41
+ behind: comparison < 0,
42
+ };
43
+ }
44
+
45
+ function normalizeGitHubReleaseVersion(tagName) {
46
+ return String(tagName).replace(/^v/i, '');
47
+ }
48
+
49
+ function evaluateOpenClawSourceParity({ npmLatestVersion, githubLatestVersion }) {
50
+ const normalizedGitHubVersion = normalizeGitHubReleaseVersion(githubLatestVersion);
51
+ return {
52
+ npmLatestVersion,
53
+ githubLatestVersion: normalizedGitHubVersion,
54
+ inParity: compareOpenClawVersions(npmLatestVersion, normalizedGitHubVersion) === 0,
55
+ };
56
+ }
57
+
58
+ function httpGetJson(url) {
59
+ return new Promise((resolve, reject) => {
60
+ https
61
+ .get(
62
+ url,
63
+ {
64
+ headers: {
65
+ 'user-agent': 'guard-scanner-openclaw-upstream-check',
66
+ accept: 'application/json',
67
+ },
68
+ },
69
+ (response) => {
70
+ let body = '';
71
+ response.setEncoding('utf8');
72
+ response.on('data', (chunk) => {
73
+ body += chunk;
74
+ });
75
+ response.on('end', () => {
76
+ if (response.statusCode && response.statusCode >= 400) {
77
+ reject(new Error(`GET ${url} failed with status ${response.statusCode}`));
78
+ return;
79
+ }
80
+ try {
81
+ resolve(JSON.parse(body));
82
+ } catch (error) {
83
+ reject(error);
84
+ }
85
+ });
86
+ },
87
+ )
88
+ .on('error', reject);
89
+ });
90
+ }
91
+
92
+ async function fetchLatestOpenClawRelease(fetchJson = httpGetJson) {
93
+ const npmMeta = await fetchJson('https://registry.npmjs.org/openclaw');
94
+ const latestVersion = npmMeta['dist-tags']?.latest;
95
+ if (!latestVersion) {
96
+ throw new Error('npm registry metadata missing dist-tags.latest for openclaw');
97
+ }
98
+
99
+ const githubRelease = await fetchJson('https://api.github.com/repos/openclaw/openclaw/releases/latest');
100
+ const githubLatestVersion = normalizeGitHubReleaseVersion(githubRelease.tag_name || '');
101
+ if (!githubLatestVersion) {
102
+ throw new Error('GitHub releases/latest missing tag_name for openclaw/openclaw');
103
+ }
104
+
105
+ const parity = evaluateOpenClawSourceParity({
106
+ npmLatestVersion: latestVersion,
107
+ githubLatestVersion,
108
+ });
109
+
110
+ return {
111
+ latestVersion,
112
+ latestPublishedAt: npmMeta.time?.[latestVersion] ?? null,
113
+ source: 'npm',
114
+ registryModifiedAt: npmMeta.time?.modified ?? null,
115
+ githubLatestVersion,
116
+ githubPublishedAt: githubRelease.published_at ?? null,
117
+ githubUrl: githubRelease.html_url ?? null,
118
+ sourceParity: parity,
119
+ };
120
+ }
121
+
122
+ module.exports = {
123
+ compareOpenClawVersions,
124
+ evaluateOpenClawBaseline,
125
+ evaluateOpenClawSourceParity,
126
+ fetchLatestOpenClawRelease,
127
+ normalizeGitHubReleaseVersion,
128
+ };