@sentinel-atl/scanner 0.3.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 +104 -0
- package/dist/dependency-scanner.d.ts +22 -0
- package/dist/dependency-scanner.d.ts.map +1 -0
- package/dist/dependency-scanner.js +70 -0
- package/dist/dependency-scanner.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/package-resolver.d.ts +30 -0
- package/dist/package-resolver.d.ts.map +1 -0
- package/dist/package-resolver.js +105 -0
- package/dist/package-resolver.js.map +1 -0
- package/dist/pattern-scanner.d.ts +30 -0
- package/dist/pattern-scanner.d.ts.map +1 -0
- package/dist/pattern-scanner.js +178 -0
- package/dist/pattern-scanner.js.map +1 -0
- package/dist/permission-scanner.d.ts +28 -0
- package/dist/permission-scanner.d.ts.map +1 -0
- package/dist/permission-scanner.js +100 -0
- package/dist/permission-scanner.js.map +1 -0
- package/dist/publisher-scanner.d.ts +56 -0
- package/dist/publisher-scanner.d.ts.map +1 -0
- package/dist/publisher-scanner.js +238 -0
- package/dist/publisher-scanner.js.map +1 -0
- package/dist/scanner.d.ts +61 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +71 -0
- package/dist/scanner.js.map +1 -0
- package/dist/stc.d.ts +88 -0
- package/dist/stc.d.ts.map +1 -0
- package/dist/stc.js +115 -0
- package/dist/stc.js.map +1 -0
- package/dist/tool-prober.d.ts +46 -0
- package/dist/tool-prober.d.ts.map +1 -0
- package/dist/tool-prober.js +158 -0
- package/dist/tool-prober.js.map +1 -0
- package/dist/trust-score.d.ts +26 -0
- package/dist/trust-score.d.ts.map +1 -0
- package/dist/trust-score.js +65 -0
- package/dist/trust-score.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @sentinel-atl/scanner
|
|
2
|
+
|
|
3
|
+
MCP server security scanner — npm audit meets AI agent trust verification.
|
|
4
|
+
|
|
5
|
+
Analyzes MCP server packages for dependency vulnerabilities, dangerous code patterns, permission requirements, and publisher identity. Produces a 0–100 trust score and can issue/verify Sentinel Trust Certificates (STCs).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @sentinel-atl/scanner
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { scan, issueSTC } from '@sentinel-atl/scanner';
|
|
17
|
+
|
|
18
|
+
// Full security scan
|
|
19
|
+
const report = await scan({ packageName: 'some-mcp-server' });
|
|
20
|
+
console.log(report.trustScore.overall); // 0-100
|
|
21
|
+
console.log(report.trustScore.grade); // A-F
|
|
22
|
+
|
|
23
|
+
// Issue a Sentinel Trust Certificate
|
|
24
|
+
const stc = await issueSTC({
|
|
25
|
+
issuer: { did: 'did:key:z6Mk...' },
|
|
26
|
+
subject: { packageName: 'some-mcp-server', packageVersion: '1.0.0' },
|
|
27
|
+
findings: report.findings,
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Security Analysis
|
|
32
|
+
|
|
33
|
+
The scanner runs 4 sub-scanners and aggregates results into a single trust score:
|
|
34
|
+
|
|
35
|
+
### 1. Dependency Vulnerabilities
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { scanDependencies } from '@sentinel-atl/scanner';
|
|
39
|
+
const result = await scanDependencies('/path/to/package');
|
|
40
|
+
// Integrates with npm audit
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Code Pattern Analysis
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { scanCodePatterns } from '@sentinel-atl/scanner';
|
|
47
|
+
const result = await scanCodePatterns('/path/to/package');
|
|
48
|
+
// Detects: eval, child_process, fs, net, exfiltration, obfuscation
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Permission Analysis
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { scanPermissions } from '@sentinel-atl/scanner';
|
|
55
|
+
const result = await scanPermissions('/path/to/package');
|
|
56
|
+
// Analyzes: filesystem, network, process, crypto, environment, native
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 4. Publisher Identity
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { scanPublisher } from '@sentinel-atl/scanner';
|
|
63
|
+
const result = await scanPublisher('express');
|
|
64
|
+
// Checks npm registry: maintainers, downloads, repository presence
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Trust Score
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { computeTrustScore } from '@sentinel-atl/scanner';
|
|
71
|
+
|
|
72
|
+
const score = computeTrustScore(findings);
|
|
73
|
+
// { overall: 82, grade: 'B', breakdown: { dependencies, codePatterns, permissions, publisher } }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Grading scale: **A** (90–100), **B** (80–89), **C** (70–79), **D** (60–69), **F** (0–59).
|
|
77
|
+
|
|
78
|
+
## Sentinel Trust Certificates (STC)
|
|
79
|
+
|
|
80
|
+
STCs are signed attestations of a scan result — the core artifact of the Sentinel trust ecosystem.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { issueSTC, verifySTC } from '@sentinel-atl/scanner';
|
|
84
|
+
|
|
85
|
+
// Issue
|
|
86
|
+
const stc = await issueSTC({ issuer, subject, findings });
|
|
87
|
+
|
|
88
|
+
// Verify
|
|
89
|
+
const result = verifySTC(stc);
|
|
90
|
+
// { valid: true, expired: false, ... }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## MCP Tool Probing
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { probeTools } from '@sentinel-atl/scanner';
|
|
97
|
+
|
|
98
|
+
const result = await probeTools('http://localhost:4000/sse');
|
|
99
|
+
// Returns tool definitions with permission analysis
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency scanner — checks for known vulnerabilities via npm audit.
|
|
3
|
+
*/
|
|
4
|
+
import type { Finding } from './scanner.js';
|
|
5
|
+
export interface VulnerablePackage {
|
|
6
|
+
name: string;
|
|
7
|
+
severity: 'critical' | 'high' | 'moderate' | 'low' | 'info';
|
|
8
|
+
title: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
range?: string;
|
|
11
|
+
fixAvailable: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface DependencyScanResult {
|
|
14
|
+
vulnerabilities: VulnerablePackage[];
|
|
15
|
+
totalDependencies: number;
|
|
16
|
+
findings: Finding[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run `npm audit --json` against a package directory and parse results.
|
|
20
|
+
*/
|
|
21
|
+
export declare function scanDependencies(packagePath: string): Promise<DependencyScanResult>;
|
|
22
|
+
//# sourceMappingURL=dependency-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-scanner.d.ts","sourceRoot":"","sources":["../src/dependency-scanner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,iBAAiB,EAAE,CAAC;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAoDzF"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency scanner — checks for known vulnerabilities via npm audit.
|
|
3
|
+
*/
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
/**
|
|
8
|
+
* Run `npm audit --json` against a package directory and parse results.
|
|
9
|
+
*/
|
|
10
|
+
export async function scanDependencies(packagePath) {
|
|
11
|
+
const findings = [];
|
|
12
|
+
const vulnerabilities = [];
|
|
13
|
+
// Check if package-lock.json exists (npm audit requires it)
|
|
14
|
+
const lockPath = join(packagePath, 'package-lock.json');
|
|
15
|
+
if (!existsSync(lockPath)) {
|
|
16
|
+
return { vulnerabilities: [], totalDependencies: 0, findings };
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const auditOutput = await runNpmAudit(packagePath);
|
|
20
|
+
const audit = JSON.parse(auditOutput);
|
|
21
|
+
const totalDependencies = audit.metadata?.totalDependencies ?? 0;
|
|
22
|
+
// Parse npm audit v2 format
|
|
23
|
+
if (audit.vulnerabilities) {
|
|
24
|
+
for (const [name, info] of Object.entries(audit.vulnerabilities)) {
|
|
25
|
+
const vuln = {
|
|
26
|
+
name,
|
|
27
|
+
severity: info.severity ?? 'info',
|
|
28
|
+
title: info.via?.[0]?.title ?? info.via?.[0] ?? 'Unknown vulnerability',
|
|
29
|
+
url: info.via?.[0]?.url,
|
|
30
|
+
range: info.range,
|
|
31
|
+
fixAvailable: !!info.fixAvailable,
|
|
32
|
+
};
|
|
33
|
+
vulnerabilities.push(vuln);
|
|
34
|
+
const severityMap = {
|
|
35
|
+
critical: 'critical',
|
|
36
|
+
high: 'high',
|
|
37
|
+
moderate: 'medium',
|
|
38
|
+
low: 'low',
|
|
39
|
+
info: 'info',
|
|
40
|
+
};
|
|
41
|
+
findings.push({
|
|
42
|
+
severity: severityMap[vuln.severity] ?? 'info',
|
|
43
|
+
category: 'vulnerability',
|
|
44
|
+
title: `Vulnerable dependency: ${name}`,
|
|
45
|
+
description: typeof vuln.title === 'string' ? vuln.title : `Vulnerability in ${name}`,
|
|
46
|
+
evidence: vuln.url,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { vulnerabilities, totalDependencies, findings };
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// npm audit failed — could be no lockfile, network issue, etc.
|
|
54
|
+
return { vulnerabilities: [], totalDependencies: 0, findings };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function runNpmAudit(cwd) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
execFile('npm', ['audit', '--json', '--omit=dev'], { cwd, maxBuffer: 10 * 1024 * 1024 }, (error, stdout) => {
|
|
60
|
+
// npm audit exits with non-zero when vulnerabilities exist, but still outputs JSON
|
|
61
|
+
if (stdout) {
|
|
62
|
+
resolve(stdout);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
reject(error ?? new Error('npm audit produced no output'));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=dependency-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-scanner.js","sourceRoot":"","sources":["../src/dependency-scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sentinel-atl/scanner — MCP Server Security Scanner
|
|
3
|
+
*
|
|
4
|
+
* "npm audit for MCP servers."
|
|
5
|
+
*
|
|
6
|
+
* Static analysis engine that scans MCP server packages for:
|
|
7
|
+
* 1. Dependency vulnerabilities (npm audit integration)
|
|
8
|
+
* 2. Dangerous code patterns (eval, child_process, fs, net, exfiltration)
|
|
9
|
+
* 3. Obfuscation detection (encoded payloads, minified code in src)
|
|
10
|
+
* 4. Permission scope analysis (what system resources does it access?)
|
|
11
|
+
* 5. Publisher identity verification (npm registry checks)
|
|
12
|
+
*
|
|
13
|
+
* Produces a TrustScore (0-100) and a ScanReport with detailed findings.
|
|
14
|
+
*/
|
|
15
|
+
export { scan, type ScanOptions, type ScanReport, type TrustScore, type Finding, type FindingSeverity, type FindingCategory, } from './scanner.js';
|
|
16
|
+
export { scanDependencies, type DependencyScanResult, type VulnerablePackage, } from './dependency-scanner.js';
|
|
17
|
+
export { scanCodePatterns, type PatternScanResult, type CodePattern, type PatternCategory, } from './pattern-scanner.js';
|
|
18
|
+
export { scanPermissions, type PermissionScanResult, type DetectedPermission, type PermissionKind, } from './permission-scanner.js';
|
|
19
|
+
export { computeTrustScore, type ScoreBreakdown, } from './trust-score.js';
|
|
20
|
+
export { issueSTC, verifySTC, type SentinelTrustCertificate, type STCIssuer, type STCSubject, type STCFindingSummary, type STCProof, type STCVerifyResult, type IssueSTCOptions, } from './stc.js';
|
|
21
|
+
export { resolvePackage, cleanupPackage, type ResolvedPackage, } from './package-resolver.js';
|
|
22
|
+
export { probeTools, type ToolProbeResult, type MCPTool, type ProbeOptions, } from './tool-prober.js';
|
|
23
|
+
export { scanPublisher, type PublisherInfo, type PublisherScanResult, } from './publisher-scanner.js';
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,IAAI,EACJ,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,QAAQ,EACR,SAAS,EACT,KAAK,wBAAwB,EAC7B,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,cAAc,EACd,cAAc,EACd,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,YAAY,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,mBAAmB,GACzB,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sentinel-atl/scanner — MCP Server Security Scanner
|
|
3
|
+
*
|
|
4
|
+
* "npm audit for MCP servers."
|
|
5
|
+
*
|
|
6
|
+
* Static analysis engine that scans MCP server packages for:
|
|
7
|
+
* 1. Dependency vulnerabilities (npm audit integration)
|
|
8
|
+
* 2. Dangerous code patterns (eval, child_process, fs, net, exfiltration)
|
|
9
|
+
* 3. Obfuscation detection (encoded payloads, minified code in src)
|
|
10
|
+
* 4. Permission scope analysis (what system resources does it access?)
|
|
11
|
+
* 5. Publisher identity verification (npm registry checks)
|
|
12
|
+
*
|
|
13
|
+
* Produces a TrustScore (0-100) and a ScanReport with detailed findings.
|
|
14
|
+
*/
|
|
15
|
+
export { scan, } from './scanner.js';
|
|
16
|
+
export { scanDependencies, } from './dependency-scanner.js';
|
|
17
|
+
export { scanCodePatterns, } from './pattern-scanner.js';
|
|
18
|
+
export { scanPermissions, } from './permission-scanner.js';
|
|
19
|
+
export { computeTrustScore, } from './trust-score.js';
|
|
20
|
+
export { issueSTC, verifySTC, } from './stc.js';
|
|
21
|
+
export { resolvePackage, cleanupPackage, } from './package-resolver.js';
|
|
22
|
+
export { probeTools, } from './tool-prober.js';
|
|
23
|
+
export { scanPublisher, } from './publisher-scanner.js';
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,IAAI,GAOL,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,gBAAgB,GAGjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,gBAAgB,GAIjB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,eAAe,GAIhB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,iBAAiB,GAElB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,QAAQ,EACR,SAAS,GAQV,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,cAAc,EACd,cAAc,GAEf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,UAAU,GAIX,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,GAGd,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NPM package resolver — download and extract npm packages for scanning.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - npm package names: "@modelcontextprotocol/server-filesystem"
|
|
6
|
+
* - npm package@version: "@scope/name@1.2.3"
|
|
7
|
+
* - Local paths: "./my-server" or "/abs/path"
|
|
8
|
+
* - GitHub URLs (future): "github:user/repo"
|
|
9
|
+
*/
|
|
10
|
+
export interface ResolvedPackage {
|
|
11
|
+
/** Absolute path to the extracted package directory */
|
|
12
|
+
path: string;
|
|
13
|
+
/** Package name from package.json */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Package version from package.json */
|
|
16
|
+
version: string;
|
|
17
|
+
/** Source type */
|
|
18
|
+
source: 'npm' | 'local';
|
|
19
|
+
/** Whether we need to clean up (true for npm downloads) */
|
|
20
|
+
isTemporary: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a package specifier to a local directory ready for scanning.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolvePackage(specifier: string): Promise<ResolvedPackage>;
|
|
26
|
+
/**
|
|
27
|
+
* Clean up a temporary package directory.
|
|
28
|
+
*/
|
|
29
|
+
export declare function cleanupPackage(resolved: ResolvedPackage): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=package-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-resolver.d.ts","sourceRoot":"","sources":["../src/package-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB;IAClB,MAAM,EAAE,KAAK,GAAG,OAAO,CAAC;IACxB,2DAA2D;IAC3D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAmBhF;AA6CD;;GAEG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAM7E"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NPM package resolver — download and extract npm packages for scanning.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - npm package names: "@modelcontextprotocol/server-filesystem"
|
|
6
|
+
* - npm package@version: "@scope/name@1.2.3"
|
|
7
|
+
* - Local paths: "./my-server" or "/abs/path"
|
|
8
|
+
* - GitHub URLs (future): "github:user/repo"
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from 'node:child_process';
|
|
11
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
12
|
+
import { existsSync } from 'node:fs';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a package specifier to a local directory ready for scanning.
|
|
17
|
+
*/
|
|
18
|
+
export async function resolvePackage(specifier) {
|
|
19
|
+
// Check if it's a local path (starts with . or / or is absolute, or exists on disk)
|
|
20
|
+
const absPath = resolve(specifier);
|
|
21
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || isAbsolute(specifier) || existsSync(absPath)) {
|
|
22
|
+
if (!existsSync(absPath)) {
|
|
23
|
+
throw new Error(`Local path not found: ${absPath}`);
|
|
24
|
+
}
|
|
25
|
+
const pkg = await readPackageJson(absPath);
|
|
26
|
+
return {
|
|
27
|
+
path: absPath,
|
|
28
|
+
name: pkg.name ?? 'unknown',
|
|
29
|
+
version: pkg.version ?? '0.0.0',
|
|
30
|
+
source: 'local',
|
|
31
|
+
isTemporary: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// npm package
|
|
35
|
+
return resolveFromNpm(specifier);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Download a package from npm registry and extract it.
|
|
39
|
+
*/
|
|
40
|
+
async function resolveFromNpm(specifier) {
|
|
41
|
+
const tmpDir = await mkdtemp(join(tmpdir(), 'sentinel-scan-'));
|
|
42
|
+
try {
|
|
43
|
+
// Use `npm pack` to download the tarball, then extract
|
|
44
|
+
await execPromise('npm', ['pack', specifier, '--pack-destination', tmpDir], { cwd: tmpDir });
|
|
45
|
+
// Find the .tgz file
|
|
46
|
+
const { readdir } = await import('node:fs/promises');
|
|
47
|
+
const files = await readdir(tmpDir);
|
|
48
|
+
const tgz = files.find(f => f.endsWith('.tgz'));
|
|
49
|
+
if (!tgz) {
|
|
50
|
+
throw new Error(`npm pack did not produce a tarball for: ${specifier}`);
|
|
51
|
+
}
|
|
52
|
+
// Extract
|
|
53
|
+
await execPromise('tar', ['xzf', join(tmpDir, tgz), '-C', tmpDir]);
|
|
54
|
+
// npm pack extracts to a `package/` subdirectory
|
|
55
|
+
const packageDir = join(tmpDir, 'package');
|
|
56
|
+
if (!existsSync(packageDir)) {
|
|
57
|
+
throw new Error('Extracted package directory not found');
|
|
58
|
+
}
|
|
59
|
+
const pkg = await readPackageJson(packageDir);
|
|
60
|
+
return {
|
|
61
|
+
path: packageDir,
|
|
62
|
+
name: pkg.name ?? specifier,
|
|
63
|
+
version: pkg.version ?? '0.0.0',
|
|
64
|
+
source: 'npm',
|
|
65
|
+
isTemporary: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
// Clean up on failure
|
|
70
|
+
await rm(tmpDir, { recursive: true, force: true }).catch(() => { });
|
|
71
|
+
throw new Error(`Failed to resolve npm package "${specifier}": ${err.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Clean up a temporary package directory.
|
|
76
|
+
*/
|
|
77
|
+
export async function cleanupPackage(resolved) {
|
|
78
|
+
if (resolved.isTemporary) {
|
|
79
|
+
// Go up one directory from 'package/' to the tmp dir
|
|
80
|
+
const tmpDir = join(resolved.path, '..');
|
|
81
|
+
await rm(tmpDir, { recursive: true, force: true }).catch(() => { });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function readPackageJson(dir) {
|
|
85
|
+
try {
|
|
86
|
+
const raw = await readFile(join(dir, 'package.json'), 'utf-8');
|
|
87
|
+
return JSON.parse(raw);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function execPromise(cmd, args, options) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
execFile(cmd, args, { ...options, maxBuffer: 10 * 1024 * 1024, timeout: 60_000 }, (error, stdout, stderr) => {
|
|
96
|
+
if (error) {
|
|
97
|
+
reject(new Error(`${cmd} ${args.join(' ')} failed: ${stderr || error.message}`));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
resolve(stdout);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=package-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-resolver.js","sourceRoot":"","sources":["../src/package-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code pattern scanner — detects dangerous patterns via regex-based static analysis.
|
|
3
|
+
*
|
|
4
|
+
* Categories:
|
|
5
|
+
* - dangerous-pattern: eval(), new Function(), dynamic require
|
|
6
|
+
* - obfuscation: hex-encoded strings, base64 payloads, packed code
|
|
7
|
+
* - exfiltration: HTTP calls to external domains, DNS lookups, socket connections
|
|
8
|
+
*/
|
|
9
|
+
import type { Finding } from './scanner.js';
|
|
10
|
+
export type PatternCategory = 'dangerous-pattern' | 'obfuscation' | 'exfiltration';
|
|
11
|
+
export interface CodePattern {
|
|
12
|
+
name: string;
|
|
13
|
+
category: PatternCategory;
|
|
14
|
+
severity: Finding['severity'];
|
|
15
|
+
pattern: RegExp;
|
|
16
|
+
description: string;
|
|
17
|
+
}
|
|
18
|
+
export interface PatternScanResult {
|
|
19
|
+
totalFiles: number;
|
|
20
|
+
totalLines: number;
|
|
21
|
+
matchedPatterns: Array<{
|
|
22
|
+
pattern: string;
|
|
23
|
+
file: string;
|
|
24
|
+
line: number;
|
|
25
|
+
evidence: string;
|
|
26
|
+
}>;
|
|
27
|
+
findings: Finding[];
|
|
28
|
+
}
|
|
29
|
+
export declare function scanCodePatterns(packagePath: string, extensions: string[]): Promise<PatternScanResult>;
|
|
30
|
+
//# sourceMappingURL=pattern-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-scanner.d.ts","sourceRoot":"","sources":["../src/pattern-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG,mBAAmB,GAAG,aAAa,GAAG,cAAc,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,KAAK,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAkGD,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAsD5B"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code pattern scanner — detects dangerous patterns via regex-based static analysis.
|
|
3
|
+
*
|
|
4
|
+
* Categories:
|
|
5
|
+
* - dangerous-pattern: eval(), new Function(), dynamic require
|
|
6
|
+
* - obfuscation: hex-encoded strings, base64 payloads, packed code
|
|
7
|
+
* - exfiltration: HTTP calls to external domains, DNS lookups, socket connections
|
|
8
|
+
*/
|
|
9
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
10
|
+
import { join, relative } from 'node:path';
|
|
11
|
+
// ─── Pattern Definitions ─────────────────────────────────────────────
|
|
12
|
+
const PATTERNS = [
|
|
13
|
+
// Dangerous execution patterns
|
|
14
|
+
{
|
|
15
|
+
name: 'eval-usage',
|
|
16
|
+
category: 'dangerous-pattern',
|
|
17
|
+
severity: 'critical',
|
|
18
|
+
pattern: /\beval\s*\(/g,
|
|
19
|
+
description: 'eval() can execute arbitrary code — a major security risk',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'new-function',
|
|
23
|
+
category: 'dangerous-pattern',
|
|
24
|
+
severity: 'critical',
|
|
25
|
+
pattern: /new\s+Function\s*\(/g,
|
|
26
|
+
description: 'new Function() is equivalent to eval()',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'dynamic-import-variable',
|
|
30
|
+
category: 'dangerous-pattern',
|
|
31
|
+
severity: 'high',
|
|
32
|
+
pattern: /import\s*\(\s*[^'"]/g,
|
|
33
|
+
description: 'Dynamic import with variable — could load arbitrary modules',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'child-process-exec',
|
|
37
|
+
category: 'dangerous-pattern',
|
|
38
|
+
severity: 'high',
|
|
39
|
+
pattern: /(?:exec|execSync|spawn|spawnSync|fork)\s*\(/g,
|
|
40
|
+
description: 'Shell command execution detected',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'process-env-access',
|
|
44
|
+
category: 'dangerous-pattern',
|
|
45
|
+
severity: 'medium',
|
|
46
|
+
pattern: /process\.env\[/g,
|
|
47
|
+
description: 'Dynamic environment variable access',
|
|
48
|
+
},
|
|
49
|
+
// Obfuscation patterns
|
|
50
|
+
{
|
|
51
|
+
name: 'hex-string-long',
|
|
52
|
+
category: 'obfuscation',
|
|
53
|
+
severity: 'high',
|
|
54
|
+
pattern: /["']\\x[0-9a-fA-F]{2}(?:\\x[0-9a-fA-F]{2}){7,}["']/g,
|
|
55
|
+
description: 'Long hex-encoded string — possible obfuscated payload',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'base64-long-literal',
|
|
59
|
+
category: 'obfuscation',
|
|
60
|
+
severity: 'medium',
|
|
61
|
+
pattern: /atob\s*\(\s*["'][A-Za-z0-9+/=]{50,}["']\s*\)/g,
|
|
62
|
+
description: 'Base64 decode of a long literal — possible hidden payload',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'char-code-array',
|
|
66
|
+
category: 'obfuscation',
|
|
67
|
+
severity: 'medium',
|
|
68
|
+
pattern: /String\.fromCharCode\s*\(\s*(?:\d+\s*,\s*){5,}/g,
|
|
69
|
+
description: 'String.fromCharCode with many values — possible obfuscation',
|
|
70
|
+
},
|
|
71
|
+
// Exfiltration patterns
|
|
72
|
+
{
|
|
73
|
+
name: 'fetch-external',
|
|
74
|
+
category: 'exfiltration',
|
|
75
|
+
severity: 'high',
|
|
76
|
+
pattern: /(?:fetch|axios|got|node-fetch|request)\s*\(\s*[`"']https?:\/\//g,
|
|
77
|
+
description: 'HTTP request to external URL',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'dns-lookup',
|
|
81
|
+
category: 'exfiltration',
|
|
82
|
+
severity: 'medium',
|
|
83
|
+
pattern: /dns\.(?:lookup|resolve|resolve4|resolve6)\s*\(/g,
|
|
84
|
+
description: 'DNS lookup — could be used for DNS exfiltration',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'websocket-connection',
|
|
88
|
+
category: 'exfiltration',
|
|
89
|
+
severity: 'medium',
|
|
90
|
+
pattern: /new\s+WebSocket\s*\(/g,
|
|
91
|
+
description: 'WebSocket connection — could be used for data exfiltration',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'net-socket',
|
|
95
|
+
category: 'exfiltration',
|
|
96
|
+
severity: 'high',
|
|
97
|
+
pattern: /(?:net|tls)\.(?:createConnection|connect|createServer)\s*\(/g,
|
|
98
|
+
description: 'Low-level network socket — could send data anywhere',
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
// ─── Scanner ─────────────────────────────────────────────────────────
|
|
102
|
+
export async function scanCodePatterns(packagePath, extensions) {
|
|
103
|
+
const files = await collectSourceFiles(packagePath, extensions);
|
|
104
|
+
const findings = [];
|
|
105
|
+
const matchedPatterns = [];
|
|
106
|
+
let totalLines = 0;
|
|
107
|
+
for (const filePath of files) {
|
|
108
|
+
const content = await readFile(filePath, 'utf-8');
|
|
109
|
+
const lines = content.split('\n');
|
|
110
|
+
totalLines += lines.length;
|
|
111
|
+
const relPath = relative(packagePath, filePath);
|
|
112
|
+
for (const pattern of PATTERNS) {
|
|
113
|
+
// Reset regex state
|
|
114
|
+
pattern.pattern.lastIndex = 0;
|
|
115
|
+
for (let i = 0; i < lines.length; i++) {
|
|
116
|
+
const line = lines[i];
|
|
117
|
+
// Reset for each line
|
|
118
|
+
pattern.pattern.lastIndex = 0;
|
|
119
|
+
if (pattern.pattern.test(line)) {
|
|
120
|
+
const trimmed = line.trim();
|
|
121
|
+
// Skip matches in comments
|
|
122
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
matchedPatterns.push({
|
|
126
|
+
pattern: pattern.name,
|
|
127
|
+
file: relPath,
|
|
128
|
+
line: i + 1,
|
|
129
|
+
evidence: trimmed.slice(0, 120),
|
|
130
|
+
});
|
|
131
|
+
findings.push({
|
|
132
|
+
severity: pattern.severity,
|
|
133
|
+
category: pattern.category,
|
|
134
|
+
title: `${pattern.name} in ${relPath}:${i + 1}`,
|
|
135
|
+
description: pattern.description,
|
|
136
|
+
file: relPath,
|
|
137
|
+
line: i + 1,
|
|
138
|
+
evidence: trimmed.slice(0, 120),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
totalFiles: files.length,
|
|
146
|
+
totalLines,
|
|
147
|
+
matchedPatterns,
|
|
148
|
+
findings,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// ─── File Collection ─────────────────────────────────────────────────
|
|
152
|
+
async function collectSourceFiles(dir, extensions, basePath) {
|
|
153
|
+
basePath ??= dir;
|
|
154
|
+
const files = [];
|
|
155
|
+
let entries;
|
|
156
|
+
try {
|
|
157
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return files;
|
|
161
|
+
}
|
|
162
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
163
|
+
const fullPath = join(dir, entry.name);
|
|
164
|
+
const relPath = relative(basePath, fullPath);
|
|
165
|
+
// Skip common non-source directories
|
|
166
|
+
if (entry.isDirectory()) {
|
|
167
|
+
if (['node_modules', 'dist', '.git', '.turbo', 'coverage', '__pycache__'].includes(entry.name)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
files.push(...await collectSourceFiles(fullPath, extensions, basePath));
|
|
171
|
+
}
|
|
172
|
+
else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
173
|
+
files.push(fullPath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return files;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=pattern-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern-scanner.js","sourceRoot":"","sources":["../src/pattern-scanner.ts"],"names":[],"mappings":"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"}
|