@aiclude/security-skill 2.0.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 +26 -33
- package/dist/index.d.ts +5 -25
- package/dist/index.js +3 -151
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -2,22 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Security vulnerability scanner for MCP Servers and AI Agent Skills. Provides the `/security-scan` slash command for Claude Code.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- **Name-based lookup**: Query the [AIclude scan database](https://vs.aiclude.com) for existing vulnerability reports. If none exists, the target is automatically registered and scanned.
|
|
8
|
-
- **Local scan**: Run 7 scan engines directly on a local directory — fully offline, no data sent anywhere.
|
|
9
|
-
|
|
10
|
-
## Scan Engines
|
|
11
|
-
|
|
12
|
-
| Engine | What It Detects |
|
|
13
|
-
|--------|----------------|
|
|
14
|
-
| SAST | Code vulnerabilities via pattern matching |
|
|
15
|
-
| SCA | Known CVEs in dependencies (OSV.dev) |
|
|
16
|
-
| Tool Analyzer | MCP tool poisoning, shadowing, rug-pull |
|
|
17
|
-
| DAST | SQL/Command/XSS injection via fuzzing |
|
|
18
|
-
| Permission Checker | Excessive filesystem/network/process access |
|
|
19
|
-
| Behavior Monitor | Suspicious runtime behavior patterns |
|
|
20
|
-
| Malware Detector | Backdoors, cryptominers, ransomware, data stealers |
|
|
5
|
+
Queries the [AIclude scan database](https://vs.aiclude.com) for existing vulnerability reports. If no report exists, the target is automatically registered and scanned server-side.
|
|
21
6
|
|
|
22
7
|
## Installation
|
|
23
8
|
|
|
@@ -30,11 +15,8 @@ npm install @aiclude/security-skill
|
|
|
30
15
|
### As a Claude Code Skill
|
|
31
16
|
|
|
32
17
|
```
|
|
33
|
-
# Look up scan results by package name
|
|
34
18
|
/security-scan --name @anthropic/mcp-server-fetch
|
|
35
|
-
|
|
36
|
-
# Scan a local directory
|
|
37
|
-
/security-scan ./my-mcp-server
|
|
19
|
+
/security-scan --name my-awesome-skill --type skill
|
|
38
20
|
```
|
|
39
21
|
|
|
40
22
|
### Programmatic API
|
|
@@ -44,18 +26,10 @@ import { SkillHandler } from "@aiclude/security-skill";
|
|
|
44
26
|
|
|
45
27
|
const handler = new SkillHandler();
|
|
46
28
|
|
|
47
|
-
// Remote lookup — queries AIclude scan database
|
|
48
29
|
const report = await handler.lookup({
|
|
49
30
|
name: "@some/mcp-server",
|
|
50
31
|
type: "mcp-server",
|
|
51
32
|
});
|
|
52
|
-
|
|
53
|
-
// Local scan — runs 7 engines offline
|
|
54
|
-
const result = await handler.handle({
|
|
55
|
-
targetPath: "./my-project",
|
|
56
|
-
type: "mcp-server",
|
|
57
|
-
format: "markdown",
|
|
58
|
-
});
|
|
59
33
|
```
|
|
60
34
|
|
|
61
35
|
## Parameters
|
|
@@ -63,11 +37,30 @@ const result = await handler.handle({
|
|
|
63
37
|
| Parameter | Description |
|
|
64
38
|
|-----------|-------------|
|
|
65
39
|
| `--name` | Package name to search (npm, GitHub, etc.) |
|
|
66
|
-
| `target-path` | Local directory to scan |
|
|
67
40
|
| `--type` | `mcp-server` or `skill` (auto-detected) |
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
41
|
+
|
|
42
|
+
## How It Works
|
|
43
|
+
|
|
44
|
+
1. Sends the package name to the AIclude scan API
|
|
45
|
+
2. If a scan report exists, returns it immediately
|
|
46
|
+
3. If not, registers the target for server-side scanning
|
|
47
|
+
4. Waits for the scan to complete and returns the results
|
|
48
|
+
|
|
49
|
+
Only the package name and type are sent. No source code, files, or credentials are transmitted.
|
|
50
|
+
|
|
51
|
+
## Server-Side Scan Engines
|
|
52
|
+
|
|
53
|
+
The AIclude server runs 7 engines on registered targets:
|
|
54
|
+
|
|
55
|
+
| Engine | What It Detects |
|
|
56
|
+
|--------|----------------|
|
|
57
|
+
| SAST | Code vulnerabilities via pattern matching |
|
|
58
|
+
| SCA | Known CVEs in dependencies (OSV.dev) |
|
|
59
|
+
| Tool Analyzer | MCP tool poisoning, shadowing, rug-pull |
|
|
60
|
+
| DAST | SQL/Command/XSS injection via fuzzing |
|
|
61
|
+
| Permission Checker | Excessive filesystem/network/process access |
|
|
62
|
+
| Behavior Monitor | Suspicious runtime behavior patterns |
|
|
63
|
+
| Malware Detector | Backdoors, cryptominers, ransomware, data stealers |
|
|
71
64
|
|
|
72
65
|
## Output
|
|
73
66
|
|
|
@@ -81,7 +74,7 @@ Reports include:
|
|
|
81
74
|
## Related Packages
|
|
82
75
|
|
|
83
76
|
- [`@aiclude/security-mcp`](https://www.npmjs.com/package/@aiclude/security-mcp) — MCP Server interface
|
|
84
|
-
- [vs.aiclude.com](https://vs.aiclude.com) — Web dashboard
|
|
77
|
+
- [vs.aiclude.com](https://vs.aiclude.com) — Web dashboard with full scan results
|
|
85
78
|
|
|
86
79
|
## License
|
|
87
80
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import { ReportFormat, ScanEngineId } from '@asvs/core';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Skill Handler
|
|
5
3
|
* Processes /security-scan skill invocations from Claude Code
|
|
6
|
-
* 서버 조회 → 없으면 등록 → 스캔 결과 반환
|
|
7
|
-
* 로컬 스캔도 지원
|
|
4
|
+
* 서버 DB 조회 → 없으면 등록 → 스캔 결과 반환
|
|
8
5
|
*/
|
|
9
|
-
|
|
10
|
-
interface SkillInvocation {
|
|
11
|
-
targetPath: string;
|
|
12
|
-
type?: "mcp-server" | "skill";
|
|
13
|
-
profile?: "strict" | "standard" | "permissive";
|
|
14
|
-
format?: ReportFormat;
|
|
15
|
-
engines?: ScanEngineId[];
|
|
16
|
-
}
|
|
17
|
-
/** API 기반 조회 요청 */
|
|
18
6
|
interface SkillLookupInvocation {
|
|
19
7
|
name: string;
|
|
20
8
|
type?: "mcp-server" | "skill";
|
|
@@ -23,23 +11,15 @@ interface SkillLookupInvocation {
|
|
|
23
11
|
npmPackage?: string;
|
|
24
12
|
}
|
|
25
13
|
declare class SkillHandler {
|
|
26
|
-
|
|
27
|
-
private reporter;
|
|
28
|
-
constructor();
|
|
29
|
-
/** API 인증 헤더 생성 — 시간 기반 서명, 환경변수 불필요 */
|
|
14
|
+
/** API 인증 헤더 생성 */
|
|
30
15
|
private createAuthHeaders;
|
|
31
16
|
/**
|
|
32
|
-
*
|
|
33
|
-
* 기존
|
|
17
|
+
* 보안 스캔 조회/등록
|
|
18
|
+
* 기존 결과 검색 → 없으면 서버에 등록 → 스캔 실행 → 결과 반환
|
|
34
19
|
*/
|
|
35
20
|
lookup(invocation: SkillLookupInvocation): Promise<string>;
|
|
36
|
-
/** Handle a /security-scan invocation (local path scan) */
|
|
37
|
-
handle(invocation: SkillInvocation): Promise<string>;
|
|
38
|
-
private detectTargetType;
|
|
39
|
-
private formatOutput;
|
|
40
|
-
/** API에서 받은 상세 리포트를 마크다운으로 포맷 */
|
|
41
21
|
private formatApiReport;
|
|
42
22
|
private formatScanSummary;
|
|
43
23
|
}
|
|
44
24
|
|
|
45
|
-
export { SkillHandler, type
|
|
25
|
+
export { SkillHandler, type SkillLookupInvocation };
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
// src/skill-handler.ts
|
|
2
|
-
import {
|
|
3
|
-
Scanner,
|
|
4
|
-
SastEngine,
|
|
5
|
-
ScaEngine,
|
|
6
|
-
ToolAnalyzerEngine,
|
|
7
|
-
MalwareDetectorEngine,
|
|
8
|
-
PermissionCheckerEngine,
|
|
9
|
-
DastEngine,
|
|
10
|
-
BehaviorMonitorEngine,
|
|
11
|
-
RiskLevel,
|
|
12
|
-
validateScanPath
|
|
13
|
-
} from "@asvs/core";
|
|
14
|
-
import { ReportGenerator } from "@asvs/reporter";
|
|
15
|
-
import { existsSync } from "fs";
|
|
16
|
-
import { resolve } from "path";
|
|
17
2
|
import { createHmac } from "crypto";
|
|
18
3
|
var API_BASE = "https://vs-api.aiclude.com";
|
|
19
4
|
function createRequestSignature(name, timestamp) {
|
|
@@ -23,20 +8,7 @@ function createRequestSignature(name, timestamp) {
|
|
|
23
8
|
return createHmac("sha256", derivedKey).update(payload).digest("hex");
|
|
24
9
|
}
|
|
25
10
|
var SkillHandler = class {
|
|
26
|
-
|
|
27
|
-
reporter;
|
|
28
|
-
constructor() {
|
|
29
|
-
this.scanner = new Scanner();
|
|
30
|
-
this.reporter = new ReportGenerator();
|
|
31
|
-
this.scanner.registerEngine(new SastEngine());
|
|
32
|
-
this.scanner.registerEngine(new ScaEngine());
|
|
33
|
-
this.scanner.registerEngine(new ToolAnalyzerEngine());
|
|
34
|
-
this.scanner.registerEngine(new MalwareDetectorEngine());
|
|
35
|
-
this.scanner.registerEngine(new PermissionCheckerEngine());
|
|
36
|
-
this.scanner.registerEngine(new DastEngine());
|
|
37
|
-
this.scanner.registerEngine(new BehaviorMonitorEngine());
|
|
38
|
-
}
|
|
39
|
-
/** API 인증 헤더 생성 — 시간 기반 서명, 환경변수 불필요 */
|
|
11
|
+
/** API 인증 헤더 생성 */
|
|
40
12
|
createAuthHeaders(name) {
|
|
41
13
|
const timestamp = String(Date.now());
|
|
42
14
|
const signature = createRequestSignature(name, timestamp);
|
|
@@ -48,8 +20,8 @@ var SkillHandler = class {
|
|
|
48
20
|
};
|
|
49
21
|
}
|
|
50
22
|
/**
|
|
51
|
-
*
|
|
52
|
-
* 기존
|
|
23
|
+
* 보안 스캔 조회/등록
|
|
24
|
+
* 기존 결과 검색 → 없으면 서버에 등록 → 스캔 실행 → 결과 반환
|
|
53
25
|
*/
|
|
54
26
|
async lookup(invocation) {
|
|
55
27
|
const apiUrl = `${API_BASE}/api/v1/scan/lookup`;
|
|
@@ -113,126 +85,6 @@ var SkillHandler = class {
|
|
|
113
85
|
}
|
|
114
86
|
return `API response: ${JSON.stringify(lookupData, null, 2)}`;
|
|
115
87
|
}
|
|
116
|
-
/** Handle a /security-scan invocation (local path scan) */
|
|
117
|
-
async handle(invocation) {
|
|
118
|
-
const defaultConfig = {
|
|
119
|
-
engines: ["sast", "sca", "tool-analyzer", "permission-checker", "malware-detector", "dast", "behavior-monitor"],
|
|
120
|
-
sandboxProfile: "standard",
|
|
121
|
-
rulesets: ["default"],
|
|
122
|
-
timeout: 3e5,
|
|
123
|
-
maxConcurrency: 4,
|
|
124
|
-
skipPatterns: ["node_modules/**", ".git/**", "dist/**", "coverage/**"]
|
|
125
|
-
};
|
|
126
|
-
const scanConfig = {
|
|
127
|
-
...defaultConfig,
|
|
128
|
-
...invocation.profile && { sandboxProfile: invocation.profile },
|
|
129
|
-
...invocation.engines && { engines: invocation.engines }
|
|
130
|
-
};
|
|
131
|
-
const targetPath = validateScanPath(invocation.targetPath);
|
|
132
|
-
const targetType = invocation.type ?? this.detectTargetType(targetPath);
|
|
133
|
-
const target = {
|
|
134
|
-
type: targetType,
|
|
135
|
-
name: targetPath.split("/").pop() ?? targetPath,
|
|
136
|
-
path: targetPath
|
|
137
|
-
};
|
|
138
|
-
const scanResult = await this.scanner.scan(target, scanConfig);
|
|
139
|
-
const format = invocation.format ?? "markdown";
|
|
140
|
-
const report = this.reporter.generate(scanResult, format);
|
|
141
|
-
return this.formatOutput(report, format, scanResult.engineResults.flatMap((r) => r.vulnerabilities));
|
|
142
|
-
}
|
|
143
|
-
detectTargetType(targetPath) {
|
|
144
|
-
if (existsSync(resolve(targetPath, "SKILL.md")) || targetPath.endsWith("SKILL.md") || targetPath.includes("/skill")) {
|
|
145
|
-
return "skill";
|
|
146
|
-
}
|
|
147
|
-
return "mcp-server";
|
|
148
|
-
}
|
|
149
|
-
formatOutput(report, format, vulnerabilities) {
|
|
150
|
-
if (format === "json") {
|
|
151
|
-
return JSON.stringify(report, null, 2);
|
|
152
|
-
}
|
|
153
|
-
const lines = [];
|
|
154
|
-
const { summary } = report.scanResult;
|
|
155
|
-
const icon = summary.overallRiskLevel === RiskLevel.CRITICAL ? "[CRITICAL]" : summary.overallRiskLevel === RiskLevel.HIGH ? "[HIGH RISK]" : summary.overallRiskLevel === RiskLevel.MEDIUM ? "[MEDIUM RISK]" : summary.overallRiskLevel === RiskLevel.LOW ? "[LOW RISK]" : "[CLEAN]";
|
|
156
|
-
lines.push(`# ${icon} Security Scan Report: ${report.title}`);
|
|
157
|
-
lines.push("");
|
|
158
|
-
lines.push(`| Metric | Value |`);
|
|
159
|
-
lines.push(`|--------|-------|`);
|
|
160
|
-
lines.push(`| Scan Date | ${report.generatedAt} |`);
|
|
161
|
-
lines.push(`| Target | ${report.scanResult.target.path} |`);
|
|
162
|
-
lines.push(`| Target Type | ${report.scanResult.target.type} |`);
|
|
163
|
-
lines.push(`| Overall Risk | **${summary.overallRiskLevel}** |`);
|
|
164
|
-
lines.push(`| Risk Score | **${summary.overallScore}/100** |`);
|
|
165
|
-
lines.push(`| Total Findings | ${summary.totalVulnerabilities} |`);
|
|
166
|
-
lines.push(`| Scan Duration | ${report.scanResult.duration}ms |`);
|
|
167
|
-
lines.push("");
|
|
168
|
-
lines.push("## Severity Breakdown");
|
|
169
|
-
lines.push("");
|
|
170
|
-
lines.push(`| Severity | Count |`);
|
|
171
|
-
lines.push(`|----------|-------|`);
|
|
172
|
-
for (const [level, count] of Object.entries(summary.bySeverity)) {
|
|
173
|
-
if (count > 0) {
|
|
174
|
-
lines.push(`| **${level}** | ${count} |`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
lines.push("");
|
|
178
|
-
if (vulnerabilities.length > 0) {
|
|
179
|
-
lines.push("## Detailed Findings");
|
|
180
|
-
lines.push("");
|
|
181
|
-
const sorted = [...vulnerabilities].sort((a, b) => {
|
|
182
|
-
const order = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, INFO: 4 };
|
|
183
|
-
return (order[a.severity] ?? 5) - (order[b.severity] ?? 5);
|
|
184
|
-
});
|
|
185
|
-
const top = sorted.slice(0, 20);
|
|
186
|
-
for (let i = 0; i < top.length; i++) {
|
|
187
|
-
const v = top[i];
|
|
188
|
-
lines.push(`### ${i + 1}. [${v.severity}] ${v.title}`);
|
|
189
|
-
lines.push("");
|
|
190
|
-
lines.push(`- **ID**: ${v.id}`);
|
|
191
|
-
lines.push(`- **Category**: ${v.category}`);
|
|
192
|
-
lines.push(`- **Confidence**: ${Math.round(v.confidence * 100)}%`);
|
|
193
|
-
if (v.location) {
|
|
194
|
-
lines.push(`- **Location**: \`${v.location.file}:${v.location.line}\``);
|
|
195
|
-
}
|
|
196
|
-
lines.push(`- **Description**: ${v.description}`);
|
|
197
|
-
lines.push(`- **Remediation**: ${v.remediation}`);
|
|
198
|
-
if (v.cveIds?.length) {
|
|
199
|
-
lines.push(`- **CVE**: ${v.cveIds.join(", ")}`);
|
|
200
|
-
}
|
|
201
|
-
if (v.location?.snippet) {
|
|
202
|
-
lines.push("");
|
|
203
|
-
lines.push("```");
|
|
204
|
-
lines.push(v.location.snippet.substring(0, 300));
|
|
205
|
-
lines.push("```");
|
|
206
|
-
}
|
|
207
|
-
lines.push("");
|
|
208
|
-
}
|
|
209
|
-
if (sorted.length > 20) {
|
|
210
|
-
lines.push(`> ... and ${sorted.length - 20} more findings. Use \`--format json\` for the complete list.`);
|
|
211
|
-
lines.push("");
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (report.risks.length > 0) {
|
|
215
|
-
lines.push("## Risk Assessment");
|
|
216
|
-
lines.push("");
|
|
217
|
-
for (const risk of report.risks) {
|
|
218
|
-
lines.push(`### [${risk.level}] ${risk.area}`);
|
|
219
|
-
lines.push(`- **Impact**: ${risk.impact}`);
|
|
220
|
-
lines.push(`- **Likelihood**: ${risk.likelihood}`);
|
|
221
|
-
lines.push(`- **Remediation**: ${risk.remediation}`);
|
|
222
|
-
lines.push("");
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (report.precautions.length > 0) {
|
|
226
|
-
lines.push("## Precautions & Warnings");
|
|
227
|
-
lines.push("");
|
|
228
|
-
for (const precaution of report.precautions) {
|
|
229
|
-
lines.push(`- ${precaution}`);
|
|
230
|
-
}
|
|
231
|
-
lines.push("");
|
|
232
|
-
}
|
|
233
|
-
return lines.join("\n");
|
|
234
|
-
}
|
|
235
|
-
/** API에서 받은 상세 리포트를 마크다운으로 포맷 */
|
|
236
88
|
formatApiReport(report, target) {
|
|
237
89
|
const lines = [];
|
|
238
90
|
const scanResult = report["scanResult"];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiclude/security-skill",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "AIclude Security Vulnerability Scanner - Claude Code Skill for
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "AIclude Security Vulnerability Scanner - Claude Code Skill for querying the AIclude scan database",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -53,10 +53,7 @@
|
|
|
53
53
|
"clean": "rm -rf dist",
|
|
54
54
|
"prepublishOnly": "npm run build"
|
|
55
55
|
},
|
|
56
|
-
"dependencies": {
|
|
57
|
-
"@asvs/core": "workspace:*",
|
|
58
|
-
"@asvs/reporter": "workspace:*"
|
|
59
|
-
},
|
|
56
|
+
"dependencies": {},
|
|
60
57
|
"devDependencies": {
|
|
61
58
|
"tsup": "^8.0.0",
|
|
62
59
|
"typescript": "^5.5.0"
|