secmanifest 1.0.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 +308 -0
- package/package.json +54 -0
- package/src/cli.ts +138 -0
- package/src/commands/audit.ts +249 -0
- package/src/commands/fix.ts +88 -0
- package/src/commands/watch.ts +40 -0
- package/src/core/fixer.ts +89 -0
- package/src/core/html-report.ts +259 -0
- package/src/core/notify.ts +153 -0
- package/src/core/package-manager.ts +85 -0
- package/src/core/project-analyzer.ts +84 -0
- package/src/core/reporter.ts +170 -0
- package/src/i18n/index.ts +256 -0
- package/src/scanners/backdoors.ts +192 -0
- package/src/scanners/binaries.ts +102 -0
- package/src/scanners/bundle-size.ts +114 -0
- package/src/scanners/duplicates.ts +116 -0
- package/src/scanners/integrity.ts +108 -0
- package/src/scanners/licenses.ts +111 -0
- package/src/scanners/lockfile-drift.ts +182 -0
- package/src/scanners/malware.ts +148 -0
- package/src/scanners/metadata.ts +148 -0
- package/src/scanners/node-version.ts +71 -0
- package/src/scanners/obfuscation.ts +151 -0
- package/src/scanners/outdated.ts +76 -0
- package/src/scanners/secrets.ts +224 -0
- package/src/scanners/socket-dev.ts +140 -0
- package/src/scanners/transitive.ts +97 -0
- package/src/scanners/vulnerabilities.ts +63 -0
- package/src/utils/cache.ts +59 -0
- package/src/utils/http.ts +134 -0
- package/src/utils/registry.ts +170 -0
- package/src/utils/types.ts +67 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const OSS_INDEX_URL = "https://ossindex.sonatype.org/api/v3/component-report";
|
|
2
|
+
|
|
3
|
+
interface RequestOptions {
|
|
4
|
+
method?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
body?: unknown;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function httpRequest<T>(
|
|
11
|
+
url: string,
|
|
12
|
+
options: RequestOptions = {},
|
|
13
|
+
): Promise<T> {
|
|
14
|
+
const { method = "GET", headers = {}, body, timeout = 30000 } = options;
|
|
15
|
+
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(url, {
|
|
21
|
+
method,
|
|
22
|
+
headers: {
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
...headers,
|
|
25
|
+
},
|
|
26
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return (await response.json()) as T;
|
|
35
|
+
} finally {
|
|
36
|
+
clearTimeout(timeoutId);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function checkOssIndex(
|
|
41
|
+
packages: Array<{ name: string; version: string }>,
|
|
42
|
+
token?: string,
|
|
43
|
+
): Promise<
|
|
44
|
+
Array<{
|
|
45
|
+
coordinates: string;
|
|
46
|
+
vulnerabilities: Array<{
|
|
47
|
+
id: string;
|
|
48
|
+
displayName: string;
|
|
49
|
+
title: string;
|
|
50
|
+
description: string;
|
|
51
|
+
severity: string;
|
|
52
|
+
cvssScore: number;
|
|
53
|
+
cve: string;
|
|
54
|
+
reference: string;
|
|
55
|
+
versionRanges: string[];
|
|
56
|
+
}>;
|
|
57
|
+
reference: string;
|
|
58
|
+
}>
|
|
59
|
+
> {
|
|
60
|
+
const components = packages.map(
|
|
61
|
+
(pkg) => `pkg:npm/${pkg.name}@${pkg.version}`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const headers: Record<string, string> = {};
|
|
65
|
+
if (token) {
|
|
66
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const results: Array<{
|
|
70
|
+
coordinates: string;
|
|
71
|
+
vulnerabilities: Array<{
|
|
72
|
+
id: string;
|
|
73
|
+
displayName: string;
|
|
74
|
+
title: string;
|
|
75
|
+
description: string;
|
|
76
|
+
severity: string;
|
|
77
|
+
cvssScore: number;
|
|
78
|
+
cve: string;
|
|
79
|
+
reference: string;
|
|
80
|
+
versionRanges: string[];
|
|
81
|
+
}>;
|
|
82
|
+
reference: string;
|
|
83
|
+
}> = [];
|
|
84
|
+
|
|
85
|
+
const BATCH_SIZE = 128;
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < components.length; i += BATCH_SIZE) {
|
|
88
|
+
const batch = components.slice(i, i + BATCH_SIZE);
|
|
89
|
+
|
|
90
|
+
let retries = 3;
|
|
91
|
+
let delay = 1000;
|
|
92
|
+
|
|
93
|
+
while (retries > 0) {
|
|
94
|
+
try {
|
|
95
|
+
const response = await httpRequest<{
|
|
96
|
+
components: Array<{
|
|
97
|
+
coordinates: string;
|
|
98
|
+
vulnerabilities: Array<{
|
|
99
|
+
id: string;
|
|
100
|
+
displayName: string;
|
|
101
|
+
title: string;
|
|
102
|
+
description: string;
|
|
103
|
+
severity: string;
|
|
104
|
+
cvssScore: number;
|
|
105
|
+
cve: string;
|
|
106
|
+
reference: string;
|
|
107
|
+
versionRanges: string[];
|
|
108
|
+
}>;
|
|
109
|
+
reference: string;
|
|
110
|
+
}>;
|
|
111
|
+
}>(OSS_INDEX_URL, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers,
|
|
114
|
+
body: { components: batch.map((c) => ({ coordinates: c })) },
|
|
115
|
+
timeout: 60000,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
results.push(...response.components);
|
|
119
|
+
break;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
retries--;
|
|
122
|
+
if (retries === 0) throw error;
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
124
|
+
delay *= 2;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (i + BATCH_SIZE < components.length) {
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { httpRequest } from "./http.js";
|
|
2
|
+
|
|
3
|
+
interface NpmPackageMetadata {
|
|
4
|
+
name: string;
|
|
5
|
+
"dist-tags": Record<string, string>;
|
|
6
|
+
versions: Record<string, {
|
|
7
|
+
version: string;
|
|
8
|
+
dist: {
|
|
9
|
+
tarball: string;
|
|
10
|
+
integrity: string;
|
|
11
|
+
};
|
|
12
|
+
deprecated?: string;
|
|
13
|
+
time?: string;
|
|
14
|
+
}>;
|
|
15
|
+
time: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SafeVersionResult {
|
|
19
|
+
packageName: string;
|
|
20
|
+
currentVersion: string;
|
|
21
|
+
latestVersion: string;
|
|
22
|
+
safeVersion: string | null;
|
|
23
|
+
reason: string;
|
|
24
|
+
allVersions: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const UNSAFE_KEYWORDS = [
|
|
28
|
+
"deprecated",
|
|
29
|
+
"malicious",
|
|
30
|
+
"security",
|
|
31
|
+
"vulnerability",
|
|
32
|
+
"compromised",
|
|
33
|
+
"backdoor",
|
|
34
|
+
"malware",
|
|
35
|
+
"trojan",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export async function getPackageMetadata(
|
|
39
|
+
packageName: string,
|
|
40
|
+
): Promise<NpmPackageMetadata | null> {
|
|
41
|
+
try {
|
|
42
|
+
const response = await httpRequest<NpmPackageMetadata>(
|
|
43
|
+
`https://registry.npmjs.org/${encodeURIComponent(packageName)}`,
|
|
44
|
+
{
|
|
45
|
+
headers: {
|
|
46
|
+
Accept: "application/vnd.npm.install-v1+json",
|
|
47
|
+
},
|
|
48
|
+
timeout: 10000,
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
return response;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function findSafeVersion(
|
|
58
|
+
packageName: string,
|
|
59
|
+
currentVersion: string,
|
|
60
|
+
): Promise<SafeVersionResult> {
|
|
61
|
+
const metadata = await getPackageMetadata(packageName);
|
|
62
|
+
|
|
63
|
+
if (!metadata) {
|
|
64
|
+
return {
|
|
65
|
+
packageName,
|
|
66
|
+
currentVersion,
|
|
67
|
+
latestVersion: currentVersion,
|
|
68
|
+
safeVersion: null,
|
|
69
|
+
reason: "No se pudo consultar el registry de npm",
|
|
70
|
+
allVersions: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const latestVersion = metadata["dist-tags"]?.["latest"] ?? currentVersion;
|
|
75
|
+
const allVersions = Object.keys(metadata.versions ?? {}).sort(
|
|
76
|
+
(a, b) => {
|
|
77
|
+
const timeA = metadata.time?.[a] ?? "";
|
|
78
|
+
const timeB = metadata.time?.[b] ?? "";
|
|
79
|
+
return timeB.localeCompare(timeA);
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const currentMeta = metadata.versions?.[currentVersion];
|
|
84
|
+
if (currentMeta?.deprecated) {
|
|
85
|
+
const safeVersion = findNonDeprecatedVersion(
|
|
86
|
+
allVersions,
|
|
87
|
+
metadata,
|
|
88
|
+
currentVersion,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
packageName,
|
|
93
|
+
currentVersion,
|
|
94
|
+
latestVersion,
|
|
95
|
+
safeVersion,
|
|
96
|
+
reason: `Version ${currentVersion} esta deprecada: ${currentMeta.deprecated}`,
|
|
97
|
+
allVersions,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const isLatestUnsafe = latestVersion !== currentVersion;
|
|
102
|
+
if (isLatestUnsafe) {
|
|
103
|
+
const latestMeta = metadata.versions?.[latestVersion];
|
|
104
|
+
if (latestMeta?.deprecated) {
|
|
105
|
+
const safeVersion = findNonDeprecatedVersion(
|
|
106
|
+
allVersions,
|
|
107
|
+
metadata,
|
|
108
|
+
currentVersion,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
packageName,
|
|
113
|
+
currentVersion,
|
|
114
|
+
latestVersion,
|
|
115
|
+
safeVersion,
|
|
116
|
+
reason: `Version latest (${latestVersion}) esta deprecada`,
|
|
117
|
+
allVersions,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
packageName,
|
|
124
|
+
currentVersion,
|
|
125
|
+
latestVersion,
|
|
126
|
+
safeVersion: null,
|
|
127
|
+
reason: "Version actual parece segura",
|
|
128
|
+
allVersions,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function findNonDeprecatedVersion(
|
|
133
|
+
versions: string[],
|
|
134
|
+
metadata: NpmPackageMetadata,
|
|
135
|
+
excludeVersion: string,
|
|
136
|
+
): string | null {
|
|
137
|
+
for (const version of versions) {
|
|
138
|
+
if (version === excludeVersion) continue;
|
|
139
|
+
const meta = metadata.versions?.[version];
|
|
140
|
+
if (meta && !meta.deprecated) {
|
|
141
|
+
return version;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function findLatestSafeVersion(
|
|
148
|
+
packageName: string,
|
|
149
|
+
): Promise<string | null> {
|
|
150
|
+
const metadata = await getPackageMetadata(packageName);
|
|
151
|
+
if (!metadata) return null;
|
|
152
|
+
|
|
153
|
+
const latest = metadata["dist-tags"]?.["latest"];
|
|
154
|
+
if (!latest) return null;
|
|
155
|
+
|
|
156
|
+
const meta = metadata.versions?.[latest];
|
|
157
|
+
if (meta && !meta.deprecated) {
|
|
158
|
+
return latest;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const allVersions = Object.keys(metadata.versions ?? {}).sort(
|
|
162
|
+
(a, b) => {
|
|
163
|
+
const timeA = metadata.time?.[a] ?? "";
|
|
164
|
+
const timeB = metadata.time?.[b] ?? "";
|
|
165
|
+
return timeB.localeCompare(timeA);
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return findNonDeprecatedVersion(allVersions, metadata, latest);
|
|
170
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type Severity = "critical" | "high" | "medium" | "low" | "info";
|
|
2
|
+
|
|
3
|
+
export interface Finding {
|
|
4
|
+
id: string;
|
|
5
|
+
severity: Severity;
|
|
6
|
+
category: "vulnerability" | "malware" | "backdoor" | "drift" | "secrets" | "license" | "metadata" | "obfuscation" | "node-version";
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
package?: string;
|
|
10
|
+
version?: string;
|
|
11
|
+
cve?: string;
|
|
12
|
+
recommendation?: string;
|
|
13
|
+
reference?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ScanResult {
|
|
17
|
+
findings: Finding[];
|
|
18
|
+
errors: Error[];
|
|
19
|
+
duration: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PackageManager {
|
|
23
|
+
name: "pnpm" | "bun" | "yarn" | "npm" | null;
|
|
24
|
+
command: string;
|
|
25
|
+
version?: string;
|
|
26
|
+
blocked: boolean;
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ProjectInfo {
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
dependencies: Record<string, string>;
|
|
34
|
+
devDependencies: Record<string, string>;
|
|
35
|
+
scripts: Record<string, string>;
|
|
36
|
+
lockfilePath: string | null;
|
|
37
|
+
lockfileType: "pnpm" | "bun" | "yarn" | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AuditReport {
|
|
41
|
+
projectPath: string;
|
|
42
|
+
projectName: string;
|
|
43
|
+
packageManager: PackageManager;
|
|
44
|
+
totalPackages: number;
|
|
45
|
+
findings: Finding[];
|
|
46
|
+
score: number;
|
|
47
|
+
duration: number;
|
|
48
|
+
timestamp: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface OssIndexResponse {
|
|
52
|
+
coordinates: string;
|
|
53
|
+
vulnerabilities: OssIndexVulnerability[];
|
|
54
|
+
reference: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface OssIndexVulnerability {
|
|
58
|
+
id: string;
|
|
59
|
+
displayName: string;
|
|
60
|
+
title: string;
|
|
61
|
+
description: string;
|
|
62
|
+
severity: string;
|
|
63
|
+
cvssScore: number;
|
|
64
|
+
cve: string;
|
|
65
|
+
reference: string;
|
|
66
|
+
versionRanges: string[];
|
|
67
|
+
}
|