@elliotllliu/agentshield 0.1.0 → 0.2.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 +84 -3
- package/README.zh-CN.md +66 -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 +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Catch data exfiltration, backdoors, privilege escalation, and supply chain vulne
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx agentshield scan ./my-skill/
|
|
10
|
+
npx @elliotllliu/agentshield scan ./my-skill/
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## What It Detects
|
|
@@ -29,6 +29,8 @@ npx agentshield scan ./my-skill/
|
|
|
29
29
|
| `sensitive-read` | 🟡 Warning | Accesses `~/.ssh/id_rsa`, `~/.aws/credentials`, etc. |
|
|
30
30
|
| `excessive-perms` | 🟡 Warning | Too many or dangerous permissions in SKILL.md |
|
|
31
31
|
| `phone-home` | 🟡 Warning | Periodic timers + HTTP requests (beacon/heartbeat pattern) |
|
|
32
|
+
| `mcp-manifest` | 🟡 Warning | MCP server: wildcard perms, undeclared capabilities, suspicious tool descriptions |
|
|
33
|
+
| `mcp-manifest` | 🟡 Warning | MCP server tool/resource declarations vs actual code behavior |
|
|
32
34
|
|
|
33
35
|
## Example Output
|
|
34
36
|
|
|
@@ -63,18 +65,97 @@ agentshield scan ./skill/ --json
|
|
|
63
65
|
# Fail CI if score is below threshold
|
|
64
66
|
agentshield scan ./skill/ --fail-under 70
|
|
65
67
|
|
|
68
|
+
# Disable specific rules
|
|
69
|
+
agentshield scan ./skill/ --disable supply-chain,phone-home
|
|
70
|
+
|
|
71
|
+
# Only run specific rules
|
|
72
|
+
agentshield scan ./skill/ --enable backdoor,data-exfil
|
|
73
|
+
|
|
66
74
|
# Shorthand (directory as first arg)
|
|
67
75
|
agentshield ./skill/
|
|
76
|
+
|
|
77
|
+
# Generate config files
|
|
78
|
+
agentshield init
|
|
79
|
+
|
|
80
|
+
# Watch mode (re-scan on changes)
|
|
81
|
+
agentshield watch ./skill/
|
|
82
|
+
|
|
83
|
+
# Compare two versions
|
|
84
|
+
agentshield compare ./skill-v1/ ./skill-v2/
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
Create `.agentshield.yml` in your project (or run `agentshield init`):
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
rules:
|
|
93
|
+
disable:
|
|
94
|
+
- supply-chain # skip npm audit
|
|
95
|
+
- phone-home # allow periodic HTTP
|
|
96
|
+
|
|
97
|
+
severity:
|
|
98
|
+
sensitive-read: info # downgrade to info
|
|
99
|
+
|
|
100
|
+
failUnder: 70 # CI threshold
|
|
101
|
+
|
|
102
|
+
ignore:
|
|
103
|
+
- "tests/**"
|
|
104
|
+
- "*.test.ts"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `.agentshieldignore`
|
|
108
|
+
|
|
109
|
+
Exclude files from scanning (same syntax as `.gitignore`):
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
node_modules/
|
|
113
|
+
dist/
|
|
114
|
+
*.test.ts
|
|
115
|
+
__tests__/
|
|
68
116
|
```
|
|
69
117
|
|
|
70
118
|
## CI Integration
|
|
71
119
|
|
|
120
|
+
### GitHub Action (recommended)
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
# .github/workflows/security.yml
|
|
124
|
+
name: Security Scan
|
|
125
|
+
on: [push, pull_request]
|
|
126
|
+
jobs:
|
|
127
|
+
scan:
|
|
128
|
+
runs-on: ubuntu-latest
|
|
129
|
+
steps:
|
|
130
|
+
- uses: actions/checkout@v4
|
|
131
|
+
- uses: elliotllliu/agentshield@main
|
|
132
|
+
with:
|
|
133
|
+
path: './skills/'
|
|
134
|
+
fail-under: '70'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### npx one-liner
|
|
138
|
+
|
|
72
139
|
```yaml
|
|
73
|
-
# GitHub Actions
|
|
74
140
|
- name: Security scan
|
|
75
|
-
run: npx agentshield scan ./skills/ --fail-under 70
|
|
141
|
+
run: npx -y @elliotllliu/agentshield scan ./skills/ --fail-under 70
|
|
76
142
|
```
|
|
77
143
|
|
|
144
|
+
### Action Inputs
|
|
145
|
+
|
|
146
|
+
| Input | Default | Description |
|
|
147
|
+
|-------|---------|-------------|
|
|
148
|
+
| `path` | `.` | Directory to scan |
|
|
149
|
+
| `fail-under` | *(none)* | Fail if score is below threshold (0-100) |
|
|
150
|
+
| `format` | `terminal` | Output format: `terminal` or `json` |
|
|
151
|
+
|
|
152
|
+
### Action Outputs
|
|
153
|
+
|
|
154
|
+
| Output | Description |
|
|
155
|
+
|--------|-------------|
|
|
156
|
+
| `score` | Security score (0-100) |
|
|
157
|
+
| `findings` | Number of findings |
|
|
158
|
+
|
|
78
159
|
## Scoring
|
|
79
160
|
|
|
80
161
|
Starts at 100, deducts per finding:
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# 🛡️ AgentShield
|
|
2
|
+
|
|
3
|
+
AI Agent 技能/插件安全扫描器
|
|
4
|
+
|
|
5
|
+
在安装第三方 AI 技能之前,扫描数据窃取、后门、权限越界和供应链漏洞。
|
|
6
|
+
|
|
7
|
+
## 快速开始
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @elliotllliu/agentshield scan ./my-skill/
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 检测能力
|
|
14
|
+
|
|
15
|
+
| 规则 | 级别 | 说明 |
|
|
16
|
+
|------|------|------|
|
|
17
|
+
| `data-exfil` | 🔴 严重 | 读取敏感文件 + 发送 HTTP 请求(数据外泄) |
|
|
18
|
+
| `backdoor` | 🔴 严重 | `eval()`、`exec()`、动态代码执行 |
|
|
19
|
+
| `reverse-shell` | 🔴 严重 | Socket 外连 + Shell 管道(反弹 Shell) |
|
|
20
|
+
| `crypto-mining` | 🔴 严重 | 挖矿池连接、已知挖矿程序 |
|
|
21
|
+
| `credential-hardcode` | 🔴 严重 | 硬编码 AWS Key、GitHub PAT、Stripe Key |
|
|
22
|
+
| `env-leak` | 🔴 严重 | 环境变量读取 + HTTP 外发 |
|
|
23
|
+
| `obfuscation` | 🔴 严重 | base64+eval 混淆、十六进制编码 |
|
|
24
|
+
| `typosquatting` | 🔴 严重 | npm 包名拼写仿冒(如 `1odash`) |
|
|
25
|
+
| `hidden-files` | 🔴 严重 | `.env` 文件包含明文密钥 |
|
|
26
|
+
| `network-ssrf` | 🟡 警告 | 用户可控 URL、SSRF、AWS 元数据端点 |
|
|
27
|
+
| `privilege` | 🟡 警告 | SKILL.md 声明权限 vs 代码实际行为不匹配 |
|
|
28
|
+
| `supply-chain` | 🟡 警告 | npm 依赖已知 CVE 漏洞 |
|
|
29
|
+
| `sensitive-read` | 🟡 警告 | 读取 SSH 密钥、AWS 凭证等 |
|
|
30
|
+
| `excessive-perms` | 🟡 警告 | 权限声明过多或过于危险 |
|
|
31
|
+
| `phone-home` | 🟡 警告 | 定时器 + HTTP 请求(心跳/信标模式) |
|
|
32
|
+
|
|
33
|
+
## 使用方法
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# 扫描目录
|
|
37
|
+
npx @elliotllliu/agentshield scan ./path/to/skill/
|
|
38
|
+
|
|
39
|
+
# JSON 输出(适用于 CI/CD)
|
|
40
|
+
npx @elliotllliu/agentshield scan ./skill/ --json
|
|
41
|
+
|
|
42
|
+
# CI 门禁:分数低于阈值则失败
|
|
43
|
+
npx @elliotllliu/agentshield scan ./skill/ --fail-under 70
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## GitHub Actions 集成
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
- uses: elliotllliu/agentshield@main
|
|
50
|
+
with:
|
|
51
|
+
path: './skills/'
|
|
52
|
+
fail-under: '70'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 安全评分
|
|
56
|
+
|
|
57
|
+
| 分数 | 风险等级 |
|
|
58
|
+
|------|----------|
|
|
59
|
+
| 90-100 | 低风险 ✅ |
|
|
60
|
+
| 70-89 | 中等风险 🟡 |
|
|
61
|
+
| 40-69 | 高风险 🟠 |
|
|
62
|
+
| 0-39 | 严重风险 🔴 |
|
|
63
|
+
|
|
64
|
+
## 许可证
|
|
65
|
+
|
|
66
|
+
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();
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** AgentShield configuration */
|
|
2
|
+
export interface ScanConfig {
|
|
3
|
+
/** Rules to enable (default: all) */
|
|
4
|
+
rules?: {
|
|
5
|
+
enable?: string[];
|
|
6
|
+
disable?: string[];
|
|
7
|
+
};
|
|
8
|
+
/** Severity overrides: rule-id → severity */
|
|
9
|
+
severity?: Record<string, "critical" | "warning" | "info">;
|
|
10
|
+
/** Score threshold for CI (same as --fail-under) */
|
|
11
|
+
failUnder?: number;
|
|
12
|
+
/** Glob patterns to ignore */
|
|
13
|
+
ignore?: string[];
|
|
14
|
+
}
|
|
15
|
+
/** Load config from target directory or parents */
|
|
16
|
+
export declare function loadConfig(dir: string): ScanConfig;
|
|
17
|
+
/** Load .agentshieldignore patterns */
|
|
18
|
+
export declare function loadIgnorePatterns(dir: string): string[];
|
|
19
|
+
/** Check if a file path matches any ignore pattern */
|
|
20
|
+
export declare function isIgnored(filePath: string, patterns: string[]): boolean;
|
|
21
|
+
/** Default config content for `agentshield init` */
|
|
22
|
+
export declare const DEFAULT_CONFIG = "# AgentShield Configuration\n# https://github.com/elliotllliu/agentshield\n\nrules:\n # disable:\n # - supply-chain # skip npm audit\n # - phone-home # allow periodic HTTP\n\n# severity:\n# sensitive-read: info # downgrade to info\n\n# failUnder: 70 # CI threshold\n\n# ignore:\n# - \"tests/**\"\n# - \"*.test.ts\"\n";
|
|
23
|
+
/** Default ignore content */
|
|
24
|
+
export declare const DEFAULT_IGNORE = "# AgentShield Ignore\n# Patterns here will be excluded from scanning\n\nnode_modules/\ndist/\nbuild/\n.git/\n*.test.ts\n*.test.js\n*.spec.ts\n*.spec.js\n__tests__/\ncoverage/\n";
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { parse as parseYaml } from "./yaml-simple.js";
|
|
4
|
+
const CONFIG_NAMES = [".agentshield.yml", ".agentshield.yaml", "agentshield.config.yml"];
|
|
5
|
+
/** Load config from target directory or parents */
|
|
6
|
+
export function loadConfig(dir) {
|
|
7
|
+
for (const name of CONFIG_NAMES) {
|
|
8
|
+
const configPath = join(dir, name);
|
|
9
|
+
if (existsSync(configPath)) {
|
|
10
|
+
try {
|
|
11
|
+
const content = readFileSync(configPath, "utf-8");
|
|
12
|
+
return parseYaml(content);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// invalid config, use defaults
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
/** Load .agentshieldignore patterns */
|
|
22
|
+
export function loadIgnorePatterns(dir) {
|
|
23
|
+
const ignorePath = join(dir, ".agentshieldignore");
|
|
24
|
+
if (!existsSync(ignorePath))
|
|
25
|
+
return [];
|
|
26
|
+
try {
|
|
27
|
+
return readFileSync(ignorePath, "utf-8")
|
|
28
|
+
.split("\n")
|
|
29
|
+
.map((line) => line.trim())
|
|
30
|
+
.filter((line) => line && !line.startsWith("#"));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Check if a file path matches any ignore pattern */
|
|
37
|
+
export function isIgnored(filePath, patterns) {
|
|
38
|
+
for (const pattern of patterns) {
|
|
39
|
+
// Simple glob matching: support * and **
|
|
40
|
+
if (pattern.endsWith("/")) {
|
|
41
|
+
// Directory pattern
|
|
42
|
+
if (filePath.startsWith(pattern) || filePath.includes("/" + pattern))
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
else if (pattern.includes("*")) {
|
|
46
|
+
const regex = new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$");
|
|
47
|
+
if (regex.test(filePath))
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Exact match or suffix match
|
|
52
|
+
if (filePath === pattern || filePath.endsWith("/" + pattern) || filePath.endsWith(pattern))
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
/** Default config content for `agentshield init` */
|
|
59
|
+
export const DEFAULT_CONFIG = `# AgentShield Configuration
|
|
60
|
+
# https://github.com/elliotllliu/agentshield
|
|
61
|
+
|
|
62
|
+
rules:
|
|
63
|
+
# disable:
|
|
64
|
+
# - supply-chain # skip npm audit
|
|
65
|
+
# - phone-home # allow periodic HTTP
|
|
66
|
+
|
|
67
|
+
# severity:
|
|
68
|
+
# sensitive-read: info # downgrade to info
|
|
69
|
+
|
|
70
|
+
# failUnder: 70 # CI threshold
|
|
71
|
+
|
|
72
|
+
# ignore:
|
|
73
|
+
# - "tests/**"
|
|
74
|
+
# - "*.test.ts"
|
|
75
|
+
`;
|
|
76
|
+
/** Default ignore content */
|
|
77
|
+
export const DEFAULT_IGNORE = `# AgentShield Ignore
|
|
78
|
+
# Patterns here will be excluded from scanning
|
|
79
|
+
|
|
80
|
+
node_modules/
|
|
81
|
+
dist/
|
|
82
|
+
build/
|
|
83
|
+
.git/
|
|
84
|
+
*.test.ts
|
|
85
|
+
*.test.js
|
|
86
|
+
*.spec.ts
|
|
87
|
+
*.spec.js
|
|
88
|
+
__tests__/
|
|
89
|
+
coverage/
|
|
90
|
+
`;
|
|
91
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAiBtD,MAAM,YAAY,GAAG,CAAC,kBAAkB,EAAE,mBAAmB,EAAE,wBAAwB,CAAC,CAAC;AAEzF,mDAAmD;AACnD,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACnC,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;gBAClD,OAAO,SAAS,CAAC,OAAO,CAAe,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,+BAA+B;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC;IACnD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC;aACrC,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,QAAkB;IAC5D,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,yCAAyC;QACzC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,oBAAoB;YACpB,IAAI,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,OAAO,CAAC;gBAAE,OAAO,IAAI,CAAC;QACpF,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,IAAI,MAAM,CACtB,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,GAAG,CACzF,CAAC;YACF,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,OAAO,IAAI,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,8BAA8B;YAC9B,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,OAAO,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC1G,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,oDAAoD;AACpD,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;CAgB7B,CAAC;AAEF,6BAA6B;AAC7B,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;CAa7B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ScanResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generate a shields.io-style SVG badge for the security score.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateBadgeSvg(result: ScanResult): string;
|
|
6
|
+
/** Generate a markdown badge string */
|
|
7
|
+
export declare function generateBadgeMarkdown(score: number, repoUrl?: string): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a shields.io-style SVG badge for the security score.
|
|
3
|
+
*/
|
|
4
|
+
export function generateBadgeSvg(result) {
|
|
5
|
+
const score = result.score;
|
|
6
|
+
const { color, label } = getBadgeStyle(score);
|
|
7
|
+
const scoreText = `${score}/100`;
|
|
8
|
+
// Shield dimensions
|
|
9
|
+
const labelWidth = 90;
|
|
10
|
+
const valueWidth = 60;
|
|
11
|
+
const totalWidth = labelWidth + valueWidth;
|
|
12
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="20" role="img" aria-label="AgentShield: ${scoreText}">
|
|
13
|
+
<title>AgentShield: ${scoreText} (${label})</title>
|
|
14
|
+
<linearGradient id="s" x2="0" y2="100%">
|
|
15
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
16
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
17
|
+
</linearGradient>
|
|
18
|
+
<clipPath id="r">
|
|
19
|
+
<rect width="${totalWidth}" height="20" rx="3" fill="#fff"/>
|
|
20
|
+
</clipPath>
|
|
21
|
+
<g clip-path="url(#r)">
|
|
22
|
+
<rect width="${labelWidth}" height="20" fill="#555"/>
|
|
23
|
+
<rect x="${labelWidth}" width="${valueWidth}" height="20" fill="${color}"/>
|
|
24
|
+
<rect width="${totalWidth}" height="20" fill="url(#s)"/>
|
|
25
|
+
</g>
|
|
26
|
+
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110">
|
|
27
|
+
<text aria-hidden="true" x="${labelWidth * 5}" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)">${"🛡️ AgentShield"}</text>
|
|
28
|
+
<text x="${labelWidth * 5}" y="140" transform="scale(.1)">${"🛡️ AgentShield"}</text>
|
|
29
|
+
<text aria-hidden="true" x="${(labelWidth + valueWidth / 2) * 10}" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)">${scoreText}</text>
|
|
30
|
+
<text x="${(labelWidth + valueWidth / 2) * 10}" y="140" transform="scale(.1)">${scoreText}</text>
|
|
31
|
+
</g>
|
|
32
|
+
</svg>`;
|
|
33
|
+
}
|
|
34
|
+
/** Generate a markdown badge string */
|
|
35
|
+
export function generateBadgeMarkdown(score, repoUrl) {
|
|
36
|
+
const { color, label } = getBadgeStyle(score);
|
|
37
|
+
const badgeUrl = `https://img.shields.io/badge/AgentShield-${score}%2F100-${color.replace("#", "")}?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xMiAxTDMgNXY2YzAgNS41NSAzLjg0IDEwLjc0IDkgMTIgNS4xNi0xLjI2IDktNi40NSA5LTEyVjVsLTktNHoiLz48L3N2Zz4=`;
|
|
38
|
+
const link = repoUrl || "https://github.com/elliotllliu/agentshield";
|
|
39
|
+
return `[](${link})`;
|
|
40
|
+
}
|
|
41
|
+
function getBadgeStyle(score) {
|
|
42
|
+
if (score >= 90)
|
|
43
|
+
return { color: "#4c1", label: "Low Risk" };
|
|
44
|
+
if (score >= 70)
|
|
45
|
+
return { color: "#dfb317", label: "Moderate Risk" };
|
|
46
|
+
if (score >= 40)
|
|
47
|
+
return { color: "#fe7d37", label: "High Risk" };
|
|
48
|
+
return { color: "#e05d44", label: "Critical Risk" };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge.js","sourceRoot":"","sources":["../../src/reporter/badge.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IACjD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,GAAG,KAAK,MAAM,CAAC;IAEjC,oBAAoB;IACpB,MAAM,UAAU,GAAG,EAAE,CAAC;IACtB,MAAM,UAAU,GAAG,EAAE,CAAC;IACtB,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;IAE3C,OAAO,kDAAkD,UAAU,qDAAqD,SAAS;wBAC3G,SAAS,KAAK,KAAK;;;;;;mBAMxB,UAAU;;;mBAGV,UAAU;eACd,UAAU,YAAY,UAAU,uBAAuB,KAAK;mBACxD,UAAU;;;kCAGK,UAAU,GAAG,CAAC,oEAAoE,iBAAiB;eACtH,UAAU,GAAG,CAAC,mCAAmC,iBAAiB;kCAC/C,CAAC,UAAU,GAAG,UAAU,GAAG,CAAC,CAAC,GAAG,EAAE,oEAAoE,SAAS;eAClI,CAAC,UAAU,GAAG,UAAU,GAAG,CAAC,CAAC,GAAG,EAAE,mCAAmC,SAAS;;OAEtF,CAAC;AACR,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,OAAgB;IACnE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,4CAA4C,KAAK,UAAU,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,oPAAoP,CAAC;IACvV,MAAM,IAAI,GAAG,OAAO,IAAI,4CAA4C,CAAC;IACrE,OAAO,kBAAkB,KAAK,SAAS,QAAQ,MAAM,IAAI,GAAG,CAAC;AAC/D,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAC7D,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;IACrE,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IACjE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;AACtD,CAAC"}
|
package/dist/rules/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { excessivePermsRule } from "./excessive-perms.js";
|
|
|
13
13
|
import { phoneHomeRule } from "./phone-home.js";
|
|
14
14
|
import { credentialHardcodeRule } from "./credential-hardcode.js";
|
|
15
15
|
import { networkSsrfRule } from "./network-ssrf.js";
|
|
16
|
+
import { mcpManifestRule } from "./mcp-manifest.js";
|
|
16
17
|
/** All registered rules */
|
|
17
18
|
export const rules = [
|
|
18
19
|
// Original 5
|
|
@@ -32,6 +33,7 @@ export const rules = [
|
|
|
32
33
|
phoneHomeRule,
|
|
33
34
|
credentialHardcodeRule,
|
|
34
35
|
networkSsrfRule,
|
|
36
|
+
mcpManifestRule,
|
|
35
37
|
];
|
|
36
38
|
/** Get a rule by ID */
|
|
37
39
|
export function getRule(id) {
|
package/dist/rules/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,2BAA2B;AAC3B,MAAM,CAAC,MAAM,KAAK,GAAW;IAC3B,aAAa;IACb,aAAa;IACb,YAAY;IACZ,aAAa;IACb,eAAe;IACf,iBAAiB;IACjB,SAAS;IACT,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,eAAe;IACf,kBAAkB;IAClB,aAAa;IACb,sBAAsB;IACtB,eAAe;CAChB,CAAC;AAEF,uBAAuB;AACvB,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACxC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,2BAA2B;AAC3B,MAAM,CAAC,MAAM,KAAK,GAAW;IAC3B,aAAa;IACb,aAAa;IACb,YAAY;IACZ,aAAa;IACb,eAAe;IACf,iBAAiB;IACjB,SAAS;IACT,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,eAAe;IACf,kBAAkB;IAClB,aAAa;IACb,sBAAsB;IACtB,eAAe;IACf,eAAe;CAChB,CAAC;AAEF,uBAAuB;AACvB,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: mcp-manifest
|
|
3
|
+
* Validates MCP (Model Context Protocol) server configurations.
|
|
4
|
+
*
|
|
5
|
+
* Checks:
|
|
6
|
+
* 1. Declared tools/resources vs actual code behavior
|
|
7
|
+
* 2. Overly broad tool descriptions that could mislead agents
|
|
8
|
+
* 3. Undeclared file system / network / exec capabilities
|
|
9
|
+
* 4. Suspicious tool names or descriptions
|
|
10
|
+
*/
|
|
11
|
+
// Patterns indicating MCP server tool registration
|
|
12
|
+
const TOOL_REGISTER_RE = /\.tool\s*\(|addTool\s*\(|registerTool\s*\(|server\.setRequestHandler.*ListTools|tools:\s*\[/;
|
|
13
|
+
// Patterns for MCP resource registration
|
|
14
|
+
const RESOURCE_REGISTER_RE = /\.resource\s*\(|addResource\s*\(|registerResource\s*\(|server\.setRequestHandler.*ListResources|resources:\s*\[/;
|
|
15
|
+
// Dangerous patterns in tool implementations
|
|
16
|
+
const DANGEROUS_TOOL_PATTERNS = [
|
|
17
|
+
{ pattern: /child_process|execSync|exec\(|spawn\(/, desc: "Tool executes shell commands", severity: "critical" },
|
|
18
|
+
{ pattern: /fs\.unlink|fs\.rmdir|fs\.rm\b|rimraf/, desc: "Tool deletes files", severity: "warning" },
|
|
19
|
+
{ pattern: /fs\.writeFile|fs\.appendFile|fs\.createWriteStream/, desc: "Tool writes to file system", severity: "warning" },
|
|
20
|
+
{ pattern: /fetch\s*\(|axios|http\.request|https\.request/, desc: "Tool makes outbound HTTP requests", severity: "warning" },
|
|
21
|
+
{ pattern: /eval\s*\(|new\s+Function\s*\(/, desc: "Tool uses dynamic code execution", severity: "critical" },
|
|
22
|
+
{ pattern: /\.ssh|\.aws|\.env\b|credentials|secret/i, desc: "Tool accesses sensitive paths/credentials", severity: "critical" },
|
|
23
|
+
];
|
|
24
|
+
// Suspicious tool name/description patterns
|
|
25
|
+
const SUSPICIOUS_TOOL_DESC = [
|
|
26
|
+
{ pattern: /run.*any.*command|execute.*arbitrary|shell.*access/i, desc: "Tool claims unrestricted command execution" },
|
|
27
|
+
{ pattern: /access.*all.*files|read.*entire.*filesystem/i, desc: "Tool claims full filesystem access" },
|
|
28
|
+
{ pattern: /send.*data.*to|upload.*to|transmit.*to/i, desc: "Tool description mentions data transmission" },
|
|
29
|
+
{ pattern: /modify.*system|change.*config/i, desc: "Tool claims system modification capability" },
|
|
30
|
+
];
|
|
31
|
+
export const mcpManifestRule = {
|
|
32
|
+
id: "mcp-manifest",
|
|
33
|
+
name: "MCP Server Validation",
|
|
34
|
+
description: "Validates MCP server tool/resource declarations against actual code behavior",
|
|
35
|
+
run(files) {
|
|
36
|
+
const findings = [];
|
|
37
|
+
// Detect if this is an MCP server project
|
|
38
|
+
const isMcpServer = detectMcpServer(files);
|
|
39
|
+
if (!isMcpServer)
|
|
40
|
+
return findings;
|
|
41
|
+
// Check for MCP manifest/config files
|
|
42
|
+
checkMcpConfig(files, findings);
|
|
43
|
+
// Analyze tool implementations for dangerous patterns
|
|
44
|
+
checkToolImplementations(files, findings);
|
|
45
|
+
// Check tool descriptions for suspicious claims
|
|
46
|
+
checkToolDescriptions(files, findings);
|
|
47
|
+
// Check if tools are registered but have no input validation
|
|
48
|
+
checkInputValidation(files, findings);
|
|
49
|
+
return findings;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
function detectMcpServer(files) {
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
// Check package.json for MCP-related deps
|
|
55
|
+
if (file.relativePath === "package.json") {
|
|
56
|
+
try {
|
|
57
|
+
const pkg = JSON.parse(file.content);
|
|
58
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
59
|
+
if (allDeps["@modelcontextprotocol/sdk"] ||
|
|
60
|
+
allDeps["@anthropic-ai/sdk"] ||
|
|
61
|
+
pkg.mcp) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// ignore parse errors
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Check for MCP imports in code
|
|
70
|
+
if (file.ext === ".ts" || file.ext === ".js" || file.ext === ".mjs") {
|
|
71
|
+
if (file.content.includes("@modelcontextprotocol/sdk") ||
|
|
72
|
+
file.content.includes("McpServer") ||
|
|
73
|
+
file.content.includes("createMcpServer") ||
|
|
74
|
+
TOOL_REGISTER_RE.test(file.content)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Check for mcp.json or similar config
|
|
79
|
+
if (file.relativePath === "mcp.json" ||
|
|
80
|
+
file.relativePath.endsWith("/mcp.json")) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
function checkMcpConfig(files, findings) {
|
|
87
|
+
const mcpConfig = files.find((f) => f.relativePath === "mcp.json" || f.relativePath.endsWith("/mcp.json"));
|
|
88
|
+
if (mcpConfig) {
|
|
89
|
+
try {
|
|
90
|
+
const config = JSON.parse(mcpConfig.content);
|
|
91
|
+
// Check for overly broad permissions
|
|
92
|
+
if (config.permissions) {
|
|
93
|
+
const perms = Array.isArray(config.permissions)
|
|
94
|
+
? config.permissions
|
|
95
|
+
: Object.keys(config.permissions);
|
|
96
|
+
if (perms.length > 5) {
|
|
97
|
+
findings.push({
|
|
98
|
+
rule: "mcp-manifest",
|
|
99
|
+
severity: "warning",
|
|
100
|
+
file: mcpConfig.relativePath,
|
|
101
|
+
message: `MCP config declares ${perms.length} permissions — consider reducing scope`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Check for wildcard or dangerous permissions
|
|
106
|
+
const configStr = JSON.stringify(config);
|
|
107
|
+
if (configStr.includes('"*"') || configStr.includes('"all"')) {
|
|
108
|
+
findings.push({
|
|
109
|
+
rule: "mcp-manifest",
|
|
110
|
+
severity: "critical",
|
|
111
|
+
file: mcpConfig.relativePath,
|
|
112
|
+
message: "MCP config uses wildcard/all permissions",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
findings.push({
|
|
118
|
+
rule: "mcp-manifest",
|
|
119
|
+
severity: "warning",
|
|
120
|
+
file: mcpConfig.relativePath,
|
|
121
|
+
message: "Invalid JSON in MCP config file",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function checkToolImplementations(files, findings) {
|
|
127
|
+
const codeFiles = files.filter((f) => f.ext === ".ts" || f.ext === ".js" || f.ext === ".mjs" || f.ext === ".cjs");
|
|
128
|
+
for (const file of codeFiles) {
|
|
129
|
+
// Only check files that register tools
|
|
130
|
+
if (!TOOL_REGISTER_RE.test(file.content) && !RESOURCE_REGISTER_RE.test(file.content)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
134
|
+
const line = file.lines[i];
|
|
135
|
+
const trimmed = line.trimStart();
|
|
136
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
137
|
+
continue;
|
|
138
|
+
for (const { pattern, desc, severity } of DANGEROUS_TOOL_PATTERNS) {
|
|
139
|
+
if (pattern.test(line)) {
|
|
140
|
+
findings.push({
|
|
141
|
+
rule: "mcp-manifest",
|
|
142
|
+
severity,
|
|
143
|
+
file: file.relativePath,
|
|
144
|
+
line: i + 1,
|
|
145
|
+
message: `MCP tool: ${desc}`,
|
|
146
|
+
evidence: line.trim().slice(0, 120),
|
|
147
|
+
});
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function checkToolDescriptions(files, findings) {
|
|
155
|
+
const codeFiles = files.filter((f) => f.ext === ".ts" || f.ext === ".js" || f.ext === ".mjs");
|
|
156
|
+
for (const file of codeFiles) {
|
|
157
|
+
// Look for tool description strings
|
|
158
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
159
|
+
const line = file.lines[i];
|
|
160
|
+
for (const { pattern, desc } of SUSPICIOUS_TOOL_DESC) {
|
|
161
|
+
if (pattern.test(line)) {
|
|
162
|
+
findings.push({
|
|
163
|
+
rule: "mcp-manifest",
|
|
164
|
+
severity: "warning",
|
|
165
|
+
file: file.relativePath,
|
|
166
|
+
line: i + 1,
|
|
167
|
+
message: `Suspicious MCP tool description: ${desc}`,
|
|
168
|
+
evidence: line.trim().slice(0, 120),
|
|
169
|
+
});
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function checkInputValidation(files, findings) {
|
|
177
|
+
const codeFiles = files.filter((f) => f.ext === ".ts" || f.ext === ".js" || f.ext === ".mjs");
|
|
178
|
+
for (const file of codeFiles) {
|
|
179
|
+
if (!TOOL_REGISTER_RE.test(file.content))
|
|
180
|
+
continue;
|
|
181
|
+
// Check for tools that accept path inputs without validation
|
|
182
|
+
const hasPathInput = /path|file|dir|folder/i.test(file.content);
|
|
183
|
+
const hasPathValidation = /sanitize|validate|allowlist|whitelist|isAbsolute|normalize|resolve/i.test(file.content);
|
|
184
|
+
const hasTraversalCheck = /\.\.\//i.test(file.content) || /path.*traversal/i.test(file.content);
|
|
185
|
+
if (hasPathInput && !hasPathValidation && !hasTraversalCheck) {
|
|
186
|
+
findings.push({
|
|
187
|
+
rule: "mcp-manifest",
|
|
188
|
+
severity: "warning",
|
|
189
|
+
file: file.relativePath,
|
|
190
|
+
message: "MCP tool accepts path inputs but has no visible path validation/sanitization",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=mcp-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-manifest.js","sourceRoot":"","sources":["../../src/rules/mcp-manifest.ts"],"names":[],"mappings":"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"}
|
package/dist/scanner/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { ScanResult } from "../types.js";
|
|
1
|
+
import type { ScanResult, ScanConfig } from "../types.js";
|
|
2
2
|
/** Run all rules against a target directory */
|
|
3
|
-
export declare function scan(targetDir: string): ScanResult;
|
|
3
|
+
export declare function scan(targetDir: string, configOverride?: Partial<ScanConfig>): ScanResult;
|
package/dist/scanner/index.js
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
1
|
import { collectFiles, totalLines } from "./files.js";
|
|
2
2
|
import { rules } from "../rules/index.js";
|
|
3
3
|
import { computeScore } from "../score.js";
|
|
4
|
+
import { loadConfig, loadIgnorePatterns, isIgnored } from "../config.js";
|
|
4
5
|
/** Run all rules against a target directory */
|
|
5
|
-
export function scan(targetDir) {
|
|
6
|
+
export function scan(targetDir, configOverride) {
|
|
6
7
|
const start = Date.now();
|
|
7
|
-
|
|
8
|
+
// Load config
|
|
9
|
+
const fileConfig = loadConfig(targetDir);
|
|
10
|
+
const config = { ...fileConfig, ...configOverride };
|
|
11
|
+
// Load ignore patterns
|
|
12
|
+
const ignorePatterns = loadIgnorePatterns(targetDir);
|
|
13
|
+
if (config.ignore) {
|
|
14
|
+
ignorePatterns.push(...config.ignore);
|
|
15
|
+
}
|
|
16
|
+
// Collect and filter files
|
|
17
|
+
let files = collectFiles(targetDir);
|
|
18
|
+
if (ignorePatterns.length > 0) {
|
|
19
|
+
files = files.filter((f) => !isIgnored(f.relativePath, ignorePatterns));
|
|
20
|
+
}
|
|
21
|
+
// Filter rules based on config
|
|
22
|
+
let activeRules = [...rules];
|
|
23
|
+
if (config.rules?.enable) {
|
|
24
|
+
activeRules = activeRules.filter((r) => config.rules.enable.includes(r.id));
|
|
25
|
+
}
|
|
26
|
+
if (config.rules?.disable) {
|
|
27
|
+
activeRules = activeRules.filter((r) => !config.rules.disable.includes(r.id));
|
|
28
|
+
}
|
|
29
|
+
// Run rules
|
|
8
30
|
const findings = [];
|
|
9
|
-
for (const rule of
|
|
31
|
+
for (const rule of activeRules) {
|
|
10
32
|
findings.push(...rule.run(files));
|
|
11
33
|
}
|
|
34
|
+
// Apply severity overrides
|
|
35
|
+
if (config.severity) {
|
|
36
|
+
for (const finding of findings) {
|
|
37
|
+
if (config.severity[finding.rule]) {
|
|
38
|
+
finding.severity = config.severity[finding.rule];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
12
42
|
// Sort: critical first, then warning, then info
|
|
13
43
|
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
14
44
|
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -40,3 +40,13 @@ export interface SkillMetadata {
|
|
|
40
40
|
permissions?: string[];
|
|
41
41
|
[key: string]: unknown;
|
|
42
42
|
}
|
|
43
|
+
/** Scan configuration from .agentshield.yml */
|
|
44
|
+
export interface ScanConfig {
|
|
45
|
+
rules?: {
|
|
46
|
+
enable?: string[];
|
|
47
|
+
disable?: string[];
|
|
48
|
+
};
|
|
49
|
+
severity?: Record<string, "critical" | "warning" | "info">;
|
|
50
|
+
failUnder?: number;
|
|
51
|
+
ignore?: string[];
|
|
52
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal YAML parser for simple config files.
|
|
3
|
+
* Supports: scalars, lists, nested objects (2 levels deep).
|
|
4
|
+
* For complex YAML, use the 'yaml' package.
|
|
5
|
+
*/
|
|
6
|
+
export function parse(input) {
|
|
7
|
+
const result = {};
|
|
8
|
+
const lines = input.split("\n");
|
|
9
|
+
let currentKey = "";
|
|
10
|
+
let currentSubKey = "";
|
|
11
|
+
let currentList = null;
|
|
12
|
+
for (const rawLine of lines) {
|
|
13
|
+
// Skip comments and empty lines
|
|
14
|
+
const line = rawLine.replace(/#.*$/, "");
|
|
15
|
+
if (!line.trim())
|
|
16
|
+
continue;
|
|
17
|
+
const indent = rawLine.search(/\S/);
|
|
18
|
+
// List item
|
|
19
|
+
if (line.trim().startsWith("- ")) {
|
|
20
|
+
const value = line.trim().slice(2).trim().replace(/^["']|["']$/g, "");
|
|
21
|
+
if (currentList) {
|
|
22
|
+
currentList.push(value);
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// Key: value
|
|
27
|
+
const match = line.match(/^(\s*)(\w+)\s*:\s*(.*)/);
|
|
28
|
+
if (!match)
|
|
29
|
+
continue;
|
|
30
|
+
const [, , key, rawValue] = match;
|
|
31
|
+
const value = rawValue.trim().replace(/^["']|["']$/g, "");
|
|
32
|
+
if (indent === 0) {
|
|
33
|
+
// Top-level key
|
|
34
|
+
if (currentList && currentKey) {
|
|
35
|
+
// Save previous list
|
|
36
|
+
if (currentSubKey) {
|
|
37
|
+
result[currentKey][currentSubKey] = currentList;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
result[currentKey] = currentList;
|
|
41
|
+
}
|
|
42
|
+
currentList = null;
|
|
43
|
+
currentSubKey = "";
|
|
44
|
+
}
|
|
45
|
+
currentKey = key;
|
|
46
|
+
if (value) {
|
|
47
|
+
// Scalar value
|
|
48
|
+
result[currentKey] = parseScalar(value);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Object or list follows
|
|
52
|
+
if (!result[currentKey] || typeof result[currentKey] !== "object") {
|
|
53
|
+
result[currentKey] = {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (indent === 2 && currentKey) {
|
|
58
|
+
// Sub-key
|
|
59
|
+
if (currentList && currentSubKey) {
|
|
60
|
+
result[currentKey][currentSubKey] = currentList;
|
|
61
|
+
currentList = null;
|
|
62
|
+
}
|
|
63
|
+
currentSubKey = key;
|
|
64
|
+
if (value) {
|
|
65
|
+
if (typeof result[currentKey] !== "object")
|
|
66
|
+
result[currentKey] = {};
|
|
67
|
+
result[currentKey][currentSubKey] = parseScalar(value);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// List follows
|
|
71
|
+
currentList = [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Save trailing list
|
|
76
|
+
if (currentList) {
|
|
77
|
+
if (currentSubKey && currentKey) {
|
|
78
|
+
if (typeof result[currentKey] !== "object")
|
|
79
|
+
result[currentKey] = {};
|
|
80
|
+
result[currentKey][currentSubKey] = currentList;
|
|
81
|
+
}
|
|
82
|
+
else if (currentKey) {
|
|
83
|
+
result[currentKey] = currentList;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function parseScalar(value) {
|
|
89
|
+
if (value === "true")
|
|
90
|
+
return true;
|
|
91
|
+
if (value === "false")
|
|
92
|
+
return false;
|
|
93
|
+
const num = Number(value);
|
|
94
|
+
if (!isNaN(num) && value !== "")
|
|
95
|
+
return num;
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=yaml-simple.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yaml-simple.js","sourceRoot":"","sources":["../src/yaml-simple.ts"],"names":[],"mappings":"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"}
|