@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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission scanner — detects what system resources an MCP server accesses.
|
|
3
|
+
*
|
|
4
|
+
* Analyzes imports and API usage to determine permission scope:
|
|
5
|
+
* - filesystem: fs, path operations
|
|
6
|
+
* - network: http, https, net, tls, dns, fetch
|
|
7
|
+
* - process: child_process, exec, spawn
|
|
8
|
+
* - crypto: crypto operations (generally safe, but noted)
|
|
9
|
+
* - environment: process.env access
|
|
10
|
+
*/
|
|
11
|
+
import type { Finding } from './scanner.js';
|
|
12
|
+
export type PermissionKind = 'filesystem' | 'network' | 'process' | 'crypto' | 'environment' | 'native';
|
|
13
|
+
export interface DetectedPermission {
|
|
14
|
+
kind: PermissionKind;
|
|
15
|
+
source: string;
|
|
16
|
+
file: string;
|
|
17
|
+
line: number;
|
|
18
|
+
evidence: string;
|
|
19
|
+
}
|
|
20
|
+
export interface PermissionScanResult {
|
|
21
|
+
/** Unique permission kinds detected */
|
|
22
|
+
kinds: PermissionKind[];
|
|
23
|
+
/** All detected permission usages */
|
|
24
|
+
detections: DetectedPermission[];
|
|
25
|
+
findings: Finding[];
|
|
26
|
+
}
|
|
27
|
+
export declare function scanPermissions(packagePath: string, extensions: string[]): Promise<PermissionScanResult>;
|
|
28
|
+
//# sourceMappingURL=permission-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-scanner.d.ts","sourceRoot":"","sources":["../src/permission-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,MAAM,MAAM,cAAc,GACtB,YAAY,GACZ,SAAS,GACT,SAAS,GACT,QAAQ,GACR,aAAa,GACb,QAAQ,CAAC;AAEb,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,qCAAqC;IACrC,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAwCD,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,oBAAoB,CAAC,CA6C/B"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission scanner — detects what system resources an MCP server accesses.
|
|
3
|
+
*
|
|
4
|
+
* Analyzes imports and API usage to determine permission scope:
|
|
5
|
+
* - filesystem: fs, path operations
|
|
6
|
+
* - network: http, https, net, tls, dns, fetch
|
|
7
|
+
* - process: child_process, exec, spawn
|
|
8
|
+
* - crypto: crypto operations (generally safe, but noted)
|
|
9
|
+
* - environment: process.env access
|
|
10
|
+
*/
|
|
11
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
12
|
+
import { join, relative } from 'node:path';
|
|
13
|
+
const PERMISSION_RULES = [
|
|
14
|
+
// Filesystem
|
|
15
|
+
{ kind: 'filesystem', pattern: /(?:from|require\s*\()\s*['"](?:node:)?fs(?:\/promises)?['"]/g, source: 'fs module', severity: 'medium' },
|
|
16
|
+
{ kind: 'filesystem', pattern: /(?:readFile|writeFile|readdir|mkdir|rmdir|unlink|rename|copyFile|stat|access)\s*\(/g, source: 'fs operation', severity: 'medium' },
|
|
17
|
+
{ kind: 'filesystem', pattern: /(?:createReadStream|createWriteStream)\s*\(/g, source: 'fs stream', severity: 'medium' },
|
|
18
|
+
// Network
|
|
19
|
+
{ kind: 'network', pattern: /(?:from|require\s*\()\s*['"](?:node:)?(?:http|https|net|tls|dgram)['"]/g, source: 'network module', severity: 'high' },
|
|
20
|
+
{ kind: 'network', pattern: /(?:from|require\s*\()\s*['"](?:node-fetch|axios|got|undici)['"]/g, source: 'http client library', severity: 'high' },
|
|
21
|
+
{ kind: 'network', pattern: /\bfetch\s*\(/g, source: 'global fetch', severity: 'medium' },
|
|
22
|
+
// Process/Shell
|
|
23
|
+
{ kind: 'process', pattern: /(?:from|require\s*\()\s*['"](?:node:)?child_process['"]/g, source: 'child_process module', severity: 'critical' },
|
|
24
|
+
{ kind: 'process', pattern: /(?:exec|execFile|execSync|spawn|spawnSync|fork)\s*\(/g, source: 'process execution', severity: 'critical' },
|
|
25
|
+
{ kind: 'process', pattern: /process\.(?:kill|exit|abort)\s*\(/g, source: 'process control', severity: 'high' },
|
|
26
|
+
// Crypto (generally safe but noted)
|
|
27
|
+
{ kind: 'crypto', pattern: /(?:from|require\s*\()\s*['"](?:node:)?crypto['"]/g, source: 'crypto module', severity: 'info' },
|
|
28
|
+
// Environment
|
|
29
|
+
{ kind: 'environment', pattern: /process\.env(?:\.|(?:\[))/g, source: 'environment variable', severity: 'low' },
|
|
30
|
+
// Native modules
|
|
31
|
+
{ kind: 'native', pattern: /(?:from|require\s*\()\s*['"].*\.node['"]/g, source: 'native addon', severity: 'high' },
|
|
32
|
+
{ kind: 'native', pattern: /(?:from|require\s*\()\s*['"](?:node:)?(?:v8|vm|worker_threads)['"]/g, source: 'low-level module', severity: 'high' },
|
|
33
|
+
];
|
|
34
|
+
// ─── Scanner ─────────────────────────────────────────────────────────
|
|
35
|
+
export async function scanPermissions(packagePath, extensions) {
|
|
36
|
+
const files = await collectSourceFiles(packagePath, extensions);
|
|
37
|
+
const detections = [];
|
|
38
|
+
const findings = [];
|
|
39
|
+
for (const filePath of files) {
|
|
40
|
+
const content = await readFile(filePath, 'utf-8');
|
|
41
|
+
const lines = content.split('\n');
|
|
42
|
+
const relPath = relative(packagePath, filePath);
|
|
43
|
+
for (const rule of PERMISSION_RULES) {
|
|
44
|
+
for (let i = 0; i < lines.length; i++) {
|
|
45
|
+
const line = lines[i];
|
|
46
|
+
rule.pattern.lastIndex = 0;
|
|
47
|
+
if (rule.pattern.test(line)) {
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
detections.push({
|
|
53
|
+
kind: rule.kind,
|
|
54
|
+
source: rule.source,
|
|
55
|
+
file: relPath,
|
|
56
|
+
line: i + 1,
|
|
57
|
+
evidence: trimmed.slice(0, 120),
|
|
58
|
+
});
|
|
59
|
+
findings.push({
|
|
60
|
+
severity: rule.severity,
|
|
61
|
+
category: 'permission',
|
|
62
|
+
title: `${rule.kind}: ${rule.source} in ${relPath}:${i + 1}`,
|
|
63
|
+
description: `Detected ${rule.kind} access via ${rule.source}`,
|
|
64
|
+
file: relPath,
|
|
65
|
+
line: i + 1,
|
|
66
|
+
evidence: trimmed.slice(0, 120),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const kinds = [...new Set(detections.map(d => d.kind))];
|
|
73
|
+
return { kinds, detections, findings };
|
|
74
|
+
}
|
|
75
|
+
// ─── File Collection ─────────────────────────────────────────────────
|
|
76
|
+
async function collectSourceFiles(dir, extensions, basePath) {
|
|
77
|
+
basePath ??= dir;
|
|
78
|
+
const files = [];
|
|
79
|
+
let entries;
|
|
80
|
+
try {
|
|
81
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return files;
|
|
85
|
+
}
|
|
86
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
87
|
+
const fullPath = join(dir, entry.name);
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
if (['node_modules', 'dist', '.git', '.turbo', 'coverage', '__pycache__'].includes(entry.name)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
files.push(...await collectSourceFiles(fullPath, extensions, basePath));
|
|
93
|
+
}
|
|
94
|
+
else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
95
|
+
files.push(fullPath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return files;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=permission-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-scanner.js","sourceRoot":"","sources":["../src/permission-scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publisher verifier — checks npm registry for package publisher identity signals.
|
|
3
|
+
*
|
|
4
|
+
* Checks:
|
|
5
|
+
* 1. Package exists on npm registry
|
|
6
|
+
* 2. Publisher/maintainer count and identities
|
|
7
|
+
* 3. Whether the publisher has 2FA enabled (npm provenance)
|
|
8
|
+
* 4. Package age (how long has it existed?)
|
|
9
|
+
* 5. Download count (popularity signal)
|
|
10
|
+
* 6. Repository link presence and match
|
|
11
|
+
* 7. npm provenance attestation (sigstore)
|
|
12
|
+
*/
|
|
13
|
+
import type { Finding } from './scanner.js';
|
|
14
|
+
export interface PublisherInfo {
|
|
15
|
+
/** npm package name */
|
|
16
|
+
packageName: string;
|
|
17
|
+
/** Whether the package exists on npm */
|
|
18
|
+
existsOnNpm: boolean;
|
|
19
|
+
/** npm publisher username */
|
|
20
|
+
publisher?: string;
|
|
21
|
+
/** npm publisher email */
|
|
22
|
+
publisherEmail?: string;
|
|
23
|
+
/** Number of maintainers */
|
|
24
|
+
maintainerCount: number;
|
|
25
|
+
/** Maintainer usernames */
|
|
26
|
+
maintainers: string[];
|
|
27
|
+
/** Package creation date */
|
|
28
|
+
createdAt?: string;
|
|
29
|
+
/** Last publish date */
|
|
30
|
+
lastPublishedAt?: string;
|
|
31
|
+
/** Package age in days */
|
|
32
|
+
ageDays: number;
|
|
33
|
+
/** Weekly downloads */
|
|
34
|
+
weeklyDownloads: number;
|
|
35
|
+
/** Whether the package has a repository link */
|
|
36
|
+
hasRepository: boolean;
|
|
37
|
+
/** Repository URL */
|
|
38
|
+
repositoryUrl?: string;
|
|
39
|
+
/** Whether npm provenance attestation is present */
|
|
40
|
+
hasProvenance: boolean;
|
|
41
|
+
/** License */
|
|
42
|
+
license?: string;
|
|
43
|
+
/** Number of published versions */
|
|
44
|
+
versionCount: number;
|
|
45
|
+
}
|
|
46
|
+
export interface PublisherScanResult {
|
|
47
|
+
info: PublisherInfo;
|
|
48
|
+
findings: Finding[];
|
|
49
|
+
/** Publisher trust score 0-100 */
|
|
50
|
+
score: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Verify publisher identity by checking the npm registry.
|
|
54
|
+
*/
|
|
55
|
+
export declare function scanPublisher(packageName: string): Promise<PublisherScanResult>;
|
|
56
|
+
//# sourceMappingURL=publisher-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisher-scanner.d.ts","sourceRoot":"","sources":["../src/publisher-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI5C,MAAM,WAAW,aAAa;IAC5B,uBAAuB;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,WAAW,EAAE,OAAO,CAAC;IACrB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4BAA4B;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,2BAA2B;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,aAAa,EAAE,OAAO,CAAC;IACvB,qBAAqB;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;CACf;AAiCD;;GAEG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiJrF"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publisher verifier — checks npm registry for package publisher identity signals.
|
|
3
|
+
*
|
|
4
|
+
* Checks:
|
|
5
|
+
* 1. Package exists on npm registry
|
|
6
|
+
* 2. Publisher/maintainer count and identities
|
|
7
|
+
* 3. Whether the publisher has 2FA enabled (npm provenance)
|
|
8
|
+
* 4. Package age (how long has it existed?)
|
|
9
|
+
* 5. Download count (popularity signal)
|
|
10
|
+
* 6. Repository link presence and match
|
|
11
|
+
* 7. npm provenance attestation (sigstore)
|
|
12
|
+
*/
|
|
13
|
+
// ─── Registry Fetcher ─────────────────────────────────────────────────
|
|
14
|
+
async function fetchRegistryData(packageName) {
|
|
15
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
headers: { 'Accept': 'application/json' },
|
|
18
|
+
signal: AbortSignal.timeout(10_000),
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
if (response.status === 404)
|
|
22
|
+
return null;
|
|
23
|
+
throw new Error(`npm registry returned ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
return response.json();
|
|
26
|
+
}
|
|
27
|
+
async function fetchDownloadCount(packageName) {
|
|
28
|
+
try {
|
|
29
|
+
const url = `https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`;
|
|
30
|
+
const response = await fetch(url, {
|
|
31
|
+
signal: AbortSignal.timeout(5_000),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok)
|
|
34
|
+
return 0;
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
return data.downloads ?? 0;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// ─── Scanner ─────────────────────────────────────────────────────────
|
|
43
|
+
/**
|
|
44
|
+
* Verify publisher identity by checking the npm registry.
|
|
45
|
+
*/
|
|
46
|
+
export async function scanPublisher(packageName) {
|
|
47
|
+
const findings = [];
|
|
48
|
+
// Skip for local-only / unknown packages
|
|
49
|
+
if (!packageName || packageName === 'unknown' || packageName.startsWith('/')) {
|
|
50
|
+
return {
|
|
51
|
+
info: emptyInfo(packageName),
|
|
52
|
+
findings,
|
|
53
|
+
score: 50, // neutral for local packages
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const [registryData, weeklyDownloads] = await Promise.all([
|
|
58
|
+
fetchRegistryData(packageName),
|
|
59
|
+
fetchDownloadCount(packageName),
|
|
60
|
+
]);
|
|
61
|
+
if (!registryData) {
|
|
62
|
+
findings.push({
|
|
63
|
+
severity: 'medium',
|
|
64
|
+
category: 'publisher',
|
|
65
|
+
title: 'Package not found on npm',
|
|
66
|
+
description: `"${packageName}" does not exist on the npm registry`,
|
|
67
|
+
});
|
|
68
|
+
return { info: { ...emptyInfo(packageName), existsOnNpm: false }, findings, score: 20 };
|
|
69
|
+
}
|
|
70
|
+
// Extract publisher info
|
|
71
|
+
const timeData = registryData.time ?? {};
|
|
72
|
+
const createdAt = timeData.created;
|
|
73
|
+
const lastPublishedAt = timeData.modified;
|
|
74
|
+
const maintainers = registryData.maintainers ?? [];
|
|
75
|
+
const latestVersion = registryData['dist-tags']?.latest;
|
|
76
|
+
const latestData = latestVersion ? registryData.versions?.[latestVersion] : undefined;
|
|
77
|
+
const ageDays = createdAt
|
|
78
|
+
? Math.floor((Date.now() - new Date(createdAt).getTime()) / 86_400_000)
|
|
79
|
+
: 0;
|
|
80
|
+
const repositoryUrl = latestData?.repository?.url ?? registryData.repository?.url;
|
|
81
|
+
const hasRepository = !!repositoryUrl;
|
|
82
|
+
const license = latestData?.license ?? registryData.license;
|
|
83
|
+
const hasProvenance = !!latestData?.dist?.attestations || !!latestData?.dist?.signatures?.length;
|
|
84
|
+
const versionCount = Object.keys(registryData.versions ?? {}).length;
|
|
85
|
+
// Determine publisher from _npmUser on latest version
|
|
86
|
+
const npmUser = latestData?._npmUser;
|
|
87
|
+
const info = {
|
|
88
|
+
packageName,
|
|
89
|
+
existsOnNpm: true,
|
|
90
|
+
publisher: npmUser?.name,
|
|
91
|
+
publisherEmail: npmUser?.email,
|
|
92
|
+
maintainerCount: maintainers.length,
|
|
93
|
+
maintainers: maintainers.map(m => m.name),
|
|
94
|
+
createdAt,
|
|
95
|
+
lastPublishedAt,
|
|
96
|
+
ageDays,
|
|
97
|
+
weeklyDownloads,
|
|
98
|
+
hasRepository,
|
|
99
|
+
repositoryUrl,
|
|
100
|
+
hasProvenance,
|
|
101
|
+
license,
|
|
102
|
+
versionCount,
|
|
103
|
+
};
|
|
104
|
+
// Generate findings
|
|
105
|
+
if (ageDays < 30) {
|
|
106
|
+
findings.push({
|
|
107
|
+
severity: 'medium',
|
|
108
|
+
category: 'publisher',
|
|
109
|
+
title: `New package: ${ageDays} days old`,
|
|
110
|
+
description: 'Package was published less than 30 days ago — limited track record',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (weeklyDownloads < 100) {
|
|
114
|
+
findings.push({
|
|
115
|
+
severity: 'low',
|
|
116
|
+
category: 'publisher',
|
|
117
|
+
title: `Low popularity: ${weeklyDownloads} downloads/week`,
|
|
118
|
+
description: 'Low download count suggests limited community vetting',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (maintainers.length === 1) {
|
|
122
|
+
findings.push({
|
|
123
|
+
severity: 'low',
|
|
124
|
+
category: 'publisher',
|
|
125
|
+
title: 'Single maintainer',
|
|
126
|
+
description: 'Package has only one maintainer — single point of failure for supply chain',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (!hasRepository) {
|
|
130
|
+
findings.push({
|
|
131
|
+
severity: 'medium',
|
|
132
|
+
category: 'publisher',
|
|
133
|
+
title: 'No repository link',
|
|
134
|
+
description: 'Package does not link to a source code repository',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (!license) {
|
|
138
|
+
findings.push({
|
|
139
|
+
severity: 'medium',
|
|
140
|
+
category: 'publisher',
|
|
141
|
+
title: 'No license specified',
|
|
142
|
+
description: 'Package does not specify a license',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (!hasProvenance) {
|
|
146
|
+
findings.push({
|
|
147
|
+
severity: 'info',
|
|
148
|
+
category: 'publisher',
|
|
149
|
+
title: 'No npm provenance',
|
|
150
|
+
description: 'Package does not have npm provenance attestation (sigstore)',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (maintainers.length > 10) {
|
|
154
|
+
findings.push({
|
|
155
|
+
severity: 'low',
|
|
156
|
+
category: 'publisher',
|
|
157
|
+
title: `Many maintainers: ${maintainers.length}`,
|
|
158
|
+
description: 'Large number of maintainers increases attack surface',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
// Compute publisher score
|
|
162
|
+
const score = computePublisherScore(info);
|
|
163
|
+
return { info, findings, score };
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
// Network failure — return neutral score
|
|
167
|
+
findings.push({
|
|
168
|
+
severity: 'info',
|
|
169
|
+
category: 'publisher',
|
|
170
|
+
title: 'Could not check npm registry',
|
|
171
|
+
description: err.message,
|
|
172
|
+
});
|
|
173
|
+
return { info: emptyInfo(packageName), findings, score: 50 };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// ─── Scoring ──────────────────────────────────────────────────────────
|
|
177
|
+
function computePublisherScore(info) {
|
|
178
|
+
let score = 0;
|
|
179
|
+
// Exists on npm: +20
|
|
180
|
+
if (info.existsOnNpm)
|
|
181
|
+
score += 20;
|
|
182
|
+
// Age bonus: up to +20
|
|
183
|
+
if (info.ageDays > 365)
|
|
184
|
+
score += 20;
|
|
185
|
+
else if (info.ageDays > 180)
|
|
186
|
+
score += 15;
|
|
187
|
+
else if (info.ageDays > 30)
|
|
188
|
+
score += 10;
|
|
189
|
+
else
|
|
190
|
+
score += 2;
|
|
191
|
+
// Downloads: up to +20
|
|
192
|
+
if (info.weeklyDownloads > 10_000)
|
|
193
|
+
score += 20;
|
|
194
|
+
else if (info.weeklyDownloads > 1_000)
|
|
195
|
+
score += 15;
|
|
196
|
+
else if (info.weeklyDownloads > 100)
|
|
197
|
+
score += 10;
|
|
198
|
+
else
|
|
199
|
+
score += 2;
|
|
200
|
+
// Repository: +10
|
|
201
|
+
if (info.hasRepository)
|
|
202
|
+
score += 10;
|
|
203
|
+
// License: +5
|
|
204
|
+
if (info.license)
|
|
205
|
+
score += 5;
|
|
206
|
+
// Provenance: +10
|
|
207
|
+
if (info.hasProvenance)
|
|
208
|
+
score += 10;
|
|
209
|
+
// Multiple maintainers but not too many: +5
|
|
210
|
+
if (info.maintainerCount >= 2 && info.maintainerCount <= 10)
|
|
211
|
+
score += 5;
|
|
212
|
+
else if (info.maintainerCount === 1)
|
|
213
|
+
score += 2;
|
|
214
|
+
// Multiple versions (established): +10
|
|
215
|
+
if (info.versionCount > 10)
|
|
216
|
+
score += 10;
|
|
217
|
+
else if (info.versionCount > 3)
|
|
218
|
+
score += 7;
|
|
219
|
+
else if (info.versionCount > 1)
|
|
220
|
+
score += 4;
|
|
221
|
+
else
|
|
222
|
+
score += 1;
|
|
223
|
+
return Math.min(100, score);
|
|
224
|
+
}
|
|
225
|
+
function emptyInfo(packageName) {
|
|
226
|
+
return {
|
|
227
|
+
packageName,
|
|
228
|
+
existsOnNpm: false,
|
|
229
|
+
maintainerCount: 0,
|
|
230
|
+
maintainers: [],
|
|
231
|
+
ageDays: 0,
|
|
232
|
+
weeklyDownloads: 0,
|
|
233
|
+
hasRepository: false,
|
|
234
|
+
hasProvenance: false,
|
|
235
|
+
versionCount: 0,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=publisher-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisher-scanner.js","sourceRoot":"","sources":["../src/publisher-scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core scanner — orchestrates all sub-scanners and produces a unified ScanReport.
|
|
3
|
+
*/
|
|
4
|
+
import { type DependencyScanResult } from './dependency-scanner.js';
|
|
5
|
+
import { type PatternScanResult } from './pattern-scanner.js';
|
|
6
|
+
import { type PermissionScanResult } from './permission-scanner.js';
|
|
7
|
+
import { type PublisherScanResult } from './publisher-scanner.js';
|
|
8
|
+
import { type ScoreBreakdown } from './trust-score.js';
|
|
9
|
+
export type FindingSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
10
|
+
export type FindingCategory = 'vulnerability' | 'dangerous-pattern' | 'obfuscation' | 'permission' | 'exfiltration' | 'publisher';
|
|
11
|
+
export interface Finding {
|
|
12
|
+
severity: FindingSeverity;
|
|
13
|
+
category: FindingCategory;
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
file?: string;
|
|
17
|
+
line?: number;
|
|
18
|
+
evidence?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TrustScore {
|
|
21
|
+
/** Overall score 0-100 (100 = fully trusted) */
|
|
22
|
+
overall: number;
|
|
23
|
+
/** Per-category breakdown */
|
|
24
|
+
breakdown: ScoreBreakdown;
|
|
25
|
+
/** Human-readable grade: A, B, C, D, F */
|
|
26
|
+
grade: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ScanReport {
|
|
29
|
+
/** Package name */
|
|
30
|
+
packageName: string;
|
|
31
|
+
/** Package version */
|
|
32
|
+
packageVersion: string;
|
|
33
|
+
/** When scan was performed */
|
|
34
|
+
scannedAt: string;
|
|
35
|
+
/** Scanner version */
|
|
36
|
+
scannerVersion: string;
|
|
37
|
+
/** Trust score */
|
|
38
|
+
trustScore: TrustScore;
|
|
39
|
+
/** All findings */
|
|
40
|
+
findings: Finding[];
|
|
41
|
+
/** Dependency scan results */
|
|
42
|
+
dependencies: DependencyScanResult;
|
|
43
|
+
/** Code pattern scan results */
|
|
44
|
+
patterns: PatternScanResult;
|
|
45
|
+
/** Permission scan results */
|
|
46
|
+
permissions: PermissionScanResult;
|
|
47
|
+
/** Publisher identity results */
|
|
48
|
+
publisher?: PublisherScanResult;
|
|
49
|
+
/** Scan duration in ms */
|
|
50
|
+
durationMs: number;
|
|
51
|
+
}
|
|
52
|
+
export interface ScanOptions {
|
|
53
|
+
/** Path to the package directory to scan */
|
|
54
|
+
packagePath: string;
|
|
55
|
+
/** Skip dependency scanning (useful when npm not available) */
|
|
56
|
+
skipDependencies?: boolean;
|
|
57
|
+
/** Additional file extensions to scan (default: .ts, .js, .mjs, .cjs) */
|
|
58
|
+
extensions?: string[];
|
|
59
|
+
}
|
|
60
|
+
export declare function scan(options: ScanOptions): Promise<ScanReport>;
|
|
61
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAiB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAM1E,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAE9E,MAAM,MAAM,eAAe,GACvB,eAAe,GACf,mBAAmB,GACnB,aAAa,GACb,YAAY,GACZ,cAAc,GACd,WAAW,CAAC;AAEhB,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,SAAS,EAAE,cAAc,CAAC;IAC1B,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB;IAClB,UAAU,EAAE,UAAU,CAAC;IACvB,mBAAmB;IACnB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,8BAA8B;IAC9B,YAAY,EAAE,oBAAoB,CAAC;IACnC,gCAAgC;IAChC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,8BAA8B;IAC9B,WAAW,EAAE,oBAAoB,CAAC;IAClC,iCAAiC;IACjC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAMD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAkEpE"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core scanner — orchestrates all sub-scanners and produces a unified ScanReport.
|
|
3
|
+
*/
|
|
4
|
+
import { scanDependencies } from './dependency-scanner.js';
|
|
5
|
+
import { scanCodePatterns } from './pattern-scanner.js';
|
|
6
|
+
import { scanPermissions } from './permission-scanner.js';
|
|
7
|
+
import { scanPublisher } from './publisher-scanner.js';
|
|
8
|
+
import { computeTrustScore } from './trust-score.js';
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
// ─── Main Scan Function ──────────────────────────────────────────────
|
|
12
|
+
const SCANNER_VERSION = '0.3.0';
|
|
13
|
+
export async function scan(options) {
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
const { packagePath, skipDependencies = false } = options;
|
|
16
|
+
const extensions = options.extensions ?? ['.ts', '.js', '.mjs', '.cjs'];
|
|
17
|
+
// Read package.json
|
|
18
|
+
const pkgJsonPath = join(packagePath, 'package.json');
|
|
19
|
+
let packageName = 'unknown';
|
|
20
|
+
let packageVersion = '0.0.0';
|
|
21
|
+
try {
|
|
22
|
+
const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
|
|
23
|
+
packageName = pkgJson.name ?? 'unknown';
|
|
24
|
+
packageVersion = pkgJson.version ?? '0.0.0';
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// No package.json — we can still scan the code
|
|
28
|
+
}
|
|
29
|
+
// Run all sub-scanners
|
|
30
|
+
const [dependencies, patterns, permissions, publisher] = await Promise.all([
|
|
31
|
+
skipDependencies
|
|
32
|
+
? { vulnerabilities: [], totalDependencies: 0, findings: [] }
|
|
33
|
+
: scanDependencies(packagePath),
|
|
34
|
+
scanCodePatterns(packagePath, extensions),
|
|
35
|
+
scanPermissions(packagePath, extensions),
|
|
36
|
+
scanPublisher(packageName),
|
|
37
|
+
]);
|
|
38
|
+
// Aggregate findings
|
|
39
|
+
const findings = [
|
|
40
|
+
...dependencies.findings,
|
|
41
|
+
...patterns.findings,
|
|
42
|
+
...permissions.findings,
|
|
43
|
+
...publisher.findings,
|
|
44
|
+
];
|
|
45
|
+
// Compute trust score
|
|
46
|
+
const breakdown = computeTrustScore(findings, dependencies, patterns, permissions, publisher);
|
|
47
|
+
const overall = Math.round(breakdown.dependencies * 0.25 +
|
|
48
|
+
breakdown.codePatterns * 0.30 +
|
|
49
|
+
breakdown.permissions * 0.25 +
|
|
50
|
+
breakdown.publisher * 0.20);
|
|
51
|
+
const grade = overall >= 90 ? 'A'
|
|
52
|
+
: overall >= 75 ? 'B'
|
|
53
|
+
: overall >= 60 ? 'C'
|
|
54
|
+
: overall >= 40 ? 'D'
|
|
55
|
+
: 'F';
|
|
56
|
+
const trustScore = { overall, breakdown, grade };
|
|
57
|
+
return {
|
|
58
|
+
packageName,
|
|
59
|
+
packageVersion,
|
|
60
|
+
scannedAt: new Date().toISOString(),
|
|
61
|
+
scannerVersion: SCANNER_VERSION,
|
|
62
|
+
trustScore,
|
|
63
|
+
findings,
|
|
64
|
+
dependencies,
|
|
65
|
+
patterns,
|
|
66
|
+
permissions,
|
|
67
|
+
publisher,
|
|
68
|
+
durationMs: Date.now() - startTime,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAA6B,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAA0B,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,eAAe,EAA6B,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAE,aAAa,EAA4B,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAuB,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAQ,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmEjC,wEAAwE;AAExE,MAAM,eAAe,GAAG,OAAO,CAAC;AAEhC,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAoB;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,EAAE,WAAW,EAAE,gBAAgB,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAExE,oBAAoB;IACpB,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IACtD,IAAI,WAAW,GAAG,SAAS,CAAC;IAC5B,IAAI,cAAc,GAAG,OAAO,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACjE,WAAW,GAAG,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC;QACxC,cAAc,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;IAED,uBAAuB;IACvB,MAAM,CAAC,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzE,gBAAgB;YACd,CAAC,CAAC,EAAE,eAAe,EAAE,EAAE,EAAE,iBAAiB,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAe,EAAE;YAC1E,CAAC,CAAC,gBAAgB,CAAC,WAAW,CAAC;QACjC,gBAAgB,CAAC,WAAW,EAAE,UAAU,CAAC;QACzC,eAAe,CAAC,WAAW,EAAE,UAAU,CAAC;QACxC,aAAa,CAAC,WAAW,CAAC;KAC3B,CAAC,CAAC;IAEH,qBAAqB;IACrB,MAAM,QAAQ,GAAc;QAC1B,GAAG,YAAY,CAAC,QAAQ;QACxB,GAAG,QAAQ,CAAC,QAAQ;QACpB,GAAG,WAAW,CAAC,QAAQ;QACvB,GAAG,SAAS,CAAC,QAAQ;KACtB,CAAC;IAEF,sBAAsB;IACtB,MAAM,SAAS,GAAG,iBAAiB,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;IAC9F,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CACxB,SAAS,CAAC,YAAY,GAAG,IAAI;QAC7B,SAAS,CAAC,YAAY,GAAG,IAAI;QAC7B,SAAS,CAAC,WAAW,GAAG,IAAI;QAC5B,SAAS,CAAC,SAAS,GAAG,IAAI,CAC3B,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG;QAC/B,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG;YACrB,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG;gBACrB,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG;oBACrB,CAAC,CAAC,GAAG,CAAC;IAER,MAAM,UAAU,GAAe,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAE7D,OAAO;QACL,WAAW;QACX,cAAc;QACd,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,cAAc,EAAE,eAAe;QAC/B,UAAU;QACV,QAAQ;QACR,YAAY;QACZ,QAAQ;QACR,WAAW;QACX,SAAS;QACT,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;KACnC,CAAC;AACJ,CAAC"}
|