guard-scanner 1.1.1 → 2.1.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,16 +2,16 @@
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 included pending <a href="https://github.com/openclaw/openclaw/issues/18677">OpenClaw hook API adoption</a></sub>
5
+ Detect prompt injection, credential theft, exfiltration, PII exposure, Shadow AI, and 17 more threat categories.<br>
6
+ <sub>🆕 v2.1 PII Exposure Detection + Shadow AI + Plugin Hook blocking 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">
13
- <img src="https://img.shields.io/badge/patterns-186-orange" alt="186 Patterns">
14
- <img src="https://img.shields.io/badge/categories-20-blueviolet" alt="20 Categories">
12
+ <img src="https://img.shields.io/badge/tests-99%2F99-brightgreen" alt="Tests Passing">
13
+ <img src="https://img.shields.io/badge/patterns-129-orange" alt="129 Patterns">
14
+ <img src="https://img.shields.io/badge/categories-21-blueviolet" alt="21 Categories">
15
15
  </p>
16
16
  </p>
17
17
 
@@ -40,8 +40,8 @@ The AI agent skill ecosystem has the same supply-chain security problem that npm
40
40
 
41
41
  | Feature | Description |
42
42
  |---|---|
43
- | **20 Threat Categories** | Snyk ToxicSkills + OWASP MCP Top 10 + Identity Hijacking + Sandbox/Complexity/Config |
44
- | **186 Detection Patterns** | Regex-based static analysis covering code, docs, and data files |
43
+ | **21 Threat Categories** | Snyk ToxicSkills + OWASP MCP Top 10 + Identity Hijacking + Sandbox/Complexity/Config + PII |
44
+ | **129 Detection Patterns** | Regex-based static analysis covering code, docs, and data files |
45
45
  | **IoC Database** | Known malicious IPs, domains, URLs, usernames, and typosquat names |
46
46
  | **Data Flow Analysis** | Lightweight JS analysis: secret reads → network calls → exec chains |
47
47
  | **Cross-File Analysis** | Phantom references, base64 fragment assembly, multi-file exfil detection |
@@ -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.1** — PII Exposure Detection (OWASP LLM02/06) + Shadow AI detection + Plugin Hook `block`/`blockReason` API. 3 modes: `monitor`, `enforce`, `strict`.
88
+
77
89
  ### Installation (Optional)
78
90
 
79
91
  ```bash
@@ -87,17 +99,17 @@ npx guard-scanner ./skills/
87
99
  ### As an OpenClaw Skill
88
100
 
89
101
  ```bash
90
- openclaw skill install guard-scanner
102
+ clawhub 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 **21 threat categories** derived from four sources:
101
113
 
102
114
  | # | Category | Based On | Severity | What It Detects |
103
115
  |---|----------|----------|----------|----------------|
@@ -121,8 +133,9 @@ guard-scanner covers **20 threat categories** derived from three taxonomies:
121
133
  | 18 | **Sandbox Validation** | v1.1 | HIGH | Dangerous binary requirements in SKILL.md, overly broad file scope, sensitive env vars, exec/network declarations |
122
134
  | 19 | **Code Complexity** | v1.1 | MEDIUM | Excessive file length (>1000 lines), deep nesting (>5 levels), high eval/exec density |
123
135
  | 20 | **Config Impact** | v1.1 | CRITICAL | `openclaw.json` writes, exec approval bypass, exec host gateway, internal hooks modification, network wildcard |
136
+ | 21 | **PII Exposure** | v2.1 | CRITICAL | Hardcoded CC/SSN/phone/email (context-aware), PII logging/network send/plaintext store, Shadow AI (OpenAI/Anthropic/generic LLM), PII collection instructions (address/DOB/government ID) |
124
137
 
125
- > **Categories 17–20** are unique to guard-scanner. Category 17 (Identity Hijacking) was developed from a real attack. Categories 18–20 were added in v1.1.0 based on community feedback.
138
+ > **Categories 17–21** are unique to guard-scanner. Category 17 (Identity Hijacking) was developed from a real attack. Categories 18–20 added in v1.1.0. Category 21 (PII Exposure) added in v2.1.0 covering OWASP LLM02/LLM06.
126
139
 
127
140
  ---
128
141
 
@@ -131,7 +144,7 @@ guard-scanner covers **20 threat categories** derived from three taxonomies:
131
144
  ### Terminal (Default)
132
145
 
133
146
  ```
134
- 🛡️ guard-scanner v1.1.0
147
+ 🛡️ guard-scanner v2.1.0
135
148
  ══════════════════════════════════════════════════════
136
149
  📂 Scanning: ./skills/
137
150
  📦 Skills found: 22
@@ -216,6 +229,9 @@ Certain combinations multiply the base score:
216
229
  | Config impact | **×2** | OpenClaw configuration tampering |
217
230
  | Config impact + Sandbox violation | **min 70** | Combined config + capability abuse |
218
231
  | Complexity + Malicious code/Obfuscation | **×1.5** | Complex code hiding threats |
232
+ | PII exposure + Exfiltration | **×3** | PII being sent to external servers |
233
+ | PII exposure + Shadow AI | **×2.5** | PII leak through unauthorized LLM |
234
+ | PII exposure + Credential handling | **×2** | Combined PII + credential risk |
219
235
  | Known IoC (IP/URL/typosquat) | **= 100** | Confirmed malicious |
220
236
 
221
237
  ### Verdict Thresholds
@@ -388,16 +404,19 @@ Options:
388
404
  ```
389
405
  guard-scanner/
390
406
  ├── src/
391
- │ ├── scanner.js # GuardScanner class — core scan engine (20 checks)
392
- │ ├── patterns.js # 186 threat detection patterns (Cat 1–20)
407
+ │ ├── scanner.js # GuardScanner class — core scan engine (21 checks)
408
+ │ ├── patterns.js # 129 threat detection patterns (Cat 1–21)
393
409
  │ ├── ioc-db.js # Indicators of Compromise database
394
410
  │ └── cli.js # CLI entry point and argument parser
395
411
  ├── hooks/
396
412
  │ └── guard-scanner/
397
- └── handler.ts # Runtime Guard before_tool_call hook (experimental, pending OpenClaw API)
413
+ ├── plugin.ts # 🆕 Plugin Hook v2.0 actual blocking via block/blockReason
414
+ │ ├── handler.ts # Legacy Internal Hook — warn only (deprecated)
415
+ │ └── HOOK.md # Internal Hook manifest (legacy)
398
416
  ├── test/
399
- │ ├── scanner.test.js # 55 tests across 13 sections
400
- └── fixtures/ # Malicious, clean, complex, config-changer samples
417
+ │ ├── scanner.test.js # 64 tests static scanner (incl. PII v2.1)
418
+ ├── plugin.test.js # 35 tests Plugin Hook runtime guard
419
+ │ └── fixtures/ # Malicious, clean, complex, config-changer, pii-leaky samples
401
420
  ├── package.json # Zero dependencies, node --test
402
421
  ├── CHANGELOG.md
403
422
  ├── LICENSE # MIT
@@ -521,11 +540,11 @@ console.log(scanner.toHTML()); // HTML string
521
540
  ## Test Results
522
541
 
523
542
  ```
524
- ℹ tests 55
525
- ℹ suites 13
526
- ℹ pass 55
543
+ ℹ tests 99
544
+ ℹ suites 16
545
+ ℹ pass 99
527
546
  ℹ fail 0
528
- ℹ duration_ms 115ms
547
+ ℹ duration_ms 142ms
529
548
  ```
530
549
 
531
550
  | Suite | Tests | Coverage |
@@ -535,14 +554,34 @@ console.log(scanner.toHTML()); // HTML string
535
554
  | Risk Score Calculation | 5 | Empty, single, combo amplifiers, IoC override |
536
555
  | Verdict Determination | 5 | All verdicts + strict mode |
537
556
  | Output Formats | 4 | JSON + SARIF 2.1.0 + HTML structure |
538
- | Pattern Database | 4 | 100+ count, required fields, category coverage, regex safety |
557
+ | Pattern Database | 4 | 125+ count, required fields, category coverage, regex safety |
539
558
  | IoC Database | 5 | Structure, ClawHavoc C2, webhook.site |
540
559
  | Shannon Entropy | 2 | Low entropy, high entropy |
541
560
  | Ignore Functionality | 1 | Pattern exclusion |
542
561
  | Plugin API | 1 | Plugin loading + custom rule injection |
543
- | **Manifest Validation (v1.1)** | 4 | Dangerous bins, broad files, sensitive env, clean negatives |
544
- | **Complexity Metrics (v1.1)** | 2 | Deep nesting, clean negatives |
545
- | **Config Impact (v1.1)** | 4 | openclaw.json write, exec approval, gateway host, clean negatives |
562
+ | Manifest Validation | 4 | Dangerous bins, broad files, sensitive env, clean negatives |
563
+ | Complexity Metrics | 2 | Deep nesting, clean negatives |
564
+ | Config Impact | 4 | openclaw.json write, exec approval, gateway host, clean negatives |
565
+ | **🆕 PII Exposure Detection** | **8** | **Hardcoded CC/SSN, PII logging, network send, Shadow AI, doc collection, risk amp, clean negatives** |
566
+ | **Plugin Hook Runtime Guard** | **35** | **Blocking in enforce/strict, passthrough in monitor, all 12 threat patterns, blockReason format** |
567
+
568
+ ---
569
+
570
+ ## Fills OpenClaw's Own Security Gaps
571
+
572
+ OpenClaw's official [`THREAT-MODEL-ATLAS.md`](https://github.com/openclaw/openclaw/blob/main/docs/security/THREAT-MODEL-ATLAS.md) identifies security gaps that guard-scanner directly addresses:
573
+
574
+ | Gap (from ATLAS / Source Code) | OpenClaw Status | guard-scanner |
575
+ |---|---|---|
576
+ | _"Simple regex easily bypassed"_ — ClawHub moderation | ⚠️ Basic `FLAG_RULES` | ✅ 129 patterns, 21 categories |
577
+ | _"Does not analyze actual skill code content"_ | ❌ Not implemented | ✅ Full code + doc + data flow analysis |
578
+ | No SOUL.md / IDENTITY.md integrity verification | ❌ Not implemented | ✅ Identity hijacking detection (Cat 17) |
579
+ | `skill:before_install` hook | ❌ Not implemented | 🔜 Proposed ([Issue #18677](https://github.com/openclaw/openclaw/issues/18677)) |
580
+ | `before_tool_call` blocking reference impl | ❌ No official plugin | ✅ First reference implementation (plugin.ts) |
581
+ | SARIF / CI integration for skill security | ❌ Not available | ✅ SARIF 2.1.0 + GitHub Actions |
582
+ | Behavioral analysis beyond VirusTotal | ⏳ In progress | ✅ LLM-specific threat patterns (prompt injection, memory poisoning, MCP attacks) |
583
+
584
+ > guard-scanner is **complementary** to OpenClaw's built-in security — not a replacement. OpenClaw handles infrastructure security (SSRF blocking, exec approvals, sandbox, auth). guard-scanner handles **AI-specific threats** that traditional scanning misses.
546
585
 
547
586
  ---
548
587
 
@@ -556,13 +595,36 @@ console.log(scanner.toHTML()); // HTML string
556
595
 
557
596
  ---
558
597
 
598
+ ## OWASP Gen AI Top 10 Coverage
599
+
600
+ 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/):
601
+
602
+ | # | Risk | Status | Detection Method |
603
+ |---|------|--------|------------------|
604
+ | LLM01 | Prompt Injection | ⚠️ Partial | Regex: Unicode exploits, role override, system tags, base64 instructions |
605
+ | LLM02 | Sensitive Information Disclosure | ⚠️ Partial | PII Exposure Detection (v2.1): hardcoded PII, PII logging/network/storage, Shadow AI, PII collection instructions |
606
+ | LLM03 | Training Data Poisoning | ⬜ N/A | Out of scope for static analysis |
607
+ | LLM04 | Model Denial of Service | 🔜 v2.2 | Planned: excessive input / infinite loop patterns |
608
+ | LLM05 | Supply Chain Vulnerabilities | ⚠️ Partial | IoC database, typosquat detection, dependency chain scan |
609
+ | LLM06 | Insecure Output Handling | ⚠️ Partial | PII output detection (console.log, network send, plaintext store) |
610
+ | LLM07 | Insecure Plugin Design | 🔜 v1.3 | Planned: unvalidated plugin input patterns |
611
+ | LLM08 | Excessive Agency | 🔜 v1.3 | Planned: over-permissioned scope detection |
612
+ | LLM09 | Overreliance | 🔜 v1.3 | Planned: unverified output trust patterns |
613
+ | LLM10 | Model Theft | 🔜 v1.3 | Planned: model file exfiltration patterns |
614
+
615
+ > **Current coverage: 5/10 (partial).** LLM02 and LLM06 added in v2.1.0. Full coverage targeted for v3.0. See [ROADMAP.md](ROADMAP.md) for details.
616
+ >
617
+ > **Known limitation:** Regex-based detection can be evaded by AI-generated code obfuscation. v3.0 will introduce AST analysis and ML-based detection to address this structural gap.
618
+
619
+ ---
620
+
559
621
  ## Contributing
560
622
 
561
623
  1. Fork the repository
562
624
  2. Create a feature branch (`git checkout -b feature/new-pattern`)
563
625
  3. Add your pattern to `src/patterns.js` with the required fields
564
626
  4. Add a test case in `test/fixtures/` and `test/scanner.test.js`
565
- 5. Run `npm test` — all 55+ tests must pass
627
+ 5. Run `npm test` — all 99+ tests must pass
566
628
  6. Submit a Pull Request
567
629
 
568
630
  ### Adding a New Detection Pattern
@@ -601,19 +663,48 @@ We built one.
601
663
 
602
664
  ## 🔒 Need More? — GuavaSuite
603
665
 
604
- guard-scanner catches threats **before** installation. But what happens **after** a skill is running?
666
+ guard-scanner catches threats **before** installation and **blocks** CRITICAL threats at runtime. **GuavaSuite** unlocks **strict mode** — blocking HIGH + CRITICAL threats, plus exclusive defense-in-depth features.
605
667
 
606
- [**GuavaSuite**](https://github.com/koatora20) extends guard-scanner with real-time protection for production agent deployments:
668
+ ### How to Upgrade
607
669
 
608
- | | guard-scanner (OSS) | GuavaSuite (Private) |
670
+ ```bash
671
+ # 1. Install GuavaSuite
672
+ clawhub install guava-suite
673
+
674
+ # 2. Hold 1M+ $GUAVA on Polygon
675
+ # Token: 0x25cBD481901990bF0ed2ff9c5F3C0d4f743AC7B8
676
+ # Buy on QuickSwap V2: https://quickswap.exchange
677
+
678
+ # 3. Activate with your wallet → get JWT → strict mode enabled
679
+ ```
680
+
681
+ ### Feature Comparison
682
+
683
+ | | guard-scanner (Free) | GuavaSuite ($GUAVA) |
609
684
  |---|---|---|
610
- | 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 |
685
+ | Static scan (129 patterns, 21 categories) | ✅ | |
686
+ | Runtime Guard `enforce` (block CRITICAL) | ✅ | |
687
+ | **Runtime Guard `strict` (block HIGH + CRITICAL)** | | |
688
+ | **Soul Lock** (SOUL.md integrity + auto-rollback) | | ✅ |
689
+ | **Memory Guard** (L1-L5 記憶保護) | | ✅ |
690
+ | **On-chain Identity** (SoulRegistry V2 on Polygon) | ❌ | ✅ |
691
+ | Audit Log (JSONL) | ✅ | ✅ |
692
+
693
+ guard-scanner is and always will be **free, open-source, and zero-dependency**.
694
+
695
+ ---
696
+
697
+ ## Roadmap
698
+
699
+ | Version | Focus | Key Features |
700
+ |---------|-------|------|
701
+ | v1.1.1 ✅ | Stability | 56 tests, bug fixes |
702
+ | v2.0.0 ✅ | **Plugin Hook Runtime Guard** | `block`/`blockReason` API, 3 modes (monitor/enforce/strict), 91 tests |
703
+ | v2.1.0 ✅ | **PII Exposure + Shadow AI** | 13 PII patterns, OWASP LLM02/06, Shadow AI detection, 3 risk amplifiers, 99 tests |
704
+ | v2.2 | OWASP Full Coverage | LLM04/07/08/09/10, YAML pattern definitions, CONTRIBUTING guide |
705
+ | v3.0 | AST + ML | JavaScript AST analysis, taint tracking, ML-based obfuscation detection, SBOM generation |
615
706
 
616
- 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).
707
+ See [ROADMAP.md](ROADMAP.md) for full details.
617
708
 
618
709
  ---
619
710
 
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,303 @@
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
+ const SUITE_TOKEN_FILE = join(homedir(), ".openclaw", "guava-suite", "token.jwt");
176
+
177
+ /**
178
+ * Check if GuavaSuite JWT exists and hasn't expired.
179
+ * Why: Lightweight check without jsonwebtoken dependency — just decode base64 payload.
180
+ * Full JWT signature verification happens at activation time in activate.js.
181
+ */
182
+ function isSuiteActive(): boolean {
183
+ try {
184
+ const token = readFileSync(SUITE_TOKEN_FILE, "utf8").trim();
185
+ if (!token) return false;
186
+
187
+ // Decode JWT payload (base64url → JSON)
188
+ const parts = token.split(".");
189
+ if (parts.length !== 3) return false;
190
+
191
+ const payload = JSON.parse(
192
+ Buffer.from(parts[1], "base64url").toString("utf8")
193
+ );
194
+
195
+ // Check expiry
196
+ if (payload.exp && payload.exp * 1000 < Date.now()) return false;
197
+
198
+ // Check scope
199
+ return payload.scope === "suite";
200
+ } catch {
201
+ return false;
202
+ }
203
+ }
204
+
205
+ function loadMode(): GuardMode {
206
+ // Priority 1: GuavaSuite JWT token → strict
207
+ if (isSuiteActive()) {
208
+ return "strict";
209
+ }
210
+
211
+ // Priority 2: explicit config in openclaw.json
212
+ try {
213
+ const configPath = join(homedir(), ".openclaw", "openclaw.json");
214
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
215
+
216
+ // Check suiteEnabled flag (set by activate.js)
217
+ if (config?.plugins?.["guard-scanner"]?.suiteEnabled === true) {
218
+ return "strict";
219
+ }
220
+
221
+ const mode = config?.plugins?.["guard-scanner"]?.mode;
222
+ if (mode === "monitor" || mode === "enforce" || mode === "strict") {
223
+ return mode;
224
+ }
225
+ } catch {
226
+ /* config not found or invalid — use default */
227
+ }
228
+ return "enforce";
229
+ }
230
+
231
+ function shouldBlock(severity: string, mode: GuardMode): boolean {
232
+ if (mode === "monitor") return false;
233
+ if (mode === "enforce") return severity === "CRITICAL";
234
+ if (mode === "strict") return severity === "CRITICAL" || severity === "HIGH";
235
+ return false;
236
+ }
237
+
238
+ // ── Dangerous tool filter ──
239
+
240
+ const DANGEROUS_TOOLS = new Set([
241
+ "exec",
242
+ "write",
243
+ "edit",
244
+ "browser",
245
+ "web_fetch",
246
+ "message",
247
+ "shell",
248
+ "run_command",
249
+ "multi_edit",
250
+ ]);
251
+
252
+ // ── Plugin entry point ──
253
+
254
+ export default function (api: PluginAPI) {
255
+ const mode = loadMode();
256
+ api.logger.info(`🛡️ guard-scanner runtime guard loaded (mode: ${mode})`);
257
+
258
+ api.on("before_tool_call", (event, ctx) => {
259
+ const { toolName, params } = event;
260
+
261
+ // Only check tools that can cause damage
262
+ if (!DANGEROUS_TOOLS.has(toolName)) return;
263
+
264
+ const serialized = JSON.stringify(params);
265
+
266
+ for (const check of RUNTIME_CHECKS) {
267
+ if (!check.test(serialized)) continue;
268
+
269
+ const auditEntry = {
270
+ tool: toolName,
271
+ check: check.id,
272
+ severity: check.severity,
273
+ desc: check.desc,
274
+ mode,
275
+ action: "warned" as string,
276
+ session: ctx.sessionKey || "unknown",
277
+ agent: ctx.agentId || "unknown",
278
+ };
279
+
280
+ if (shouldBlock(check.severity, mode)) {
281
+ auditEntry.action = "blocked";
282
+ logAudit(auditEntry);
283
+ api.logger.warn(
284
+ `🛡️ BLOCKED ${toolName}: ${check.desc} [${check.id}] (${check.severity})`
285
+ );
286
+
287
+ return {
288
+ block: true,
289
+ blockReason: `🛡️ guard-scanner: ${check.desc} [${check.id}]`,
290
+ };
291
+ }
292
+
293
+ // Monitor mode or severity below threshold — warn only
294
+ logAudit(auditEntry);
295
+ api.logger.warn(
296
+ `🛡️ WARNING ${toolName}: ${check.desc} [${check.id}] (${check.severity})`
297
+ );
298
+ }
299
+
300
+ // No threats detected or all below threshold — allow
301
+ return;
302
+ });
303
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "guard-scanner",
3
- "version": "1.1.1",
4
- "description": "Agent skill security scanner — detect prompt injection, malicious code, credential leaks, and 20 threat categories in AI agent skills",
3
+ "version": "2.1.0",
4
+ "description": "Agent skill security scanner — detect prompt injection, malicious code, credential leaks, PII exposure, Shadow AI, and 21 threat categories in AI agent skills",
5
5
  "main": "src/scanner.js",
6
6
  "bin": {
7
7
  "guard-scanner": "src/cli.js"
package/src/patterns.js CHANGED
@@ -185,6 +185,28 @@ const PATTERNS = [
185
185
  { id: 'CFG_EXEC_HOST_GW', cat: 'config-impact', regex: /tools\.exec\.host\s*[:=]\s*['"]gateway['"]/gi, severity: 'CRITICAL', desc: 'Set exec host to gateway (bypass sandbox)', all: true },
186
186
  { id: 'CFG_SANDBOX_OFF', cat: 'config-impact', regex: /(?:sandbox|sandboxed|containerized)\s*[:=]\s*(?:false|off|none|disabled|0)/gi, severity: 'CRITICAL', desc: 'Disable sandbox via configuration', all: true },
187
187
  { id: 'CFG_TOOL_OVERRIDE', cat: 'config-impact', regex: /(?:tools|capabilities)\s*\.\s*(?:exec|write|browser|web_fetch)\s*[:=]\s*\{[^}]*(?:enabled|allowed|host)/gi, severity: 'HIGH', desc: 'Override tool security settings', codeOnly: true },
188
+
189
+ // ── Category 21: PII Exposure (OWASP LLM02 / LLM06) ──
190
+ // A. Hardcoded PII — actual PII values in code/config (context-aware to reduce FP)
191
+ { id: 'PII_HARDCODED_CC', cat: 'pii-exposure', regex: /(?:card|cc|credit|payment|pan)[_\s.-]*(?:num|number|no)?\s*[:=]\s*['"`]\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{3,4}['"`]/gi, severity: 'CRITICAL', desc: 'Hardcoded credit card number', codeOnly: true },
192
+ { id: 'PII_HARDCODED_SSN', cat: 'pii-exposure', regex: /(?:ssn|social[_\s-]*security|tax[_\s-]*id)\s*[:=]\s*['"`]\d{3}-?\d{2}-?\d{4}['"`]/gi, severity: 'CRITICAL', desc: 'Hardcoded SSN/tax ID', codeOnly: true },
193
+ { id: 'PII_HARDCODED_PHONE', cat: 'pii-exposure', regex: /(?:phone|tel|mobile|cell|fax)[_\s.-]*(?:num|number|no)?\s*[:=]\s*['"`][+]?[\d\s().-]{7,20}['"`]/gi, severity: 'HIGH', desc: 'Hardcoded phone number', codeOnly: true },
194
+ { id: 'PII_HARDCODED_EMAIL', cat: 'pii-exposure', regex: /(?:email|e-mail|user[_\s-]*mail|contact)\s*[:=]\s*['"`][a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}['"`]/gi, severity: 'HIGH', desc: 'Hardcoded email address', codeOnly: true },
195
+
196
+ // B. PII output/logging — code that outputs or transmits PII-like variables
197
+ { id: 'PII_LOG_SENSITIVE', cat: 'pii-exposure', regex: /(?:console\.log|console\.info|console\.warn|logger?\.\w+|print|puts)\s*\([^)]*\b(?:ssn|social_security|credit_card|card_number|cvv|cvc|passport|tax_id|date_of_birth|dob)\b/gi, severity: 'HIGH', desc: 'PII variable logged to console', codeOnly: true },
198
+ { id: 'PII_SEND_NETWORK', cat: 'pii-exposure', regex: /(?:fetch|axios|request|http|post|put|send)\s*\([^)]*\b(?:ssn|social_security|credit_card|card_number|cvv|passport|bank_account|routing_number)\b/gi, severity: 'CRITICAL', desc: 'PII variable sent over network', codeOnly: true },
199
+ { id: 'PII_STORE_PLAINTEXT', cat: 'pii-exposure', regex: /(?:writeFile|writeFileSync|appendFile|fs\.write|fwrite)\s*\([^)]*\b(?:ssn|social_security|credit_card|card_number|cvv|passport|tax_id|bank_account)\b/gi, severity: 'HIGH', desc: 'PII stored in plaintext file', codeOnly: true },
200
+
201
+ // C. Shadow AI — unauthorized LLM API calls (data leaks to external AI)
202
+ { id: 'SHADOW_AI_OPENAI', cat: 'pii-exposure', regex: /(?:api\.openai\.com|https:\/\/api\.openai\.com)\s*|openai\.(?:chat|completions|ChatCompletion)/gi, severity: 'HIGH', desc: 'Shadow AI: OpenAI API call', codeOnly: true },
203
+ { id: 'SHADOW_AI_ANTHROPIC', cat: 'pii-exposure', regex: /(?:api\.anthropic\.com|https:\/\/api\.anthropic\.com)\s*|anthropic\.(?:messages|completions)/gi, severity: 'HIGH', desc: 'Shadow AI: Anthropic API call', codeOnly: true },
204
+ { id: 'SHADOW_AI_GENERIC', cat: 'pii-exposure', regex: /\/v1\/(?:chat\/completions|completions|embeddings|models)\b.*(?:fetch|axios|request|http)|(?:fetch|axios|request|http)\s*\([^)]*\/v1\/(?:chat\/completions|completions|embeddings)/gi, severity: 'MEDIUM', desc: 'Shadow AI: generic LLM API endpoint', codeOnly: true },
205
+
206
+ // D. PII collection instructions in docs (extends LEAK_COLLECT_PII)
207
+ { id: 'PII_ASK_ADDRESS', cat: 'pii-exposure', regex: /(?:collect|ask\s+for|request|get|require)\s+(?:the\s+)?(?:user'?s?\s+)?(?:home\s+)?(?:address|street|zip\s*code|postal\s*code|residence)/gi, severity: 'HIGH', desc: 'PII collection: home address', docOnly: true },
208
+ { id: 'PII_ASK_DOB', cat: 'pii-exposure', regex: /(?:collect|ask\s+for|request|get|require)\s+(?:the\s+)?(?:user'?s?\s+)?(?:date\s+of\s+birth|birth\s*date|birthday|DOB|age)/gi, severity: 'HIGH', desc: 'PII collection: date of birth', docOnly: true },
209
+ { id: 'PII_ASK_GOV_ID', cat: 'pii-exposure', regex: /(?:collect|ask\s+for|request|get|require)\s+(?:the\s+)?(?:user'?s?\s+)?(?:passport|driver'?s?\s+licen[sc]e|national\s+id|my\s*number|マイナンバー|国民健康保険|social\s+insurance)/gi, severity: 'CRITICAL', desc: 'PII collection: government ID', docOnly: true },
188
210
  ];
189
211
 
190
212
  module.exports = { PATTERNS };
package/src/scanner.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * guard-scanner v1.0.0 — Agent Skill Security Scanner 🛡️
3
+ * guard-scanner v2.1.0 — Agent Skill Security Scanner 🛡️
4
4
  *
5
5
  * @security-manifest
6
6
  * env-read: []
@@ -24,13 +24,14 @@
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');
30
31
  const { generateHTML } = require('./html-template.js');
31
32
 
32
33
  // ===== CONFIGURATION =====
33
- const VERSION = '1.1.0';
34
+ const VERSION = '2.1.0';
34
35
 
35
36
  const THRESHOLDS = {
36
37
  normal: { suspicious: 30, malicious: 80 },
@@ -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 };
@@ -866,6 +868,11 @@ class GuardScanner {
866
868
  if (cats.has('config-impact') && cats.has('sandbox-validation')) score = Math.max(score, 70);
867
869
  if (cats.has('complexity') && (cats.has('malicious-code') || cats.has('obfuscation'))) score = Math.round(score * 1.5);
868
870
 
871
+ // v2.1 PII exposure amplifiers
872
+ if (cats.has('pii-exposure') && cats.has('exfiltration')) score = Math.round(score * 3);
873
+ if (cats.has('pii-exposure') && (ids.has('SHADOW_AI_OPENAI') || ids.has('SHADOW_AI_ANTHROPIC') || ids.has('SHADOW_AI_GENERIC'))) score = Math.round(score * 2.5);
874
+ if (cats.has('pii-exposure') && cats.has('credential-handling')) score = Math.round(score * 2);
875
+
869
876
  return Math.min(100, score);
870
877
  }
871
878
 
@@ -886,6 +893,8 @@ class GuardScanner {
886
893
  if (entry.name === '.git' || entry.name === 'node_modules') continue;
887
894
  results.push(...this.getFiles(fullPath));
888
895
  } else {
896
+ const baseName = entry.name.toLowerCase();
897
+ if (GENERATED_REPORT_FILES.has(baseName)) continue;
889
898
  results.push(fullPath);
890
899
  }
891
900
  }
@@ -939,6 +948,7 @@ class GuardScanner {
939
948
  if (cats.has('sandbox-validation')) skillRecs.push('🔒 SANDBOX: Skill requests dangerous capabilities.');
940
949
  if (cats.has('complexity')) skillRecs.push('🧩 COMPLEXITY: Excessive code complexity may hide malicious behavior.');
941
950
  if (cats.has('config-impact')) skillRecs.push('⚙️ CONFIG IMPACT: Modifies OpenClaw configuration. DO NOT INSTALL.');
951
+ if (cats.has('pii-exposure')) skillRecs.push('🆔 PII EXPOSURE: Handles personally identifiable information. Review data handling.');
942
952
 
943
953
  if (skillRecs.length > 0) recommendations.push({ skill: skillResult.skill, actions: skillRecs });
944
954
  }
@@ -971,11 +981,21 @@ class GuardScanner {
971
981
  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
982
  });
973
983
  }
984
+ const normalizedFile = String(f.file || '')
985
+ .replaceAll('\\', '/')
986
+ .replace(/^\/+/, '');
987
+ const artifactUri = `${skillResult.skill}/${normalizedFile}`;
988
+ const fingerprintSeed = `${f.id}|${artifactUri}|${f.line || 0}|${(f.sample || '').slice(0, 200)}`;
989
+ const lineHash = crypto.createHash('sha256').update(fingerprintSeed).digest('hex').slice(0, 24);
990
+
974
991
  results.push({
975
992
  ruleId: f.id, ruleIndex: ruleIndex[f.id],
976
993
  level: f.severity === 'CRITICAL' ? 'error' : f.severity === 'HIGH' ? 'error' : f.severity === 'MEDIUM' ? 'warning' : 'note',
977
994
  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 } }]
995
+ partialFingerprints: {
996
+ primaryLocationLineHash: lineHash
997
+ },
998
+ locations: [{ physicalLocation: { artifactLocation: { uri: artifactUri, uriBaseId: '%SRCROOT%' }, region: f.line ? { startLine: f.line } : undefined } }]
979
999
  });
980
1000
  }
981
1001
  }