@elliotllliu/agentshield 0.1.0 ā 0.2.1
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 +207 -66
- package/README.zh-CN.md +129 -0
- package/dist/cli.js +175 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +24 -0
- package/dist/config.js +91 -0
- package/dist/config.js.map +1 -0
- package/dist/reporter/badge.d.ts +7 -0
- package/dist/reporter/badge.js +50 -0
- package/dist/reporter/badge.js.map +1 -0
- package/dist/rules/index.js +2 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/mcp-manifest.d.ts +2 -0
- package/dist/rules/mcp-manifest.js +195 -0
- package/dist/rules/mcp-manifest.js.map +1 -0
- package/dist/scanner/index.d.ts +2 -2
- package/dist/scanner/index.js +33 -3
- package/dist/scanner/index.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/yaml-simple.d.ts +6 -0
- package/dist/yaml-simple.js +98 -0
- package/dist/yaml-simple.js.map +1 -0
- package/package.json +42 -3
package/README.md
CHANGED
|
@@ -1,34 +1,81 @@
|
|
|
1
1
|
# š”ļø AgentShield
|
|
2
2
|
|
|
3
|
-
Security scanner for AI agent skills, MCP servers, and plugins
|
|
3
|
+
**Security scanner for AI agent skills, MCP servers, and plugins.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@elliotllliu/agentshield)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Catch data exfiltration, backdoors, privilege escalation, credential leaks, and supply chain vulnerabilities **before** they reach your AI agents.
|
|
9
|
+
|
|
10
|
+
> **We scanned the top ClawHub skill repos ā the average security score was 47/100.** [Read the full report ā](docs/clawhub-security-report.md)
|
|
11
|
+
|
|
12
|
+
## Why AgentShield?
|
|
13
|
+
|
|
14
|
+
AI agents install and execute third-party skills, MCP servers, and plugins with minimal security review. A single malicious skill can:
|
|
15
|
+
|
|
16
|
+
- š **Steal credentials** ā SSH keys, AWS secrets, API tokens
|
|
17
|
+
- š¤ **Exfiltrate data** ā read sensitive files and send them to external servers
|
|
18
|
+
- š **Open backdoors** ā `eval()`, reverse shells, dynamic code execution
|
|
19
|
+
- āļø **Mine crypto** ā hijack compute for cryptocurrency mining
|
|
20
|
+
- šµļø **Bypass permissions** ā claim "read-only" but execute shell commands
|
|
21
|
+
|
|
22
|
+
AgentShield catches these patterns with **16 security rules** in under 50ms.
|
|
6
23
|
|
|
7
24
|
## Quick Start
|
|
8
25
|
|
|
9
26
|
```bash
|
|
10
|
-
npx agentshield scan ./my-skill/
|
|
27
|
+
npx @elliotllliu/agentshield scan ./my-skill/
|
|
11
28
|
```
|
|
12
29
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
|
20
|
-
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
No installation required. Works with Node.js 18+.
|
|
31
|
+
|
|
32
|
+
## What It Detects ā 16 Security Rules
|
|
33
|
+
|
|
34
|
+
### š“ Critical (auto-fail)
|
|
35
|
+
|
|
36
|
+
| Rule | Detects |
|
|
37
|
+
|------|---------|
|
|
38
|
+
| `data-exfil` | Reads sensitive files + sends HTTP requests (exfiltration pattern) |
|
|
39
|
+
| `backdoor` | `eval()`, `new Function()`, `child_process.exec()` with dynamic input |
|
|
40
|
+
| `reverse-shell` | Outbound socket connections piped to `/bin/sh` |
|
|
41
|
+
| `crypto-mining` | Mining pool connections, xmrig, coinhive patterns |
|
|
42
|
+
| `credential-hardcode` | Hardcoded AWS keys (`AKIA...`), GitHub PATs (`ghp_...`), Stripe keys |
|
|
43
|
+
| `env-leak` | `process.env` secrets + outbound HTTP (environment variable theft) |
|
|
44
|
+
| `obfuscation` | `eval(atob(...))`, hex strings, `String.fromCharCode` obfuscation |
|
|
45
|
+
| `typosquatting` | Suspicious npm names: `1odash` ā `lodash`, `axois` ā `axios` |
|
|
46
|
+
| `hidden-files` | `.env` files with `PASSWORD`, `SECRET`, `API_KEY` committed to repo |
|
|
47
|
+
|
|
48
|
+
### š” Warning (review recommended)
|
|
49
|
+
|
|
50
|
+
| Rule | Detects |
|
|
51
|
+
|------|---------|
|
|
52
|
+
| `network-ssrf` | User-controlled URLs in fetch, AWS metadata endpoint access |
|
|
53
|
+
| `privilege` | SKILL.md permissions vs actual code behavior mismatch |
|
|
54
|
+
| `supply-chain` | Known CVEs in npm dependencies (`npm audit`) |
|
|
55
|
+
| `sensitive-read` | Access to `~/.ssh/id_rsa`, `~/.aws/credentials`, `~/.kube/config` |
|
|
56
|
+
| `excessive-perms` | Too many or dangerous permissions in SKILL.md |
|
|
57
|
+
| `phone-home` | `setInterval` + HTTP requests (beacon/C2 heartbeat pattern) |
|
|
58
|
+
| `mcp-manifest` | MCP server: wildcard perms, undeclared capabilities, suspicious tool descriptions |
|
|
59
|
+
|
|
60
|
+
## Real-World Results
|
|
61
|
+
|
|
62
|
+
We scanned the **top 9 ClawHub skill repositories** (700K+ combined installs):
|
|
63
|
+
|
|
64
|
+
| Repository | Installs | Score | Risk |
|
|
65
|
+
|------------|----------|-------|------|
|
|
66
|
+
| vercel-labs/agent-skills | 157K | š“ 0/100 | Critical ā deploy scripts with `$(curl)` command substitution |
|
|
67
|
+
| obra/superpowers | 94K | š“ 0/100 | Critical ā dynamic code execution in render scripts |
|
|
68
|
+
| coreyhaines31/marketingskills | 42K | š“ 0/100 | Critical ā 122 critical findings (CRM credential patterns) |
|
|
69
|
+
| anthropics/skills | 36K | š“ 35/100 | Critical ā template with exec() |
|
|
70
|
+
| expo/skills | 11K | š“ 5/100 | Critical ā fetch script reads env vars |
|
|
71
|
+
| remotion-dev/skills | 140K | š” 80/100 | Moderate ā minor warnings |
|
|
72
|
+
| google-labs-code/stitch-skills | 63K | ā
100/100 | Clean |
|
|
73
|
+
| supercent-io/skills-template | 106K | ā
100/100 | Clean |
|
|
74
|
+
| squirrelscan/skills | 34K | ā
100/100 | Clean |
|
|
75
|
+
|
|
76
|
+
**Average score: 47/100** ā over half of popular skill repos have critical security findings.
|
|
77
|
+
|
|
78
|
+
[š Full security report ā](docs/clawhub-security-report.md)
|
|
32
79
|
|
|
33
80
|
## Example Output
|
|
34
81
|
|
|
@@ -37,78 +84,172 @@ npx agentshield scan ./my-skill/
|
|
|
37
84
|
š Scanned: ./my-skill/ (3 files, 44 lines)
|
|
38
85
|
|
|
39
86
|
š“ CRITICAL (3)
|
|
40
|
-
āā index.ts:13 ā [data-exfil] Reads sensitive data and sends HTTP request
|
|
87
|
+
āā index.ts:13 ā [data-exfil] Reads sensitive data and sends HTTP request
|
|
41
88
|
āā index.ts:20 ā [backdoor] eval() with dynamic input
|
|
42
|
-
āā
|
|
89
|
+
āā backdoor.sh:6 ā [backdoor] shell eval with variable
|
|
43
90
|
|
|
44
91
|
š” WARNING (2)
|
|
45
92
|
āā index.ts:23 ā [privilege] Code uses 'exec' but SKILL.md doesn't declare it
|
|
46
93
|
āā index.ts:6 ā [sensitive-read] Accesses SSH private key
|
|
47
94
|
|
|
48
|
-
š¢ INFO (1)
|
|
49
|
-
āā SKILL.md ā [privilege] Detected capabilities: exec, read, web_fetch
|
|
50
|
-
|
|
51
95
|
ā
Score: 0/100 (Critical Risk)
|
|
96
|
+
ā± 16ms
|
|
52
97
|
```
|
|
53
98
|
|
|
54
99
|
## Usage
|
|
55
100
|
|
|
56
101
|
```bash
|
|
57
102
|
# Scan a directory
|
|
58
|
-
agentshield scan ./path/to/skill/
|
|
103
|
+
npx @elliotllliu/agentshield scan ./path/to/skill/
|
|
104
|
+
|
|
105
|
+
# JSON output (for CI/CD pipelines)
|
|
106
|
+
npx @elliotllliu/agentshield scan ./skill/ --json
|
|
107
|
+
|
|
108
|
+
# Fail CI if score drops below threshold
|
|
109
|
+
npx @elliotllliu/agentshield scan ./skill/ --fail-under 70
|
|
110
|
+
|
|
111
|
+
# Disable specific rules
|
|
112
|
+
npx @elliotllliu/agentshield scan ./skill/ --disable supply-chain,phone-home
|
|
59
113
|
|
|
60
|
-
#
|
|
61
|
-
agentshield scan ./skill/ --
|
|
114
|
+
# Only run specific rules
|
|
115
|
+
npx @elliotllliu/agentshield scan ./skill/ --enable backdoor,data-exfil
|
|
62
116
|
|
|
63
|
-
#
|
|
64
|
-
|
|
117
|
+
# Generate config files
|
|
118
|
+
npx @elliotllliu/agentshield init
|
|
65
119
|
|
|
66
|
-
#
|
|
67
|
-
agentshield ./skill/
|
|
120
|
+
# Watch mode ā re-scan on file changes
|
|
121
|
+
npx @elliotllliu/agentshield watch ./skill/
|
|
122
|
+
|
|
123
|
+
# Compare two versions
|
|
124
|
+
npx @elliotllliu/agentshield compare ./skill-v1/ ./skill-v2/
|
|
125
|
+
|
|
126
|
+
# Generate a security badge for your README
|
|
127
|
+
npx @elliotllliu/agentshield badge ./skill/
|
|
68
128
|
```
|
|
69
129
|
|
|
70
130
|
## CI Integration
|
|
71
131
|
|
|
132
|
+
### GitHub Action (recommended)
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
# .github/workflows/security.yml
|
|
136
|
+
name: Security Scan
|
|
137
|
+
on: [push, pull_request]
|
|
138
|
+
jobs:
|
|
139
|
+
scan:
|
|
140
|
+
runs-on: ubuntu-latest
|
|
141
|
+
steps:
|
|
142
|
+
- uses: actions/checkout@v4
|
|
143
|
+
- uses: elliotllliu/agentshield@main
|
|
144
|
+
with:
|
|
145
|
+
path: './skills/'
|
|
146
|
+
fail-under: '70'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### npx one-liner
|
|
150
|
+
|
|
72
151
|
```yaml
|
|
73
|
-
# GitHub Actions
|
|
74
152
|
- name: Security scan
|
|
75
|
-
run: npx agentshield scan ./skills/ --fail-under 70
|
|
153
|
+
run: npx -y @elliotllliu/agentshield scan ./skills/ --fail-under 70
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Action Inputs & Outputs
|
|
157
|
+
|
|
158
|
+
| Input | Default | Description |
|
|
159
|
+
|-------|---------|-------------|
|
|
160
|
+
| `path` | `.` | Directory to scan |
|
|
161
|
+
| `fail-under` | ā | Fail if score < threshold (0-100) |
|
|
162
|
+
| `format` | `terminal` | `terminal` or `json` |
|
|
163
|
+
|
|
164
|
+
| Output | Description |
|
|
165
|
+
|--------|-------------|
|
|
166
|
+
| `score` | Security score (0-100) |
|
|
167
|
+
| `findings` | Number of findings |
|
|
168
|
+
|
|
169
|
+
## Configuration
|
|
170
|
+
|
|
171
|
+
Create `.agentshield.yml` (or run `agentshield init`):
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
rules:
|
|
175
|
+
disable:
|
|
176
|
+
- supply-chain # skip npm audit
|
|
177
|
+
- phone-home # allow periodic HTTP
|
|
178
|
+
|
|
179
|
+
severity:
|
|
180
|
+
sensitive-read: info # downgrade to info
|
|
181
|
+
|
|
182
|
+
failUnder: 70 # CI threshold
|
|
183
|
+
|
|
184
|
+
ignore:
|
|
185
|
+
- "tests/**"
|
|
186
|
+
- "*.test.ts"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### `.agentshieldignore`
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
node_modules/
|
|
193
|
+
dist/
|
|
194
|
+
*.test.ts
|
|
195
|
+
__tests__/
|
|
76
196
|
```
|
|
77
197
|
|
|
78
198
|
## Scoring
|
|
79
199
|
|
|
80
|
-
|
|
200
|
+
| Severity | Points Deducted |
|
|
201
|
+
|----------|----------------|
|
|
202
|
+
| š“ Critical | -25 |
|
|
203
|
+
| š” Warning | -10 |
|
|
204
|
+
| š¢ Info | 0 |
|
|
205
|
+
|
|
206
|
+
| Score | Risk Level | Recommendation |
|
|
207
|
+
|-------|------------|----------------|
|
|
208
|
+
| 90-100 | ā
Low Risk | Safe to install |
|
|
209
|
+
| 70-89 | š” Moderate | Review warnings |
|
|
210
|
+
| 40-69 | š High Risk | Investigate before using |
|
|
211
|
+
| 0-39 | š“ Critical | Do not install |
|
|
212
|
+
|
|
213
|
+
## Supported Platforms
|
|
214
|
+
|
|
215
|
+
- **AI Agent Skills** ā OpenClaw, Codex, Claude Code
|
|
216
|
+
- **MCP Servers** ā Model Context Protocol tool servers
|
|
217
|
+
- **npm Packages** ā any npm package with executable code
|
|
218
|
+
- **General** ā any directory with JS/TS/Python/Shell code
|
|
219
|
+
|
|
220
|
+
### Supported File Types
|
|
81
221
|
|
|
82
|
-
|
|
|
222
|
+
| Language | Extensions |
|
|
83
223
|
|----------|-----------|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
88
|
-
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
##
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- [
|
|
110
|
-
- [ ]
|
|
111
|
-
- [ ]
|
|
224
|
+
| JavaScript/TypeScript | `.js`, `.ts`, `.mjs`, `.cjs`, `.tsx`, `.jsx` |
|
|
225
|
+
| Python | `.py` |
|
|
226
|
+
| Shell | `.sh`, `.bash`, `.zsh` |
|
|
227
|
+
| Config | `.json`, `.yaml`, `.yml`, `.toml` |
|
|
228
|
+
| Docs | `SKILL.md` (permission analysis) |
|
|
229
|
+
|
|
230
|
+
## Comparison with Other Tools
|
|
231
|
+
|
|
232
|
+
| Feature | AgentShield | npm audit | Snyk | ESLint Security |
|
|
233
|
+
|---------|------------|-----------|------|-----------------|
|
|
234
|
+
| AI skill/MCP specific rules | ā
| ā | ā | ā |
|
|
235
|
+
| Data exfiltration detection | ā
| ā | ā | ā |
|
|
236
|
+
| Permission mismatch (SKILL.md) | ā
| ā | ā | ā |
|
|
237
|
+
| Credential hardcode detection | ā
| ā | ā
| ā
|
|
|
238
|
+
| Supply chain CVEs | ā
| ā
| ā
| ā |
|
|
239
|
+
| Zero config | ā
| ā
| ā | ā |
|
|
240
|
+
| No API key required | ā
| ā
| ā | ā
|
|
|
241
|
+
| < 50ms scan time | ā
| ā | ā | ā |
|
|
242
|
+
|
|
243
|
+
## Contributing
|
|
244
|
+
|
|
245
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to add new rules.
|
|
246
|
+
|
|
247
|
+
## Links
|
|
248
|
+
|
|
249
|
+
- š¦ [npm](https://www.npmjs.com/package/@elliotllliu/agentshield)
|
|
250
|
+
- š [Rule Documentation](docs/rules.md)
|
|
251
|
+
- š [ClawHub Security Report](docs/clawhub-security-report.md)
|
|
252
|
+
- šØš³ [äøę README](README.zh-CN.md)
|
|
112
253
|
|
|
113
254
|
## License
|
|
114
255
|
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# š”ļø AgentShield ā AI Agent å®å
Øę«ęåØ
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@elliotllliu/agentshield)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
äøäøŗ AI Agent ęč½ćMCP Serverćę件设讔ēå®å
Øę«ęå·„å
·ćåØå®č£
第äøę¹ę©å±ä¹åļ¼ę£ęµę°ę®ēŖåćåéØćåčÆę³é²åä¾åŗé¾ę¼ę“ć
|
|
7
|
+
|
|
8
|
+
> **ę们ę«ęäŗ ClawHub ēéØ skill ä»åŗ ā å¹³åå®å
Øåä»
47/100ć** [ę„ēå®ę“ę„å ā](docs/clawhub-security-report.md)
|
|
9
|
+
|
|
10
|
+
## äøŗä»ä¹éč¦ AgentShieldļ¼
|
|
11
|
+
|
|
12
|
+
AI Agent ä¼å®č£
å¹¶ę§č”第äøę¹ęč½åęä»¶ļ¼å®å
Øå®”ę„å ä¹äøŗé¶ćäøäøŖę¶ę skill å°±č½ļ¼
|
|
13
|
+
|
|
14
|
+
- š **å·åčÆ** ā SSH åÆé„ćAWS SecretćAPI Token
|
|
15
|
+
- š¤ **å¤ę³ę°ę®** ā 读åęęęä»¶åéå°å¤éØęå”åØ
|
|
16
|
+
- š **ę¤å
„åéØ** ā eval()ćåå¼¹ ShellćåØę代ē ę§č”
|
|
17
|
+
- āļø **ęēæ** ā å©ēØä½ ēē®åęå åÆč“§åø
|
|
18
|
+
- šµļø **č¶ę** ā 声称åŖčÆ»ä½å®é
ę§č” Shell å½ä»¤
|
|
19
|
+
|
|
20
|
+
AgentShield ēØ **16 ę”å®å
Øč§å**åØ 50ms å
ę£åŗčæäŗåØčć
|
|
21
|
+
|
|
22
|
+
## åæ«éå¼å§
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @elliotllliu/agentshield scan ./my-skill/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
ę éå®č£
ļ¼Node.js 18+ å³åÆčæč”ć
|
|
29
|
+
|
|
30
|
+
## 16 ę”å®å
Øč§å
|
|
31
|
+
|
|
32
|
+
### š“ äø„éļ¼čŖåØå¤å®äøå®å
Øļ¼
|
|
33
|
+
|
|
34
|
+
| č§å | ę£ęµå
容 |
|
|
35
|
+
|------|----------|
|
|
36
|
+
| `data-exfil` | 读ęęęä»¶ + å HTTP 请ę±ļ¼ę°ę®å¤ę³ęØ”å¼ļ¼ |
|
|
37
|
+
| `backdoor` | `eval()`ć`exec()`ćåØę代ē ę§č” |
|
|
38
|
+
| `reverse-shell` | Socket å¤čæ + Shell ē®”é |
|
|
39
|
+
| `crypto-mining` | ēæę± čæę„ćxmrigćcoinhive |
|
|
40
|
+
| `credential-hardcode` | 甬ē¼ē AWS KeyćGitHub PATćStripe Key |
|
|
41
|
+
| `env-leak` | ēÆå¢åé + HTTP å¤å |
|
|
42
|
+
| `obfuscation` | base64+evalćåå
čæå¶ę··ę· |
|
|
43
|
+
| `typosquatting` | npm å
å仿åļ¼`1odash` ā `lodash`ļ¼ |
|
|
44
|
+
| `hidden-files` | `.env` ęęåÆé„ |
|
|
45
|
+
|
|
46
|
+
### š” č¦åļ¼å»ŗč®®å®”ę„ļ¼
|
|
47
|
+
|
|
48
|
+
| č§å | ę£ęµå
容 |
|
|
49
|
+
|------|----------|
|
|
50
|
+
| `network-ssrf` | ēØę·åÆę§ URLćSSRF |
|
|
51
|
+
| `privilege` | SKILL.md 声ę vs 代ē å®é
č”äøŗäøå¹é
|
|
|
52
|
+
| `supply-chain` | npm ä¾čµå·²ē„ CVE |
|
|
53
|
+
| `sensitive-read` | 读å SSH åÆé„ćAWS åčÆ |
|
|
54
|
+
| `excessive-perms` | ęé声ęčæå¤ |
|
|
55
|
+
| `phone-home` | å®ę¶åØ + HTTP åæč·³ |
|
|
56
|
+
| `mcp-manifest` | MCP Server éé
ęéćåÆēå·„å
·ęčæ° |
|
|
57
|
+
|
|
58
|
+
## ēå®ę«ęę°ę®
|
|
59
|
+
|
|
60
|
+
ę们ę«äŗ ClawHub **Top 9 ēéØ skill ä»åŗ**ļ¼ę»å®č£
é 70 äø+ļ¼ļ¼
|
|
61
|
+
|
|
62
|
+
| ä»åŗ | å®č£
é | åę° | é£é© |
|
|
63
|
+
|------|--------|------|------|
|
|
64
|
+
| vercel-labs/agent-skills | 157K | š“ 0/100 | deploy čę¬ę `$(curl)` å½ä»¤ęæę¢ |
|
|
65
|
+
| obra/superpowers | 94K | š“ 0/100 | ęø²ęčę¬ęåØę代ē ę§č” |
|
|
66
|
+
| coreyhaines31/marketingskills | 42K | š“ 0/100 | 122 äøŖ criticalļ¼CRM åčÆęØ”å¼ļ¼ |
|
|
67
|
+
| anthropics/skills | 36K | š“ 35/100 | 樔ęæę exec() |
|
|
68
|
+
| google-labs-code/stitch-skills | 63K | ā
100/100 | å¹²å |
|
|
69
|
+
| supercent-io/skills-template | 106K | ā
100/100 | å¹²å |
|
|
70
|
+
|
|
71
|
+
**å¹³ååļ¼47/100** ā č¶
åę°ēéØ skill ęäø„éå®å
Øéę£ć
|
|
72
|
+
|
|
73
|
+
## 使ēØę¹ę³
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# ę«ęē®å½
|
|
77
|
+
npx @elliotllliu/agentshield scan ./skill/
|
|
78
|
+
|
|
79
|
+
# JSON č¾åŗ
|
|
80
|
+
npx @elliotllliu/agentshield scan ./skill/ --json
|
|
81
|
+
|
|
82
|
+
# CI éØē¦
|
|
83
|
+
npx @elliotllliu/agentshield scan ./skill/ --fail-under 70
|
|
84
|
+
|
|
85
|
+
# ē¦ēØē¹å®č§å
|
|
86
|
+
npx @elliotllliu/agentshield scan ./skill/ --disable supply-chain
|
|
87
|
+
|
|
88
|
+
# åå§åé
ē½®
|
|
89
|
+
npx @elliotllliu/agentshield init
|
|
90
|
+
|
|
91
|
+
# å®ę¶ēę§
|
|
92
|
+
npx @elliotllliu/agentshield watch ./skill/
|
|
93
|
+
|
|
94
|
+
# ēę¬åƹęÆ
|
|
95
|
+
npx @elliotllliu/agentshield compare ./v1/ ./v2/
|
|
96
|
+
|
|
97
|
+
# ēęå®å
Øå¾½ē«
|
|
98
|
+
npx @elliotllliu/agentshield badge ./skill/
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## GitHub Actions éę
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
- uses: elliotllliu/agentshield@main
|
|
105
|
+
with:
|
|
106
|
+
path: './skills/'
|
|
107
|
+
fail-under: '70'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## äøå
¶ä»å·„å
·åƹęÆ
|
|
111
|
+
|
|
112
|
+
| åč½ | AgentShield | npm audit | Snyk | ESLint |
|
|
113
|
+
|------|------------|-----------|------|--------|
|
|
114
|
+
| AI Skill/MCP äøēØč§å | ā
| ā | ā | ā |
|
|
115
|
+
| ę°ę®å¤ę³ę£ęµ | ā
| ā | ā | ā |
|
|
116
|
+
| ęéäøå¹é
ę£ęµ | ā
| ā | ā | ā |
|
|
117
|
+
| é¶é
ē½® | ā
| ā
| ā | ā |
|
|
118
|
+
| < 50ms ę«ę | ā
| ā | ā | ā |
|
|
119
|
+
|
|
120
|
+
## é¾ę„
|
|
121
|
+
|
|
122
|
+
- š¦ [npm](https://www.npmjs.com/package/@elliotllliu/agentshield)
|
|
123
|
+
- š [č§åę攣](docs/rules.md)
|
|
124
|
+
- š [ClawHub å®å
Øę„å](docs/clawhub-security-report.md)
|
|
125
|
+
- š¬š§ [English README](README.md)
|
|
126
|
+
|
|
127
|
+
## 许åÆčÆ
|
|
128
|
+
|
|
129
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { resolve } from "path";
|
|
4
|
-
import { existsSync, statSync } from "fs";
|
|
3
|
+
import { resolve, join } from "path";
|
|
4
|
+
import { existsSync, statSync, writeFileSync, watch as fsWatch, mkdirSync } from "fs";
|
|
5
5
|
import { scan } from "./scanner/index.js";
|
|
6
6
|
import { printReport } from "./reporter/terminal.js";
|
|
7
7
|
import { printJsonReport } from "./reporter/json.js";
|
|
8
|
+
import { generateBadgeSvg, generateBadgeMarkdown } from "./reporter/badge.js";
|
|
9
|
+
import { DEFAULT_CONFIG, DEFAULT_IGNORE } from "./config.js";
|
|
8
10
|
const program = new Command();
|
|
9
11
|
program
|
|
10
12
|
.name("agentshield")
|
|
@@ -16,27 +18,195 @@ program
|
|
|
16
18
|
.argument("<directory>", "Target directory to scan")
|
|
17
19
|
.option("--json", "Output results as JSON")
|
|
18
20
|
.option("--fail-under <score>", "Exit with code 1 if score is below threshold", parseInt)
|
|
21
|
+
.option("--disable <rules>", "Comma-separated rules to disable")
|
|
22
|
+
.option("--enable <rules>", "Comma-separated rules to enable (only these)")
|
|
19
23
|
.action((directory, options) => {
|
|
20
24
|
const target = resolve(directory);
|
|
21
25
|
if (!existsSync(target) || !statSync(target).isDirectory()) {
|
|
22
26
|
console.error(`Error: "${directory}" is not a valid directory`);
|
|
23
27
|
process.exit(1);
|
|
24
28
|
}
|
|
25
|
-
const
|
|
29
|
+
const configOverride = {};
|
|
30
|
+
if (options.disable || options.enable) {
|
|
31
|
+
configOverride.rules = {};
|
|
32
|
+
if (options.disable) {
|
|
33
|
+
configOverride.rules.disable = options.disable.split(",").map((s) => s.trim());
|
|
34
|
+
}
|
|
35
|
+
if (options.enable) {
|
|
36
|
+
configOverride.rules.enable = options.enable.split(",").map((s) => s.trim());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const result = scan(target, configOverride);
|
|
26
40
|
if (options.json) {
|
|
27
41
|
printJsonReport(result);
|
|
28
42
|
}
|
|
29
43
|
else {
|
|
30
44
|
printReport(result);
|
|
31
45
|
}
|
|
46
|
+
const threshold = options.failUnder ?? result.score;
|
|
32
47
|
if (options.failUnder !== undefined && result.score < options.failUnder) {
|
|
33
48
|
process.exit(1);
|
|
34
49
|
}
|
|
35
50
|
});
|
|
51
|
+
program
|
|
52
|
+
.command("init")
|
|
53
|
+
.description("Generate .agentshield.yml and .agentshieldignore config files")
|
|
54
|
+
.argument("[directory]", "Target directory", ".")
|
|
55
|
+
.action((directory) => {
|
|
56
|
+
const target = resolve(directory);
|
|
57
|
+
if (!existsSync(target)) {
|
|
58
|
+
mkdirSync(target, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
const configPath = join(target, ".agentshield.yml");
|
|
61
|
+
const ignorePath = join(target, ".agentshieldignore");
|
|
62
|
+
if (existsSync(configPath)) {
|
|
63
|
+
console.log(`ā ļø ${configPath} already exists, skipping`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
writeFileSync(configPath, DEFAULT_CONFIG);
|
|
67
|
+
console.log(`ā
Created ${configPath}`);
|
|
68
|
+
}
|
|
69
|
+
if (existsSync(ignorePath)) {
|
|
70
|
+
console.log(`ā ļø ${ignorePath} already exists, skipping`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
writeFileSync(ignorePath, DEFAULT_IGNORE);
|
|
74
|
+
console.log(`ā
Created ${ignorePath}`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
program
|
|
78
|
+
.command("watch")
|
|
79
|
+
.description("Watch a directory and re-scan on file changes")
|
|
80
|
+
.argument("<directory>", "Target directory to watch")
|
|
81
|
+
.option("--json", "Output results as JSON")
|
|
82
|
+
.action((directory, options) => {
|
|
83
|
+
const target = resolve(directory);
|
|
84
|
+
if (!existsSync(target) || !statSync(target).isDirectory()) {
|
|
85
|
+
console.error(`Error: "${directory}" is not a valid directory`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
console.log(`š Watching ${target} for changes... (Ctrl+C to stop)\n`);
|
|
89
|
+
const runScan = () => {
|
|
90
|
+
console.clear();
|
|
91
|
+
console.log(`š Watching ${target} ā last scan: ${new Date().toLocaleTimeString()}\n`);
|
|
92
|
+
const result = scan(target);
|
|
93
|
+
if (options.json) {
|
|
94
|
+
printJsonReport(result);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
printReport(result);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
// Initial scan
|
|
101
|
+
runScan();
|
|
102
|
+
// Watch for changes
|
|
103
|
+
try {
|
|
104
|
+
const watcher = fsWatch(target, { recursive: true }, () => {
|
|
105
|
+
runScan();
|
|
106
|
+
});
|
|
107
|
+
process.on("SIGINT", () => {
|
|
108
|
+
watcher.close();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
console.error("ā ļø fs.watch recursive not supported on this platform. Use: nodemon --exec 'agentshield scan .'");
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
program
|
|
117
|
+
.command("compare")
|
|
118
|
+
.description("Compare security scores between two directories or git refs")
|
|
119
|
+
.argument("<dirA>", "First directory")
|
|
120
|
+
.argument("<dirB>", "Second directory")
|
|
121
|
+
.option("--json", "Output as JSON")
|
|
122
|
+
.action((dirA, dirB, options) => {
|
|
123
|
+
const targetA = resolve(dirA);
|
|
124
|
+
const targetB = resolve(dirB);
|
|
125
|
+
for (const [label, dir] of [["A", targetA], ["B", targetB]]) {
|
|
126
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
|
|
127
|
+
console.error(`Error: directory ${label} "${dir}" is not valid`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const resultA = scan(targetA);
|
|
132
|
+
const resultB = scan(targetB);
|
|
133
|
+
if (options.json) {
|
|
134
|
+
console.log(JSON.stringify({
|
|
135
|
+
before: { target: resultA.target, score: resultA.score, findings: resultA.findings.length },
|
|
136
|
+
after: { target: resultB.target, score: resultB.score, findings: resultB.findings.length },
|
|
137
|
+
delta: resultB.score - resultA.score,
|
|
138
|
+
}, null, 2));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
console.log("\nš AgentShield Comparison\n");
|
|
142
|
+
console.log(` A: ${dirA} ā Score: ${resultA.score}/100 (${resultA.findings.length} findings)`);
|
|
143
|
+
console.log(` B: ${dirB} ā Score: ${resultB.score}/100 (${resultB.findings.length} findings)`);
|
|
144
|
+
console.log();
|
|
145
|
+
const delta = resultB.score - resultA.score;
|
|
146
|
+
if (delta > 0) {
|
|
147
|
+
console.log(` ā
Improved by ${delta} points`);
|
|
148
|
+
}
|
|
149
|
+
else if (delta < 0) {
|
|
150
|
+
console.log(` š“ Degraded by ${Math.abs(delta)} points`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(` ā”ļø No change`);
|
|
154
|
+
}
|
|
155
|
+
// Show new findings in B that aren't in A
|
|
156
|
+
const aKeys = new Set(resultA.findings.map((f) => `${f.rule}:${f.file}:${f.line}`));
|
|
157
|
+
const newFindings = resultB.findings.filter((f) => !aKeys.has(`${f.rule}:${f.file}:${f.line}`));
|
|
158
|
+
const fixedFindings = resultA.findings.filter((f) => {
|
|
159
|
+
const bKeys = new Set(resultB.findings.map((bf) => `${bf.rule}:${bf.file}:${bf.line}`));
|
|
160
|
+
return !bKeys.has(`${f.rule}:${f.file}:${f.line}`);
|
|
161
|
+
});
|
|
162
|
+
if (newFindings.length > 0) {
|
|
163
|
+
console.log(`\n š New findings (${newFindings.length}):`);
|
|
164
|
+
for (const f of newFindings.slice(0, 10)) {
|
|
165
|
+
console.log(` ${f.file}${f.line ? `:${f.line}` : ""} ā [${f.rule}] ${f.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (fixedFindings.length > 0) {
|
|
169
|
+
console.log(`\n ā
Fixed (${fixedFindings.length}):`);
|
|
170
|
+
for (const f of fixedFindings.slice(0, 10)) {
|
|
171
|
+
console.log(` ${f.file}${f.line ? `:${f.line}` : ""} ā [${f.rule}] ${f.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
console.log();
|
|
175
|
+
});
|
|
176
|
+
program
|
|
177
|
+
.command("badge")
|
|
178
|
+
.description("Generate a security badge for your project")
|
|
179
|
+
.argument("<directory>", "Target directory to scan")
|
|
180
|
+
.option("--svg", "Output raw SVG")
|
|
181
|
+
.option("--markdown", "Output markdown badge (default)")
|
|
182
|
+
.option("-o, --output <file>", "Save SVG to file")
|
|
183
|
+
.action((directory, options) => {
|
|
184
|
+
const target = resolve(directory);
|
|
185
|
+
if (!existsSync(target) || !statSync(target).isDirectory()) {
|
|
186
|
+
console.error(`Error: "${directory}" is not a valid directory`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const result = scan(target);
|
|
190
|
+
if (options.svg || options.output) {
|
|
191
|
+
const svg = generateBadgeSvg(result);
|
|
192
|
+
if (options.output) {
|
|
193
|
+
writeFileSync(resolve(options.output), svg);
|
|
194
|
+
console.log(`ā
Badge saved to ${options.output}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.log(svg);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// Default: markdown
|
|
202
|
+
const md = generateBadgeMarkdown(result.score);
|
|
203
|
+
console.log(md);
|
|
204
|
+
console.log(`\nPaste this in your README.md to show the badge.`);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
36
207
|
// Default: if first arg looks like a directory, treat as scan
|
|
37
208
|
const args = process.argv.slice(2);
|
|
38
|
-
if (args.length > 0 && !args[0].startsWith("-") &&
|
|
39
|
-
// Rewrite: `agentshield ./dir` ā `agentshield scan ./dir`
|
|
209
|
+
if (args.length > 0 && !args[0].startsWith("-") && !["scan", "init", "watch", "compare", "badge", "help"].includes(args[0])) {
|
|
40
210
|
process.argv.splice(2, 0, "scan");
|
|
41
211
|
}
|
|
42
212
|
program.parse();
|