guard-scanner 1.1.1 → 2.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.
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
  <h1 align="center">🛡️ guard-scanner</h1>
3
3
  <p align="center">
4
4
  <strong>Static security scanner for AI agent skills</strong><br>
5
- Detect prompt injection, credential theft, exfiltration, identity hijacking, and 17 more threat categories.<br>
6
- <sub>Runtime Guard hook includedpending <a href="https://github.com/openclaw/openclaw/issues/18677">OpenClaw hook API adoption</a></sub>
5
+ Detect prompt injection, credential theft, exfiltration, identity hijacking, and 16 more threat categories.<br>
6
+ <sub>🆕 Plugin Hook v2.0 — <strong>actual blocking</strong> via <code>block</code>/<code>blockReason</code> API</sub>
7
7
  </p>
8
8
  <p align="center">
9
9
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
10
10
  <img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen" alt="Node.js 18+">
11
11
  <img src="https://img.shields.io/badge/dependencies-0-success" alt="Zero Dependencies">
12
- <img src="https://img.shields.io/badge/tests-55%2F55-brightgreen" alt="Tests Passing">
12
+ <img src="https://img.shields.io/badge/tests-56%2F56-brightgreen" alt="Tests Passing">
13
13
  <img src="https://img.shields.io/badge/patterns-186-orange" alt="186 Patterns">
14
14
  <img src="https://img.shields.io/badge/categories-20-blueviolet" alt="20 Categories">
15
15
  </p>
@@ -74,6 +74,18 @@ npx guard-scanner ./skills/ --strict
74
74
  npx guard-scanner ./skills/ --verbose --check-deps --json --sarif --html
75
75
  ```
76
76
 
77
+ ## OpenClaw Recommended Setup (short)
78
+
79
+ ```bash
80
+ # 1) Pre-install / pre-update static gate
81
+ npx guard-scanner ~/.openclaw/workspace/skills --self-exclude --verbose
82
+
83
+ # 2) Runtime guard — Plugin Hook version (blocks dangerous calls!)
84
+ cp hooks/guard-scanner/plugin.ts ~/.openclaw/plugins/guard-scanner-runtime.ts
85
+ ```
86
+
87
+ > **🆕 v2.0 Plugin Hook** — Uses OpenClaw's native `block`/`blockReason` API to actually prevent dangerous tool calls. Supports 3 modes: `monitor` (log only), `enforce` (block CRITICAL), `strict` (block HIGH + CRITICAL).
88
+
77
89
  ### Installation (Optional)
78
90
 
79
91
  ```bash
@@ -91,13 +103,13 @@ openclaw skill install guard-scanner
91
103
  guard-scanner ~/.openclaw/workspace/skills/ --self-exclude --verbose
92
104
  ```
93
105
 
94
- > **⚠️ Runtime Guard (handler.ts)** The real-time `before_tool_call` hook requires OpenClaw's Hook API ([Issue #18677](https://github.com/openclaw/openclaw/issues/18677)). The hook is registered and runs on `agent:before_tool_call` events, but OpenClaw's `InternalHookEvent` does not yet expose a cancel/veto mechanism so **detections are warned but not blocked**. The static scanner (`npx guard-scanner`) works fully and independently.
106
+ > **🆕 Plugin Hook version** (`plugin.ts`) uses the `before_tool_call` Plugin Hook API with `block`/`blockReason` — **detections are actually blocked**. The legacy Internal Hook version (`handler.ts`) is still available for backward compatibility but can only warn.
95
107
 
96
108
  ---
97
109
 
98
110
  ## Threat Categories
99
111
 
100
- guard-scanner covers **20 threat categories** derived from three taxonomies:
112
+ guard-scanner covers **20 threat categories** derived from four sources:
101
113
 
102
114
  | # | Category | Based On | Severity | What It Detects |
103
115
  |---|----------|----------|----------|----------------|
@@ -131,7 +143,7 @@ guard-scanner covers **20 threat categories** derived from three taxonomies:
131
143
  ### Terminal (Default)
132
144
 
133
145
  ```
134
- 🛡️ guard-scanner v1.1.0
146
+ 🛡️ guard-scanner v1.1.1
135
147
  ══════════════════════════════════════════════════════
136
148
  📂 Scanning: ./skills/
137
149
  📦 Skills found: 22
@@ -394,9 +406,12 @@ guard-scanner/
394
406
  │ └── cli.js # CLI entry point and argument parser
395
407
  ├── hooks/
396
408
  │ └── guard-scanner/
397
- └── handler.ts # Runtime Guard before_tool_call hook (experimental, pending OpenClaw API)
409
+ ├── plugin.ts # 🆕 Plugin Hook v2.0 actual blocking via block/blockReason
410
+ │ ├── handler.ts # Legacy Internal Hook — warn only (deprecated)
411
+ │ └── HOOK.md # Internal Hook manifest (legacy)
398
412
  ├── test/
399
- │ ├── scanner.test.js # 55 tests across 13 sections
413
+ │ ├── scanner.test.js # 56 tests static scanner
414
+ │ ├── plugin.test.js # 35 tests — Plugin Hook runtime guard
400
415
  │ └── fixtures/ # Malicious, clean, complex, config-changer samples
401
416
  ├── package.json # Zero dependencies, node --test
402
417
  ├── CHANGELOG.md
@@ -521,11 +536,11 @@ console.log(scanner.toHTML()); // HTML string
521
536
  ## Test Results
522
537
 
523
538
  ```
524
- ℹ tests 55
539
+ ℹ tests 56
525
540
  ℹ suites 13
526
- ℹ pass 55
541
+ ℹ pass 56
527
542
  ℹ fail 0
528
- ℹ duration_ms 115ms
543
+ ℹ duration_ms 108ms
529
544
  ```
530
545
 
531
546
  | Suite | Tests | Coverage |
@@ -556,13 +571,36 @@ console.log(scanner.toHTML()); // HTML string
556
571
 
557
572
  ---
558
573
 
574
+ ## OWASP Gen AI Top 10 Coverage
575
+
576
+ guard-scanner's coverage of the [OWASP Top 10 for LLM Applications (2025)](https://owasp.org/www-project-top-10-for-large-language-model-applications/):
577
+
578
+ | # | Risk | Status | Detection Method |
579
+ |---|------|--------|------------------|
580
+ | LLM01 | Prompt Injection | ⚠️ Partial | Regex: Unicode exploits, role override, system tags, base64 instructions |
581
+ | LLM02 | Insecure Output Handling | 🔜 v1.2 | Planned: unvalidated output execution patterns |
582
+ | LLM03 | Training Data Poisoning | ⬜ N/A | Out of scope for static analysis |
583
+ | LLM04 | Model Denial of Service | 🔜 v1.3 | Planned: excessive input / infinite loop patterns |
584
+ | LLM05 | Supply Chain Vulnerabilities | ⚠️ Partial | IoC database, typosquat detection, dependency chain scan |
585
+ | LLM06 | Sensitive Information Disclosure | ⚠️ Partial | Secret detection, PII patterns, credential leaks |
586
+ | LLM07 | Insecure Plugin Design | 🔜 v1.3 | Planned: unvalidated plugin input patterns |
587
+ | LLM08 | Excessive Agency | 🔜 v1.3 | Planned: over-permissioned scope detection |
588
+ | LLM09 | Overreliance | 🔜 v1.3 | Planned: unverified output trust patterns |
589
+ | LLM10 | Model Theft | 🔜 v1.3 | Planned: model file exfiltration patterns |
590
+
591
+ > **Current coverage: 3/10 (partial).** Full OWASP Gen AI coverage is targeted for v1.3. See [ROADMAP.md](ROADMAP.md) for details.
592
+ >
593
+ > **Known limitation:** Regex-based detection can be evaded by AI-generated code obfuscation. v2.0 will introduce AST analysis and ML-based detection to address this structural gap.
594
+
595
+ ---
596
+
559
597
  ## Contributing
560
598
 
561
599
  1. Fork the repository
562
600
  2. Create a feature branch (`git checkout -b feature/new-pattern`)
563
601
  3. Add your pattern to `src/patterns.js` with the required fields
564
602
  4. Add a test case in `test/fixtures/` and `test/scanner.test.js`
565
- 5. Run `npm test` — all 55+ tests must pass
603
+ 5. Run `npm test` — all 56+ tests must pass
566
604
  6. Submit a Pull Request
567
605
 
568
606
  ### Adding a New Detection Pattern
@@ -608,15 +646,29 @@ guard-scanner catches threats **before** installation. But what happens **after*
608
646
  | | guard-scanner (OSS) | GuavaSuite (Private) |
609
647
  |---|---|---|
610
648
  | Static scan | ✅ 20 categories | ✅ 20 categories |
611
- | Runtime blocking | ⚠️ Warn only (cancel API pending) | ✅ Real-time `before_tool_call` guard |
612
- | SOUL.md integrity | Pattern detection only | SHA-256 hash watchdog |
613
- | On-chain verification | — | SoulChain (Polygon) |
614
- | Identity recovery | — | Automatic rollback |
649
+ | Runtime blocking | Plugin Hook v2.0 (`block`/`blockReason`) | ✅ SuiteGate (enhanced ruleset) |
650
+ | SOUL.md integrity | Pattern detection only | SHA-256 hash watchdog (W4 E2E) |
651
+ | On-chain verification | — | SoulChain (Polygon, Phase 2) |
652
+ | Identity recovery | — | Automatic rollback (Phase 2) |
615
653
 
616
654
  guard-scanner is and always will be **free, open-source, and zero-dependency**. If your agent handles production workloads and you want defense-in-depth, [reach out](https://github.com/koatora20).
617
655
 
618
656
  ---
619
657
 
658
+ ## Roadmap
659
+
660
+ | Version | Focus | Key Features |
661
+ |---------|-------|------|
662
+ | v1.1.1 ✅ | Stability | 56 tests, bug fixes |
663
+ | v1.2 | PII + Shadow AI | Credential-in-context, unauthorized LLM API calls, memory poisoning vectors |
664
+ | v1.3 | OWASP Gen AI | Complete LLM02/04/07/08/09/10 coverage |
665
+ | v2.0 | AST + ML | JavaScript AST analysis, taint tracking, ML-based obfuscation detection, SBOM generation |
666
+ | v2.1 | Community | YAML pattern definitions, CONTRIBUTING guide, automated pattern updates |
667
+
668
+ See [ROADMAP.md](ROADMAP.md) for full details.
669
+
670
+ ---
671
+
620
672
  ## 💜 Sponsor This Project
621
673
 
622
674
  If guard-scanner helps protect your agents, consider sponsoring continued development:
package/SECURITY.md CHANGED
@@ -5,7 +5,7 @@
5
5
  If you discover a security vulnerability in guard-scanner itself, please report it responsibly:
6
6
 
7
7
  1. **Do NOT open a public issue**
8
- 2. Email: [security contact via GitHub private vulnerability reporting]
8
+ 2. Email: socialgreen.jp@gmail.com
9
9
  3. Include: affected version, steps to reproduce, potential impact
10
10
 
11
11
  We will respond within 48 hours and provide a fix within 7 days for critical issues.
package/SKILL.md CHANGED
@@ -54,29 +54,23 @@ Scan a specific skill:
54
54
  node skills/guard-scanner/src/cli.js /path/to/new-skill/ --strict --verbose
55
55
  ```
56
56
 
57
- ### 2. Runtime Guard (Real-time Protection) — ⚠️ Experimental
57
+ ### 2. Runtime Guard (OpenClaw) — ⚠️ warn-only currently
58
58
 
59
- > **Note:** Requires the OpenClaw Hook API ([Issue #18677](https://github.com/openclaw/openclaw/issues/18677)), which has not been officially adopted yet. The handler is included for early testing and will be updated once the API is finalized.
60
-
61
- Install the hook to block dangerous tool calls before execution:
59
+ > **Note:** OpenClaw `InternalHookEvent` does not yet expose cancel/veto. Runtime hook detections are warning + audit log until [Issue #18677](https://github.com/openclaw/openclaw/issues/18677) is adopted.
62
60
 
63
61
  ```bash
64
62
  openclaw hooks install skills/guard-scanner/hooks/guard-scanner
65
63
  openclaw hooks enable guard-scanner
64
+ openclaw hooks list
66
65
  ```
67
66
 
68
- Restart the gateway, then verify:
69
- ```bash
70
- openclaw hooks list # Should show 🛡️ guard-scanner as ✓ ready
71
- ```
72
-
73
- ### 3. Full Setup (Recommended)
67
+ ### 3. Recommended order
74
68
 
75
69
  ```bash
76
- # Static scan first
70
+ # Pre-install / pre-update gate first
77
71
  node skills/guard-scanner/src/cli.js ~/.openclaw/workspace/skills/ --verbose --self-exclude --html
78
72
 
79
- # Then enable runtime protection
73
+ # Then keep runtime monitoring enabled
80
74
  openclaw hooks install skills/guard-scanner/hooks/guard-scanner
81
75
  openclaw hooks enable guard-scanner
82
76
  ```
@@ -0,0 +1,88 @@
1
+ # OpenClaw Docs PR-Ready Patch (Reference Implementation)
2
+
3
+ Updated: 2026-02-18
4
+ Target: `docs/automation/hooks.md` (new subsection)
5
+
6
+ ## Section Title
7
+ `### Runtime Security Guard (Reference: before_tool_call)`
8
+
9
+ ## Paste-ready content
10
+
11
+ ```md
12
+ ### Runtime Security Guard (Reference: `agent:before_tool_call`)
13
+
14
+ This reference shows a backward-compatible runtime hook pattern for tool-call safety.
15
+
16
+ #### Proposed event fields (backward-compatible)
17
+
18
+ ```ts
19
+ interface InternalHookEvent {
20
+ // existing fields
21
+ cancel?: boolean; // default false
22
+ cancelReason?: string; // user-visible cancellation reason
23
+ policyMode?: "warn" | "balanced" | "strict";
24
+ }
25
+ ```
26
+
27
+ - Existing hooks remain unchanged.
28
+ - If `cancel` fields are not used/supported, behavior stays warn-only.
29
+
30
+ #### Recommended policy semantics
31
+
32
+ - `warn`: never block, only warn/log.
33
+ - `balanced`: block high-confidence dangerous patterns.
34
+ - `strict`: block any policy hit.
35
+
36
+ #### `HOOK.md`
37
+
38
+ ```md
39
+ ---
40
+ name: security-runtime-guard
41
+ description: "Reference runtime guard hook for tool-call safety"
42
+ metadata:
43
+ { "openclaw": { "emoji": "🛡️", "events": ["agent:before_tool_call"] } }
44
+ ---
45
+
46
+ # security-runtime-guard
47
+
48
+ Reference implementation for runtime tool-call policy checks.
49
+ ```
50
+
51
+ #### `handler.ts`
52
+
53
+ ```ts
54
+ import type { HookHandler } from "../../src/hooks/hooks.js";
55
+
56
+ const HIGH_RISK = [/curl\s+.*\|\s*sh/i, /reverse\s*shell/i, /169\.254\.169\.254/];
57
+
58
+ const handler: HookHandler = async (event) => {
59
+ if (event.type !== "agent" || event.action !== "before_tool_call") return;
60
+
61
+ const mode = event.policyMode ?? "warn";
62
+ const text = JSON.stringify(event.context ?? {});
63
+ const hit = HIGH_RISK.find((re) => re.test(text));
64
+ if (!hit) return;
65
+
66
+ event.messages.push(`🛡️ Runtime guard detected risky pattern: ${hit}`);
67
+
68
+ if (mode === "warn") return;
69
+
70
+ event.cancel = true;
71
+ event.cancelReason =
72
+ mode === "strict"
73
+ ? "Blocked by strict runtime policy"
74
+ : "Blocked by balanced runtime policy (high-risk pattern)";
75
+ };
76
+
77
+ export default handler;
78
+ ```
79
+
80
+ #### Operational note
81
+
82
+ If your current OpenClaw runtime is warn-only for tool-call hooks, this reference still works as observability-first policy (`warn` mode). Enforcement activates once cancel/veto is available.
83
+ ```
84
+
85
+ ## Reviewer Notes
86
+ - Keeps behavior backward-compatible.
87
+ - Encourages monitor -> enforce rollout.
88
+ - Aligned with install-time + runtime defense-in-depth guidance.
@@ -0,0 +1,78 @@
1
+ # OpenClaw Hook Schema Reference Draft (Issue #18677)
2
+
3
+ Updated: 2026-02-18
4
+
5
+ ## Goal
6
+ Provide a docs-ready, backward-compatible reference implementation for runtime security hooks.
7
+
8
+ ## Proposed Event Extension
9
+
10
+ ```ts
11
+ interface InternalHookEvent {
12
+ // existing fields
13
+ cancel?: boolean;
14
+ cancelReason?: string;
15
+ policyMode?: "warn" | "balanced" | "strict";
16
+ }
17
+ ```
18
+
19
+ ### Compatibility
20
+ - Existing hooks continue to work without changes.
21
+ - If cancel fields are absent, runtime behavior is unchanged.
22
+
23
+ ## Policy Semantics
24
+ - `warn`: never block, log + user warning only
25
+ - `balanced`: block HIGH/CRITICAL confidence matches
26
+ - `strict`: block any matched policy rule
27
+
28
+ ## Reference `HOOK.md`
29
+
30
+ ```md
31
+ ---
32
+ name: security-runtime-guard
33
+ description: "Reference runtime guard hook for tool-call safety"
34
+ metadata:
35
+ { "openclaw": { "emoji": "🛡️", "events": ["agent:before_tool_call"] } }
36
+ ---
37
+
38
+ # security-runtime-guard
39
+
40
+ Reference implementation for cancel/veto-enabled runtime checks.
41
+ ```
42
+
43
+ ## Reference `handler.ts`
44
+
45
+ ```ts
46
+ import type { HookHandler } from "../../src/hooks/hooks.js";
47
+
48
+ const HIGH_RISK = [/curl\s+.*\|\s*sh/i, /reverse\s*shell/i, /169\.254\.169\.254/];
49
+
50
+ const handler: HookHandler = async (event) => {
51
+ if (event.type !== "agent" || event.action !== "before_tool_call") return;
52
+
53
+ const mode = event.policyMode ?? "warn";
54
+ const text = JSON.stringify(event.context ?? {});
55
+ const hit = HIGH_RISK.find((re) => re.test(text));
56
+ if (!hit) return;
57
+
58
+ event.messages.push(`🛡️ Runtime guard detected risky pattern: ${hit}`);
59
+
60
+ if (mode === "warn") return;
61
+
62
+ // balanced/strict: cancellation path
63
+ event.cancel = true;
64
+ event.cancelReason =
65
+ mode === "strict"
66
+ ? "Blocked by strict runtime policy"
67
+ : "Blocked by balanced runtime policy (high-risk pattern)";
68
+ };
69
+
70
+ export default handler;
71
+ ```
72
+
73
+ ## Notes for Docs
74
+ 1. Document that some releases may still be warn-only until cancel support lands.
75
+ 2. Recommend combining install-time static scan + runtime guard:
76
+ - Install-time: `guard-scanner`
77
+ - Runtime: internal hook guard
78
+ 3. Add troubleshooting section for false positives (context-aware suppression).
@@ -0,0 +1,47 @@
1
+ # guard-scanner Research-First Tasklist (t-wada + 鉄の掟)
2
+
3
+ Updated: 2026-02-18
4
+
5
+ ## 0) Research Baseline (完了)
6
+ - [x] Semgrep rule testing practices(ruleid/okでFN/FPを分離管理)
7
+ - [x] GitHub SARIF要件(重複防止fingerprint・SARIF 2.1.0 subset)
8
+ - [x] OWASP MCP Top 10(context/protocol attack surface)
9
+ - [x] SLSA levels(provenance配布・build track段階化)
10
+
11
+ ## 1) RED — テスト仕様を先に固定
12
+ - [ ] T1: False Positive回帰テストを追加(safe skill 20ケース)
13
+ - [ ] T2: False Negative回帰テストを追加(malicious corpus 20ケース)
14
+ - [x] T3: node_modules/生成レポート由来ノイズの再現テスト
15
+ - [x] T4: SARIF妥当性テスト(schema + GitHub ingestion向け)
16
+ - [ ] T5: 性能テスト(N=10/50/100 skillsで時間とメモリ)
17
+
18
+ ## 2) GREEN — 最小実装
19
+ - [x] G1: デフォルト除外ルール(node_modules / guard-scanner-report.*)
20
+ - [ ] G2: trust-boundaryの文脈制約(docs/コメント誤検知を抑制)
21
+ - [ ] G3: entropy検知の閾値と例外改善(既知データ定数除外)
22
+ - [x] G4: SARIF fingerprint一貫性の強化(`partialFingerprints.primaryLocationLineHash` 生成)
23
+
24
+ ## 3) REFACTOR — 構造改善
25
+ - [ ] R1: 検知ルールを「攻撃シグナル」と「文脈ルール」に分離
26
+ - [ ] R2: ルール単位で precision/recall メトリクス出力
27
+ - [ ] R3: release gate(テスト全通過 + SLSA provenance確認)
28
+
29
+ ## 4) OpenClawコミュニティ最優先(即効価値)
30
+ - [x] C1: #18677 に方針返信(agent連携文脈 + cancel/veto提案)
31
+ - [x] C2: #18677 へ実装可能な最小仕様を追記(`cancel`, `cancelReason`, `policyMode`)
32
+ - [x] C3: OpenClaw公式Docs向けサンプルhook草案を作成(before_install + before_tool_call) ※ before_tool_call版ドラフト作成済み: `docs/OPENCLAW_HOOK_SCHEMA_REFERENCE_DRAFT.md`
33
+ - [x] C4: guard-scannerの「OpenClaw推奨導入手順」短縮版をREADME/SKILLに反映
34
+ - [x] C5: OpenClaw Discord向け技術共有文(宣伝色なし、再現手順中心)を作成 (`output/OPENCLAW_DISCORD_TECHNICAL_SHARE_DRAFT_2026-02-18.md`)
35
+ - [x] C6: OpenClaw docs向けPR-readyパッチ作成 + #18677に提出(`docs/OPENCLAW_DOCS_PR_READY_PATCH.md`)
36
+
37
+ ## 5) Delivery & GTM(コミュニティ同期後)
38
+ - [ ] D1: note無料記事(使い方 + 失敗例 + 回避)
39
+ - [ ] D2: X告知(SEO/LLMOワード最適化2パターンAB)
40
+ - [ ] D3: Moltbook技術投稿(検知改善ログ + フィードバック募集)
41
+
42
+ ## Exit Criteria
43
+ - FP率: 現状比 30%以上削減
44
+ - FN率: 悪性固定コーパスで95%以上検知
45
+ - 56既存テスト + 新規テスト全PASS
46
+ - SARIFをGitHub Code Scanningで取り込み確認
47
+ - OpenClawコミュニティ向け公開仕様(Issue + Docs草案)を1セット完了
@@ -1,5 +1,10 @@
1
1
  /**
2
- * guard-scanner Runtime Guard — Hook Handler
2
+ * guard-scanner Runtime Guard — Hook Handler (LEGACY)
3
+ *
4
+ * ⚠️ DEPRECATED: Use plugin.ts instead.
5
+ * This Internal Hook version can only WARN, not block.
6
+ * The Plugin Hook version (plugin.ts) uses the native
7
+ * `block` / `blockReason` API to actually prevent execution.
3
8
  *
4
9
  * Intercepts agent tool calls and checks arguments against
5
10
  * runtime threat intelligence patterns. Zero dependencies.
@@ -0,0 +1,261 @@
1
+ /**
2
+ * guard-scanner Runtime Guard — Plugin Hook Version
3
+ *
4
+ * Intercepts agent tool calls via the Plugin Hook API and blocks
5
+ * dangerous patterns using `block` / `blockReason`.
6
+ *
7
+ * Unlike the legacy Internal Hook handler (handler.ts), this version
8
+ * can ACTUALLY BLOCK tool calls, not just warn.
9
+ *
10
+ * Usage:
11
+ * Copy to ~/.openclaw/plugins/guard-scanner-runtime.ts
12
+ * Or register via openclaw plugin system.
13
+ *
14
+ * Modes:
15
+ * monitor — log only, never block
16
+ * enforce — block CRITICAL threats (default)
17
+ * strict — block HIGH + CRITICAL threats
18
+ *
19
+ * @author Guava 🍈 & Dee
20
+ * @version 2.0.0
21
+ * @license MIT
22
+ */
23
+
24
+ import { appendFileSync, mkdirSync, readFileSync } from "fs";
25
+ import { join } from "path";
26
+ import { homedir } from "os";
27
+
28
+ // ── Types (from OpenClaw src/plugins/types.ts) ──
29
+
30
+ type PluginHookBeforeToolCallEvent = {
31
+ toolName: string;
32
+ params: Record<string, unknown>;
33
+ };
34
+
35
+ type PluginHookBeforeToolCallResult = {
36
+ params?: Record<string, unknown>;
37
+ block?: boolean;
38
+ blockReason?: string;
39
+ };
40
+
41
+ type PluginHookToolContext = {
42
+ agentId?: string;
43
+ sessionKey?: string;
44
+ toolName: string;
45
+ };
46
+
47
+ type PluginAPI = {
48
+ on(
49
+ hookName: "before_tool_call",
50
+ handler: (
51
+ event: PluginHookBeforeToolCallEvent,
52
+ ctx: PluginHookToolContext
53
+ ) => PluginHookBeforeToolCallResult | void | Promise<PluginHookBeforeToolCallResult | void>
54
+ ): void;
55
+ logger: {
56
+ info: (msg: string) => void;
57
+ warn: (msg: string) => void;
58
+ error: (msg: string) => void;
59
+ };
60
+ };
61
+
62
+ // ── Runtime threat patterns (12 checks) ──
63
+
64
+ interface RuntimeCheck {
65
+ id: string;
66
+ severity: "CRITICAL" | "HIGH" | "MEDIUM";
67
+ desc: string;
68
+ test: (s: string) => boolean;
69
+ }
70
+
71
+ const RUNTIME_CHECKS: RuntimeCheck[] = [
72
+ {
73
+ id: "RT_REVSHELL",
74
+ severity: "CRITICAL",
75
+ desc: "Reverse shell attempt",
76
+ test: (s) => /\/dev\/tcp\/|nc\s+-e|ncat\s+-e|bash\s+-i\s+>&|socat\s+TCP/i.test(s),
77
+ },
78
+ {
79
+ id: "RT_CRED_EXFIL",
80
+ severity: "CRITICAL",
81
+ desc: "Credential exfiltration to external",
82
+ test: (s) =>
83
+ /(webhook\.site|requestbin\.com|hookbin\.com|pipedream\.net|ngrok\.io|socifiapp\.com)/i.test(s) &&
84
+ /(token|key|secret|password|credential|env)/i.test(s),
85
+ },
86
+ {
87
+ id: "RT_GUARDRAIL_OFF",
88
+ severity: "CRITICAL",
89
+ desc: "Guardrail disabling attempt",
90
+ test: (s) => /exec\.approvals?\s*[:=]\s*['"]?(off|false)|tools\.exec\.host\s*[:=]\s*['"]?gateway/i.test(s),
91
+ },
92
+ {
93
+ id: "RT_GATEKEEPER",
94
+ severity: "CRITICAL",
95
+ desc: "macOS Gatekeeper bypass (xattr)",
96
+ test: (s) => /xattr\s+-[crd]\s.*quarantine/i.test(s),
97
+ },
98
+ {
99
+ id: "RT_AMOS",
100
+ severity: "CRITICAL",
101
+ desc: "ClawHavoc AMOS indicator",
102
+ test: (s) => /socifiapp|Atomic\s*Stealer|AMOS/i.test(s),
103
+ },
104
+ {
105
+ id: "RT_MAL_IP",
106
+ severity: "CRITICAL",
107
+ desc: "Known malicious IP",
108
+ test: (s) => /91\.92\.242\.30/i.test(s),
109
+ },
110
+ {
111
+ id: "RT_DNS_EXFIL",
112
+ severity: "HIGH",
113
+ desc: "DNS-based exfiltration",
114
+ test: (s) => /nslookup\s+.*\$|dig\s+.*\$.*@/i.test(s),
115
+ },
116
+ {
117
+ id: "RT_B64_SHELL",
118
+ severity: "CRITICAL",
119
+ desc: "Base64 decode piped to shell",
120
+ test: (s) => /base64\s+(-[dD]|--decode)\s*\|\s*(sh|bash)/i.test(s),
121
+ },
122
+ {
123
+ id: "RT_CURL_BASH",
124
+ severity: "CRITICAL",
125
+ desc: "Download piped to shell",
126
+ test: (s) => /(curl|wget)\s+[^\n]*\|\s*(sh|bash|zsh)/i.test(s),
127
+ },
128
+ {
129
+ id: "RT_SSH_READ",
130
+ severity: "HIGH",
131
+ desc: "SSH private key access",
132
+ test: (s) => /\.ssh\/id_|\.ssh\/authorized_keys/i.test(s),
133
+ },
134
+ {
135
+ id: "RT_WALLET",
136
+ severity: "HIGH",
137
+ desc: "Crypto wallet credential access",
138
+ test: (s) => /wallet.*(?:seed|mnemonic|private.*key)|seed.*phrase/i.test(s),
139
+ },
140
+ {
141
+ id: "RT_CLOUD_META",
142
+ severity: "CRITICAL",
143
+ desc: "Cloud metadata endpoint access",
144
+ test: (s) => /169\.254\.169\.254|metadata\.google|metadata\.aws/i.test(s),
145
+ },
146
+ ];
147
+
148
+ // ── Audit logging ──
149
+
150
+ const AUDIT_DIR = join(homedir(), ".openclaw", "guard-scanner");
151
+ const AUDIT_FILE = join(AUDIT_DIR, "audit.jsonl");
152
+
153
+ function ensureAuditDir(): void {
154
+ try {
155
+ mkdirSync(AUDIT_DIR, { recursive: true });
156
+ } catch {
157
+ /* ignore */
158
+ }
159
+ }
160
+
161
+ function logAudit(entry: Record<string, unknown>): void {
162
+ ensureAuditDir();
163
+ const line = JSON.stringify({ ...entry, ts: new Date().toISOString() }) + "\n";
164
+ try {
165
+ appendFileSync(AUDIT_FILE, line);
166
+ } catch {
167
+ /* ignore */
168
+ }
169
+ }
170
+
171
+ // ── Config ──
172
+
173
+ type GuardMode = "monitor" | "enforce" | "strict";
174
+
175
+ function loadMode(): GuardMode {
176
+ try {
177
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
178
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
179
+ const mode = config?.plugins?.["guard-scanner"]?.mode;
180
+ if (mode === "monitor" || mode === "enforce" || mode === "strict") {
181
+ return mode;
182
+ }
183
+ } catch {
184
+ /* config not found or invalid — use default */
185
+ }
186
+ return "enforce";
187
+ }
188
+
189
+ function shouldBlock(severity: string, mode: GuardMode): boolean {
190
+ if (mode === "monitor") return false;
191
+ if (mode === "enforce") return severity === "CRITICAL";
192
+ if (mode === "strict") return severity === "CRITICAL" || severity === "HIGH";
193
+ return false;
194
+ }
195
+
196
+ // ── Dangerous tool filter ──
197
+
198
+ const DANGEROUS_TOOLS = new Set([
199
+ "exec",
200
+ "write",
201
+ "edit",
202
+ "browser",
203
+ "web_fetch",
204
+ "message",
205
+ "shell",
206
+ "run_command",
207
+ "multi_edit",
208
+ ]);
209
+
210
+ // ── Plugin entry point ──
211
+
212
+ export default function (api: PluginAPI) {
213
+ const mode = loadMode();
214
+ api.logger.info(`🛡️ guard-scanner runtime guard loaded (mode: ${mode})`);
215
+
216
+ api.on("before_tool_call", (event, ctx) => {
217
+ const { toolName, params } = event;
218
+
219
+ // Only check tools that can cause damage
220
+ if (!DANGEROUS_TOOLS.has(toolName)) return;
221
+
222
+ const serialized = JSON.stringify(params);
223
+
224
+ for (const check of RUNTIME_CHECKS) {
225
+ if (!check.test(serialized)) continue;
226
+
227
+ const auditEntry = {
228
+ tool: toolName,
229
+ check: check.id,
230
+ severity: check.severity,
231
+ desc: check.desc,
232
+ mode,
233
+ action: "warned" as string,
234
+ session: ctx.sessionKey || "unknown",
235
+ agent: ctx.agentId || "unknown",
236
+ };
237
+
238
+ if (shouldBlock(check.severity, mode)) {
239
+ auditEntry.action = "blocked";
240
+ logAudit(auditEntry);
241
+ api.logger.warn(
242
+ `🛡️ BLOCKED ${toolName}: ${check.desc} [${check.id}] (${check.severity})`
243
+ );
244
+
245
+ return {
246
+ block: true,
247
+ blockReason: `🛡️ guard-scanner: ${check.desc} [${check.id}]`,
248
+ };
249
+ }
250
+
251
+ // Monitor mode or severity below threshold — warn only
252
+ logAudit(auditEntry);
253
+ api.logger.warn(
254
+ `🛡️ WARNING ${toolName}: ${check.desc} [${check.id}] (${check.severity})`
255
+ );
256
+ }
257
+
258
+ // No threats detected or all below threshold — allow
259
+ return;
260
+ });
261
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guard-scanner",
3
- "version": "1.1.1",
3
+ "version": "2.0.0",
4
4
  "description": "Agent skill security scanner — detect prompt injection, malicious code, credential leaks, and 20 threat categories in AI agent skills",
5
5
  "main": "src/scanner.js",
6
6
  "bin": {
package/src/scanner.js CHANGED
@@ -24,6 +24,7 @@
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
26
  const os = require('os');
27
+ const crypto = require('crypto');
27
28
 
28
29
  const { PATTERNS } = require('./patterns.js');
29
30
  const { KNOWN_MALICIOUS } = require('./ioc-db.js');
@@ -42,6 +43,7 @@ const CODE_EXTENSIONS = new Set(['.js', '.ts', '.mjs', '.cjs', '.py', '.sh', '.b
42
43
  const DOC_EXTENSIONS = new Set(['.md', '.txt', '.rst', '.adoc']);
43
44
  const DATA_EXTENSIONS = new Set(['.json', '.yaml', '.yml', '.toml', '.xml', '.csv']);
44
45
  const BINARY_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot', '.wasm', '.wav', '.mp3', '.mp4', '.webm', '.ogg', '.pdf', '.zip', '.tar', '.gz', '.bz2', '.7z', '.exe', '.dll', '.so', '.dylib']);
46
+ const GENERATED_REPORT_FILES = new Set(['guard-scanner-report.json', 'guard-scanner-report.html', 'guard-scanner.sarif']);
45
47
 
46
48
  // Severity weights for risk scoring
47
49
  const SEVERITY_WEIGHTS = { CRITICAL: 40, HIGH: 15, MEDIUM: 5, LOW: 2 };
@@ -886,6 +888,8 @@ class GuardScanner {
886
888
  if (entry.name === '.git' || entry.name === 'node_modules') continue;
887
889
  results.push(...this.getFiles(fullPath));
888
890
  } else {
891
+ const baseName = entry.name.toLowerCase();
892
+ if (GENERATED_REPORT_FILES.has(baseName)) continue;
889
893
  results.push(fullPath);
890
894
  }
891
895
  }
@@ -971,11 +975,21 @@ class GuardScanner {
971
975
  properties: { tags: ['security', f.cat], 'security-severity': f.severity === 'CRITICAL' ? '9.0' : f.severity === 'HIGH' ? '7.0' : f.severity === 'MEDIUM' ? '4.0' : '1.0' }
972
976
  });
973
977
  }
978
+ const normalizedFile = String(f.file || '')
979
+ .replaceAll('\\', '/')
980
+ .replace(/^\/+/, '');
981
+ const artifactUri = `${skillResult.skill}/${normalizedFile}`;
982
+ const fingerprintSeed = `${f.id}|${artifactUri}|${f.line || 0}|${(f.sample || '').slice(0, 200)}`;
983
+ const lineHash = crypto.createHash('sha256').update(fingerprintSeed).digest('hex').slice(0, 24);
984
+
974
985
  results.push({
975
986
  ruleId: f.id, ruleIndex: ruleIndex[f.id],
976
987
  level: f.severity === 'CRITICAL' ? 'error' : f.severity === 'HIGH' ? 'error' : f.severity === 'MEDIUM' ? 'warning' : 'note',
977
988
  message: { text: `[${skillResult.skill}] ${f.desc}${f.sample ? ` — "${f.sample}"` : ''}` },
978
- locations: [{ physicalLocation: { artifactLocation: { uri: `${skillResult.skill}/${f.file}`, uriBaseId: '%SRCROOT%' }, region: f.line ? { startLine: f.line } : undefined } }]
989
+ partialFingerprints: {
990
+ primaryLocationLineHash: lineHash
991
+ },
992
+ locations: [{ physicalLocation: { artifactLocation: { uri: artifactUri, uriBaseId: '%SRCROOT%' }, region: f.line ? { startLine: f.line } : undefined } }]
979
993
  });
980
994
  }
981
995
  }