@ceon-oy/monitor-sdk 1.1.0 → 1.1.2
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/dist/index.d.mts +45 -21
- package/dist/index.d.ts +45 -21
- package/dist/index.js +224 -95
- package/dist/index.mjs +224 -95
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -49,6 +49,10 @@ interface MonitorClientConfig {
|
|
|
49
49
|
auditTimeoutMs?: number;
|
|
50
50
|
/** Timeout for npm registry requests in ms (default: 5000, max: 30000) */
|
|
51
51
|
registryTimeoutMs?: number;
|
|
52
|
+
/** Allow HTTP endpoints in production (default: false, requires explicit opt-in) */
|
|
53
|
+
allowInsecureHttp?: boolean;
|
|
54
|
+
/** Custom npm registry URL for fetching latest versions (default: https://registry.npmjs.org) */
|
|
55
|
+
npmRegistryUrl?: string;
|
|
52
56
|
}
|
|
53
57
|
interface TechnologyItem {
|
|
54
58
|
name: string;
|
|
@@ -117,6 +121,13 @@ interface VulnerabilityItem {
|
|
|
117
121
|
isFixable?: boolean;
|
|
118
122
|
isDirect?: boolean;
|
|
119
123
|
}
|
|
124
|
+
interface VulnerabilitySummary {
|
|
125
|
+
critical: number;
|
|
126
|
+
high: number;
|
|
127
|
+
moderate: number;
|
|
128
|
+
low: number;
|
|
129
|
+
info: number;
|
|
130
|
+
}
|
|
120
131
|
interface AuditResult {
|
|
121
132
|
environment: string;
|
|
122
133
|
totalDeps: number;
|
|
@@ -127,13 +138,7 @@ interface AuditSummary {
|
|
|
127
138
|
scanId: string;
|
|
128
139
|
processed: number;
|
|
129
140
|
resolved: number;
|
|
130
|
-
summary:
|
|
131
|
-
critical: number;
|
|
132
|
-
high: number;
|
|
133
|
-
moderate: number;
|
|
134
|
-
low: number;
|
|
135
|
-
info: number;
|
|
136
|
-
};
|
|
141
|
+
summary: VulnerabilitySummary;
|
|
137
142
|
}
|
|
138
143
|
interface MultiAuditSummary {
|
|
139
144
|
results: Array<{
|
|
@@ -141,21 +146,9 @@ interface MultiAuditSummary {
|
|
|
141
146
|
scanId: string;
|
|
142
147
|
processed: number;
|
|
143
148
|
resolved: number;
|
|
144
|
-
summary:
|
|
145
|
-
critical: number;
|
|
146
|
-
high: number;
|
|
147
|
-
moderate: number;
|
|
148
|
-
low: number;
|
|
149
|
-
info: number;
|
|
150
|
-
};
|
|
149
|
+
summary: VulnerabilitySummary;
|
|
151
150
|
}>;
|
|
152
|
-
totalSummary:
|
|
153
|
-
critical: number;
|
|
154
|
-
high: number;
|
|
155
|
-
moderate: number;
|
|
156
|
-
low: number;
|
|
157
|
-
info: number;
|
|
158
|
-
};
|
|
151
|
+
totalSummary: VulnerabilitySummary;
|
|
159
152
|
}
|
|
160
153
|
|
|
161
154
|
declare class MonitorClient {
|
|
@@ -188,6 +181,7 @@ declare class MonitorClient {
|
|
|
188
181
|
private versionCheckEnabled;
|
|
189
182
|
private auditTimeoutMs;
|
|
190
183
|
private registryTimeoutMs;
|
|
184
|
+
private npmRegistryUrl;
|
|
191
185
|
constructor(config: MonitorClientConfig);
|
|
192
186
|
/**
|
|
193
187
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -198,6 +192,18 @@ declare class MonitorClient {
|
|
|
198
192
|
* Removes potential API keys, tokens, and limits response length
|
|
199
193
|
*/
|
|
200
194
|
private sanitizeErrorResponse;
|
|
195
|
+
/**
|
|
196
|
+
* Security: Sanitize file paths in log messages to prevent exposing full filesystem structure
|
|
197
|
+
* Truncates long paths and shows only the last few segments
|
|
198
|
+
*/
|
|
199
|
+
private sanitizePath;
|
|
200
|
+
/**
|
|
201
|
+
* Execute a promise with a timeout. Properly cleans up the timer and supports cancellation.
|
|
202
|
+
* @param promiseFactory Factory function that receives an AbortSignal and returns a promise
|
|
203
|
+
* @param timeoutMs Timeout in milliseconds
|
|
204
|
+
* @param message Error message if timeout occurs
|
|
205
|
+
*/
|
|
206
|
+
private withTimeout;
|
|
201
207
|
captureError(error: Error, context?: ErrorContext): Promise<void>;
|
|
202
208
|
captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>;
|
|
203
209
|
flush(): Promise<void>;
|
|
@@ -239,9 +245,12 @@ declare class MonitorClient {
|
|
|
239
245
|
private startFlushTimer;
|
|
240
246
|
private stopFlushTimer;
|
|
241
247
|
syncDependencies(): Promise<void>;
|
|
248
|
+
private performDependencySync;
|
|
242
249
|
/**
|
|
243
250
|
* Enrich technologies with latest version information from npm registry.
|
|
244
251
|
* Only runs if versionCheckEnabled is true.
|
|
252
|
+
* @param technologies List of technologies to enrich
|
|
253
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
245
254
|
*/
|
|
246
255
|
private enrichWithLatestVersions;
|
|
247
256
|
syncTechnologies(technologies: TechnologyItem[]): Promise<void>;
|
|
@@ -251,10 +260,15 @@ declare class MonitorClient {
|
|
|
251
260
|
/**
|
|
252
261
|
* Fetch the latest version of a package from npm registry.
|
|
253
262
|
* Returns null if the package cannot be found or the request fails.
|
|
263
|
+
* Uses npmRegistryUrl config option if provided.
|
|
264
|
+
* @param packageName The package name to fetch
|
|
265
|
+
* @param signal Optional AbortSignal to cancel the request
|
|
254
266
|
*/
|
|
255
267
|
private fetchLatestVersion;
|
|
256
268
|
/**
|
|
257
269
|
* Fetch latest versions for multiple packages in parallel with concurrency limit.
|
|
270
|
+
* @param packageNames List of package names to fetch versions for
|
|
271
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
258
272
|
*/
|
|
259
273
|
private fetchLatestVersions;
|
|
260
274
|
private sendTechnologies;
|
|
@@ -320,6 +334,15 @@ declare class MonitorClient {
|
|
|
320
334
|
timeWindowMinutes?: number;
|
|
321
335
|
threshold?: number;
|
|
322
336
|
}): Promise<BruteForceDetectionResult>;
|
|
337
|
+
/**
|
|
338
|
+
* Handle exec errors from audit commands. Returns stdout if available, null if timed out, or rethrows.
|
|
339
|
+
*/
|
|
340
|
+
private handleAuditExecError;
|
|
341
|
+
/**
|
|
342
|
+
* Run a command with a reliable timeout using spawn.
|
|
343
|
+
* More reliable than execSync timeout in containerized environments.
|
|
344
|
+
*/
|
|
345
|
+
private runCommandWithTimeout;
|
|
323
346
|
/**
|
|
324
347
|
* Run npm audit and send results to the monitoring server.
|
|
325
348
|
* This scans the project for known vulnerabilities in dependencies.
|
|
@@ -342,6 +365,7 @@ declare class MonitorClient {
|
|
|
342
365
|
* @returns Combined summary of all audit results
|
|
343
366
|
*/
|
|
344
367
|
auditMultiplePaths(): Promise<MultiAuditSummary | null>;
|
|
368
|
+
private performMultiPathAudit;
|
|
345
369
|
/**
|
|
346
370
|
* Parse npm audit JSON output into vulnerability items
|
|
347
371
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -49,6 +49,10 @@ interface MonitorClientConfig {
|
|
|
49
49
|
auditTimeoutMs?: number;
|
|
50
50
|
/** Timeout for npm registry requests in ms (default: 5000, max: 30000) */
|
|
51
51
|
registryTimeoutMs?: number;
|
|
52
|
+
/** Allow HTTP endpoints in production (default: false, requires explicit opt-in) */
|
|
53
|
+
allowInsecureHttp?: boolean;
|
|
54
|
+
/** Custom npm registry URL for fetching latest versions (default: https://registry.npmjs.org) */
|
|
55
|
+
npmRegistryUrl?: string;
|
|
52
56
|
}
|
|
53
57
|
interface TechnologyItem {
|
|
54
58
|
name: string;
|
|
@@ -117,6 +121,13 @@ interface VulnerabilityItem {
|
|
|
117
121
|
isFixable?: boolean;
|
|
118
122
|
isDirect?: boolean;
|
|
119
123
|
}
|
|
124
|
+
interface VulnerabilitySummary {
|
|
125
|
+
critical: number;
|
|
126
|
+
high: number;
|
|
127
|
+
moderate: number;
|
|
128
|
+
low: number;
|
|
129
|
+
info: number;
|
|
130
|
+
}
|
|
120
131
|
interface AuditResult {
|
|
121
132
|
environment: string;
|
|
122
133
|
totalDeps: number;
|
|
@@ -127,13 +138,7 @@ interface AuditSummary {
|
|
|
127
138
|
scanId: string;
|
|
128
139
|
processed: number;
|
|
129
140
|
resolved: number;
|
|
130
|
-
summary:
|
|
131
|
-
critical: number;
|
|
132
|
-
high: number;
|
|
133
|
-
moderate: number;
|
|
134
|
-
low: number;
|
|
135
|
-
info: number;
|
|
136
|
-
};
|
|
141
|
+
summary: VulnerabilitySummary;
|
|
137
142
|
}
|
|
138
143
|
interface MultiAuditSummary {
|
|
139
144
|
results: Array<{
|
|
@@ -141,21 +146,9 @@ interface MultiAuditSummary {
|
|
|
141
146
|
scanId: string;
|
|
142
147
|
processed: number;
|
|
143
148
|
resolved: number;
|
|
144
|
-
summary:
|
|
145
|
-
critical: number;
|
|
146
|
-
high: number;
|
|
147
|
-
moderate: number;
|
|
148
|
-
low: number;
|
|
149
|
-
info: number;
|
|
150
|
-
};
|
|
149
|
+
summary: VulnerabilitySummary;
|
|
151
150
|
}>;
|
|
152
|
-
totalSummary:
|
|
153
|
-
critical: number;
|
|
154
|
-
high: number;
|
|
155
|
-
moderate: number;
|
|
156
|
-
low: number;
|
|
157
|
-
info: number;
|
|
158
|
-
};
|
|
151
|
+
totalSummary: VulnerabilitySummary;
|
|
159
152
|
}
|
|
160
153
|
|
|
161
154
|
declare class MonitorClient {
|
|
@@ -188,6 +181,7 @@ declare class MonitorClient {
|
|
|
188
181
|
private versionCheckEnabled;
|
|
189
182
|
private auditTimeoutMs;
|
|
190
183
|
private registryTimeoutMs;
|
|
184
|
+
private npmRegistryUrl;
|
|
191
185
|
constructor(config: MonitorClientConfig);
|
|
192
186
|
/**
|
|
193
187
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -198,6 +192,18 @@ declare class MonitorClient {
|
|
|
198
192
|
* Removes potential API keys, tokens, and limits response length
|
|
199
193
|
*/
|
|
200
194
|
private sanitizeErrorResponse;
|
|
195
|
+
/**
|
|
196
|
+
* Security: Sanitize file paths in log messages to prevent exposing full filesystem structure
|
|
197
|
+
* Truncates long paths and shows only the last few segments
|
|
198
|
+
*/
|
|
199
|
+
private sanitizePath;
|
|
200
|
+
/**
|
|
201
|
+
* Execute a promise with a timeout. Properly cleans up the timer and supports cancellation.
|
|
202
|
+
* @param promiseFactory Factory function that receives an AbortSignal and returns a promise
|
|
203
|
+
* @param timeoutMs Timeout in milliseconds
|
|
204
|
+
* @param message Error message if timeout occurs
|
|
205
|
+
*/
|
|
206
|
+
private withTimeout;
|
|
201
207
|
captureError(error: Error, context?: ErrorContext): Promise<void>;
|
|
202
208
|
captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>;
|
|
203
209
|
flush(): Promise<void>;
|
|
@@ -239,9 +245,12 @@ declare class MonitorClient {
|
|
|
239
245
|
private startFlushTimer;
|
|
240
246
|
private stopFlushTimer;
|
|
241
247
|
syncDependencies(): Promise<void>;
|
|
248
|
+
private performDependencySync;
|
|
242
249
|
/**
|
|
243
250
|
* Enrich technologies with latest version information from npm registry.
|
|
244
251
|
* Only runs if versionCheckEnabled is true.
|
|
252
|
+
* @param technologies List of technologies to enrich
|
|
253
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
245
254
|
*/
|
|
246
255
|
private enrichWithLatestVersions;
|
|
247
256
|
syncTechnologies(technologies: TechnologyItem[]): Promise<void>;
|
|
@@ -251,10 +260,15 @@ declare class MonitorClient {
|
|
|
251
260
|
/**
|
|
252
261
|
* Fetch the latest version of a package from npm registry.
|
|
253
262
|
* Returns null if the package cannot be found or the request fails.
|
|
263
|
+
* Uses npmRegistryUrl config option if provided.
|
|
264
|
+
* @param packageName The package name to fetch
|
|
265
|
+
* @param signal Optional AbortSignal to cancel the request
|
|
254
266
|
*/
|
|
255
267
|
private fetchLatestVersion;
|
|
256
268
|
/**
|
|
257
269
|
* Fetch latest versions for multiple packages in parallel with concurrency limit.
|
|
270
|
+
* @param packageNames List of package names to fetch versions for
|
|
271
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
258
272
|
*/
|
|
259
273
|
private fetchLatestVersions;
|
|
260
274
|
private sendTechnologies;
|
|
@@ -320,6 +334,15 @@ declare class MonitorClient {
|
|
|
320
334
|
timeWindowMinutes?: number;
|
|
321
335
|
threshold?: number;
|
|
322
336
|
}): Promise<BruteForceDetectionResult>;
|
|
337
|
+
/**
|
|
338
|
+
* Handle exec errors from audit commands. Returns stdout if available, null if timed out, or rethrows.
|
|
339
|
+
*/
|
|
340
|
+
private handleAuditExecError;
|
|
341
|
+
/**
|
|
342
|
+
* Run a command with a reliable timeout using spawn.
|
|
343
|
+
* More reliable than execSync timeout in containerized environments.
|
|
344
|
+
*/
|
|
345
|
+
private runCommandWithTimeout;
|
|
323
346
|
/**
|
|
324
347
|
* Run npm audit and send results to the monitoring server.
|
|
325
348
|
* This scans the project for known vulnerabilities in dependencies.
|
|
@@ -342,6 +365,7 @@ declare class MonitorClient {
|
|
|
342
365
|
* @returns Combined summary of all audit results
|
|
343
366
|
*/
|
|
344
367
|
auditMultiplePaths(): Promise<MultiAuditSummary | null>;
|
|
368
|
+
private performMultiPathAudit;
|
|
345
369
|
/**
|
|
346
370
|
* Parse npm audit JSON output into vulnerability items
|
|
347
371
|
*/
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,8 @@ var CONFIG_LIMITS = {
|
|
|
47
47
|
// 64KB
|
|
48
48
|
MAX_RETRY_MAP_SIZE: 1e3,
|
|
49
49
|
// Prevent unbounded growth
|
|
50
|
+
RETRY_MAP_CLEANUP_PERCENTAGE: 0.1,
|
|
51
|
+
// Remove 10% of oldest entries when map is full
|
|
50
52
|
AUDIT_MAX_BUFFER: 10 * 1024 * 1024,
|
|
51
53
|
// 10MB
|
|
52
54
|
AUDIT_TIMEOUT_MS: 6e4,
|
|
@@ -57,8 +59,16 @@ var CONFIG_LIMITS = {
|
|
|
57
59
|
// 5 minutes
|
|
58
60
|
REGISTRY_TIMEOUT_MS: 5e3,
|
|
59
61
|
// 5 seconds (default)
|
|
60
|
-
MAX_REGISTRY_TIMEOUT_MS: 3e4
|
|
62
|
+
MAX_REGISTRY_TIMEOUT_MS: 3e4,
|
|
61
63
|
// 30 seconds (max configurable)
|
|
64
|
+
TECH_VERSION_FETCH_TIMEOUT_MS: 3e4,
|
|
65
|
+
// 30 seconds max for all version fetches
|
|
66
|
+
SYNC_DEPENDENCIES_TIMEOUT_MS: 6e4,
|
|
67
|
+
// 60 seconds max for all dependency syncs
|
|
68
|
+
AUDIT_MULTI_PATH_TIMEOUT_MS: 18e4,
|
|
69
|
+
// 3 minutes max for all audit paths
|
|
70
|
+
REGISTRY_CONCURRENCY_LIMIT: 5
|
|
71
|
+
// Limit parallel requests to avoid rate limiting
|
|
62
72
|
};
|
|
63
73
|
var MonitorClient = class {
|
|
64
74
|
constructor(config) {
|
|
@@ -75,8 +85,8 @@ var MonitorClient = class {
|
|
|
75
85
|
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
76
86
|
throw new Error("[MonitorClient] API key is required");
|
|
77
87
|
}
|
|
78
|
-
if (!/^cm_[a-zA-Z0-9_-]
|
|
79
|
-
throw new Error("[MonitorClient] Invalid API key format. Expected format: cm_xxx");
|
|
88
|
+
if (!/^cm_[a-zA-Z0-9_-]{16,256}$/.test(config.apiKey)) {
|
|
89
|
+
throw new Error("[MonitorClient] Invalid API key format. Expected format: cm_xxx (16-256 characters after prefix)");
|
|
80
90
|
}
|
|
81
91
|
if (!config.endpoint || config.endpoint.trim().length === 0) {
|
|
82
92
|
throw new Error("[MonitorClient] Endpoint URL is required");
|
|
@@ -87,6 +97,9 @@ var MonitorClient = class {
|
|
|
87
97
|
throw new Error("[MonitorClient] Endpoint must use HTTP or HTTPS protocol");
|
|
88
98
|
}
|
|
89
99
|
if (url.protocol === "http:" && config.environment !== "development" && config.environment !== "test") {
|
|
100
|
+
if (!config.allowInsecureHttp) {
|
|
101
|
+
throw new Error("[MonitorClient] HTTP endpoints require allowInsecureHttp: true in non-development environments");
|
|
102
|
+
}
|
|
90
103
|
console.warn("[MonitorClient] Warning: Using HTTP in non-development environment is not recommended");
|
|
91
104
|
}
|
|
92
105
|
} catch (err) {
|
|
@@ -116,8 +129,8 @@ var MonitorClient = class {
|
|
|
116
129
|
"@typescript-eslint/*"
|
|
117
130
|
];
|
|
118
131
|
this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
|
|
119
|
-
this.compiledExcludePatterns = this.excludePatterns.map((pattern) => {
|
|
120
|
-
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(
|
|
132
|
+
this.compiledExcludePatterns = this.excludePatterns.filter((pattern) => pattern.length <= 100).map((pattern) => {
|
|
133
|
+
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*+/g, "[^/]*");
|
|
121
134
|
return new RegExp(`^${regexPattern}$`);
|
|
122
135
|
});
|
|
123
136
|
this.autoAudit = config.autoAudit || false;
|
|
@@ -126,6 +139,7 @@ var MonitorClient = class {
|
|
|
126
139
|
this.versionCheckEnabled = config.versionCheckEnabled ?? true;
|
|
127
140
|
this.auditTimeoutMs = Math.min(CONFIG_LIMITS.MAX_AUDIT_TIMEOUT_MS, Math.max(1e3, config.auditTimeoutMs || CONFIG_LIMITS.AUDIT_TIMEOUT_MS));
|
|
128
141
|
this.registryTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REGISTRY_TIMEOUT_MS, Math.max(1e3, config.registryTimeoutMs || CONFIG_LIMITS.REGISTRY_TIMEOUT_MS));
|
|
142
|
+
this.npmRegistryUrl = (config.npmRegistryUrl || "https://registry.npmjs.org").replace(/\/$/, "");
|
|
129
143
|
this.startFlushTimer();
|
|
130
144
|
if (this.trackDependencies) {
|
|
131
145
|
this.syncDependencies().catch((err) => {
|
|
@@ -166,8 +180,43 @@ var MonitorClient = class {
|
|
|
166
180
|
sanitized = sanitized.replace(/cm_[a-zA-Z0-9_-]+/g, "[REDACTED]");
|
|
167
181
|
sanitized = sanitized.replace(/Bearer\s+[a-zA-Z0-9_.-]+/gi, "Bearer [REDACTED]");
|
|
168
182
|
sanitized = sanitized.replace(/authorization['":\s]+[a-zA-Z0-9_.-]+/gi, "authorization: [REDACTED]");
|
|
183
|
+
sanitized = sanitized.replace(/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "[JWT_REDACTED]");
|
|
184
|
+
sanitized = sanitized.replace(/AKIA[A-Z0-9]{16}/g, "[AWS_KEY_REDACTED]");
|
|
185
|
+
sanitized = sanitized.replace(/(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/g, "[AWS_SECRET_REDACTED]");
|
|
186
|
+
sanitized = sanitized.replace(/gh[pousr]_[A-Za-z0-9_]{36,}/g, "[GITHUB_TOKEN_REDACTED]");
|
|
169
187
|
return sanitized;
|
|
170
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Security: Sanitize file paths in log messages to prevent exposing full filesystem structure
|
|
191
|
+
* Truncates long paths and shows only the last few segments
|
|
192
|
+
*/
|
|
193
|
+
sanitizePath(filePath, maxSegments = 3) {
|
|
194
|
+
if (!filePath) return "";
|
|
195
|
+
const segments = filePath.split(/[/\\]/);
|
|
196
|
+
if (segments.length <= maxSegments) {
|
|
197
|
+
return filePath;
|
|
198
|
+
}
|
|
199
|
+
return ".../" + segments.slice(-maxSegments).join("/");
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Execute a promise with a timeout. Properly cleans up the timer and supports cancellation.
|
|
203
|
+
* @param promiseFactory Factory function that receives an AbortSignal and returns a promise
|
|
204
|
+
* @param timeoutMs Timeout in milliseconds
|
|
205
|
+
* @param message Error message if timeout occurs
|
|
206
|
+
*/
|
|
207
|
+
withTimeout(promiseFactory, timeoutMs, message) {
|
|
208
|
+
const controller = new AbortController();
|
|
209
|
+
let timeoutId;
|
|
210
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
211
|
+
timeoutId = setTimeout(() => {
|
|
212
|
+
controller.abort();
|
|
213
|
+
reject(new Error(message));
|
|
214
|
+
}, timeoutMs);
|
|
215
|
+
});
|
|
216
|
+
return Promise.race([promiseFactory(controller.signal), timeoutPromise]).finally(() => {
|
|
217
|
+
clearTimeout(timeoutId);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
171
220
|
async captureError(error, context) {
|
|
172
221
|
if (this.isClosed) return;
|
|
173
222
|
const payload = {
|
|
@@ -222,7 +271,7 @@ var MonitorClient = class {
|
|
|
222
271
|
const retries = this.retryCount.get(key) || 0;
|
|
223
272
|
if (retries < this.maxRetries) {
|
|
224
273
|
if (this.retryCount.size >= CONFIG_LIMITS.MAX_RETRY_MAP_SIZE) {
|
|
225
|
-
const keysToRemove = Array.from(this.retryCount.keys()).slice(0, Math.ceil(CONFIG_LIMITS.MAX_RETRY_MAP_SIZE *
|
|
274
|
+
const keysToRemove = Array.from(this.retryCount.keys()).slice(0, Math.ceil(CONFIG_LIMITS.MAX_RETRY_MAP_SIZE * CONFIG_LIMITS.RETRY_MAP_CLEANUP_PERCENTAGE));
|
|
226
275
|
for (const oldKey of keysToRemove) {
|
|
227
276
|
this.retryCount.delete(oldKey);
|
|
228
277
|
}
|
|
@@ -328,6 +377,8 @@ var MonitorClient = class {
|
|
|
328
377
|
* Uses auditMultiplePaths() if auditPaths is configured, otherwise runs single audit.
|
|
329
378
|
*/
|
|
330
379
|
async runScanAndTrackTime() {
|
|
380
|
+
const startTime = Date.now();
|
|
381
|
+
console.log("[MonitorClient] Starting vulnerability scan...");
|
|
331
382
|
try {
|
|
332
383
|
if (this.auditPaths && this.auditPaths.length > 0) {
|
|
333
384
|
await this.auditMultiplePaths();
|
|
@@ -335,6 +386,8 @@ var MonitorClient = class {
|
|
|
335
386
|
await this.auditDependencies();
|
|
336
387
|
}
|
|
337
388
|
this.lastScanTime = /* @__PURE__ */ new Date();
|
|
389
|
+
const duration = Date.now() - startTime;
|
|
390
|
+
console.log(`[MonitorClient] Vulnerability scan completed in ${duration}ms`);
|
|
338
391
|
} catch (err) {
|
|
339
392
|
console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
|
|
340
393
|
}
|
|
@@ -441,38 +494,56 @@ var MonitorClient = class {
|
|
|
441
494
|
}
|
|
442
495
|
}
|
|
443
496
|
async syncDependencies() {
|
|
497
|
+
console.log("[MonitorClient] Starting technology sync...");
|
|
444
498
|
try {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
499
|
+
await this.withTimeout(
|
|
500
|
+
(signal) => this.performDependencySync(signal),
|
|
501
|
+
CONFIG_LIMITS.SYNC_DEPENDENCIES_TIMEOUT_MS,
|
|
502
|
+
"Technology sync timed out"
|
|
503
|
+
);
|
|
504
|
+
console.log("[MonitorClient] Technology sync completed successfully");
|
|
505
|
+
} catch (err) {
|
|
506
|
+
console.error("[MonitorClient] Technology sync failed:", err instanceof Error ? err.message : String(err));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async performDependencySync(signal) {
|
|
510
|
+
if (this.dependencySources && this.dependencySources.length > 0) {
|
|
511
|
+
for (const source of this.dependencySources) {
|
|
512
|
+
if (signal?.aborted) {
|
|
513
|
+
console.log("[MonitorClient] Technology sync cancelled");
|
|
514
|
+
return;
|
|
452
515
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
console.log(`[MonitorClient] Technology sync completed for ${this.environment}`);
|
|
516
|
+
const technologies = await this.readPackageJsonFromPath(source.path);
|
|
517
|
+
if (technologies.length === 0) continue;
|
|
518
|
+
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies, signal);
|
|
519
|
+
await this.sendTechnologiesWithEnvironment(enrichedTechnologies, source.environment);
|
|
520
|
+
console.log(`[MonitorClient] Technology sync completed for ${source.environment}`);
|
|
459
521
|
}
|
|
460
|
-
}
|
|
461
|
-
|
|
522
|
+
} else {
|
|
523
|
+
const technologies = await this.readPackageJson();
|
|
524
|
+
if (technologies.length === 0) return;
|
|
525
|
+
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies, signal);
|
|
526
|
+
await this.sendTechnologies(enrichedTechnologies);
|
|
527
|
+
console.log(`[MonitorClient] Technology sync completed for ${this.environment}`);
|
|
462
528
|
}
|
|
463
529
|
}
|
|
464
530
|
/**
|
|
465
531
|
* Enrich technologies with latest version information from npm registry.
|
|
466
532
|
* Only runs if versionCheckEnabled is true.
|
|
533
|
+
* @param technologies List of technologies to enrich
|
|
534
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
467
535
|
*/
|
|
468
|
-
async enrichWithLatestVersions(technologies) {
|
|
536
|
+
async enrichWithLatestVersions(technologies, signal) {
|
|
469
537
|
if (!this.versionCheckEnabled) {
|
|
470
538
|
return technologies;
|
|
471
539
|
}
|
|
540
|
+
if (signal?.aborted) {
|
|
541
|
+
return technologies;
|
|
542
|
+
}
|
|
472
543
|
try {
|
|
473
544
|
console.log(`[MonitorClient] Fetching latest versions for ${technologies.length} packages...`);
|
|
474
545
|
const packageNames = technologies.map((t) => t.name);
|
|
475
|
-
const latestVersions = await this.fetchLatestVersions(packageNames);
|
|
546
|
+
const latestVersions = await this.fetchLatestVersions(packageNames, signal);
|
|
476
547
|
const foundCount = [...latestVersions.values()].filter((v) => v !== null).length;
|
|
477
548
|
console.log(`[MonitorClient] Fetched latest versions: ${foundCount}/${technologies.length} packages`);
|
|
478
549
|
return technologies.map((tech) => ({
|
|
@@ -504,7 +575,7 @@ var MonitorClient = class {
|
|
|
504
575
|
const resolvedPath = path.isAbsolute(packagePath) ? packagePath : path.join(baseDir, packagePath);
|
|
505
576
|
const normalizedPath = path.normalize(resolvedPath);
|
|
506
577
|
if (packagePath.includes("\0")) {
|
|
507
|
-
console.warn("[MonitorClient] Suspicious path blocked:", packagePath);
|
|
578
|
+
console.warn("[MonitorClient] Suspicious path blocked:", this.sanitizePath(packagePath));
|
|
508
579
|
return [];
|
|
509
580
|
}
|
|
510
581
|
if (!normalizedPath.endsWith("package.json")) {
|
|
@@ -517,7 +588,7 @@ var MonitorClient = class {
|
|
|
517
588
|
try {
|
|
518
589
|
const realPath = fs.realpathSync(normalizedPath);
|
|
519
590
|
if (!realPath.endsWith("package.json")) {
|
|
520
|
-
console.warn("[MonitorClient] Symlink points to non-package.json file:", packagePath);
|
|
591
|
+
console.warn("[MonitorClient] Symlink points to non-package.json file:", this.sanitizePath(packagePath));
|
|
521
592
|
return [];
|
|
522
593
|
}
|
|
523
594
|
} catch {
|
|
@@ -553,13 +624,17 @@ var MonitorClient = class {
|
|
|
553
624
|
/**
|
|
554
625
|
* Fetch the latest version of a package from npm registry.
|
|
555
626
|
* Returns null if the package cannot be found or the request fails.
|
|
627
|
+
* Uses npmRegistryUrl config option if provided.
|
|
628
|
+
* @param packageName The package name to fetch
|
|
629
|
+
* @param signal Optional AbortSignal to cancel the request
|
|
556
630
|
*/
|
|
557
|
-
async fetchLatestVersion(packageName) {
|
|
631
|
+
async fetchLatestVersion(packageName, signal) {
|
|
558
632
|
try {
|
|
633
|
+
if (signal?.aborted) return null;
|
|
559
634
|
const encodedName = encodeURIComponent(packageName).replace("%40", "@");
|
|
560
|
-
const response = await fetch(
|
|
635
|
+
const response = await fetch(`${this.npmRegistryUrl}/${encodedName}`, {
|
|
561
636
|
headers: { "Accept": "application/json" },
|
|
562
|
-
signal: AbortSignal.timeout(this.registryTimeoutMs)
|
|
637
|
+
signal: signal ?? AbortSignal.timeout(this.registryTimeoutMs)
|
|
563
638
|
});
|
|
564
639
|
if (!response.ok) return null;
|
|
565
640
|
const data = await response.json();
|
|
@@ -570,16 +645,25 @@ var MonitorClient = class {
|
|
|
570
645
|
}
|
|
571
646
|
/**
|
|
572
647
|
* Fetch latest versions for multiple packages in parallel with concurrency limit.
|
|
648
|
+
* @param packageNames List of package names to fetch versions for
|
|
649
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
573
650
|
*/
|
|
574
|
-
async fetchLatestVersions(packageNames) {
|
|
651
|
+
async fetchLatestVersions(packageNames, signal) {
|
|
575
652
|
const results = /* @__PURE__ */ new Map();
|
|
576
|
-
const concurrencyLimit =
|
|
653
|
+
const concurrencyLimit = CONFIG_LIMITS.REGISTRY_CONCURRENCY_LIMIT;
|
|
654
|
+
const totalBatches = Math.ceil(packageNames.length / concurrencyLimit);
|
|
577
655
|
for (let i = 0; i < packageNames.length; i += concurrencyLimit) {
|
|
656
|
+
if (signal?.aborted) {
|
|
657
|
+
console.log("[MonitorClient] Version fetch cancelled");
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
const batchNumber = Math.floor(i / concurrencyLimit) + 1;
|
|
661
|
+
console.log(`[MonitorClient] Fetching versions batch ${batchNumber}/${totalBatches}...`);
|
|
578
662
|
const batch = packageNames.slice(i, i + concurrencyLimit);
|
|
579
663
|
const batchResults = await Promise.all(
|
|
580
664
|
batch.map(async (name) => ({
|
|
581
665
|
name,
|
|
582
|
-
version: await this.fetchLatestVersion(name)
|
|
666
|
+
version: await this.fetchLatestVersion(name, signal)
|
|
583
667
|
}))
|
|
584
668
|
);
|
|
585
669
|
for (const { name, version } of batchResults) {
|
|
@@ -713,6 +797,67 @@ var MonitorClient = class {
|
|
|
713
797
|
const result = await response.json();
|
|
714
798
|
return result.data;
|
|
715
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Handle exec errors from audit commands. Returns stdout if available, null if timed out, or rethrows.
|
|
802
|
+
*/
|
|
803
|
+
handleAuditExecError(err, packageManager) {
|
|
804
|
+
const execError = err;
|
|
805
|
+
if (execError.killed) {
|
|
806
|
+
console.error(`[MonitorClient] ${packageManager} audit timed out`);
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
if (execError.stdout) {
|
|
810
|
+
return execError.stdout;
|
|
811
|
+
}
|
|
812
|
+
throw err;
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Run a command with a reliable timeout using spawn.
|
|
816
|
+
* More reliable than execSync timeout in containerized environments.
|
|
817
|
+
*/
|
|
818
|
+
async runCommandWithTimeout(command, args, options) {
|
|
819
|
+
const childProcess = await import("child_process");
|
|
820
|
+
const { spawn } = childProcess;
|
|
821
|
+
return new Promise((resolve) => {
|
|
822
|
+
const proc = spawn(command, args, {
|
|
823
|
+
cwd: options.cwd,
|
|
824
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
825
|
+
shell: false
|
|
826
|
+
});
|
|
827
|
+
let stdout = "";
|
|
828
|
+
let stderr = "";
|
|
829
|
+
let timedOut = false;
|
|
830
|
+
let killed = false;
|
|
831
|
+
const timeoutId = setTimeout(() => {
|
|
832
|
+
timedOut = true;
|
|
833
|
+
killed = true;
|
|
834
|
+
proc.kill("SIGTERM");
|
|
835
|
+
setTimeout(() => {
|
|
836
|
+
if (!proc.killed) {
|
|
837
|
+
proc.kill("SIGKILL");
|
|
838
|
+
}
|
|
839
|
+
}, 1e3);
|
|
840
|
+
}, options.timeout);
|
|
841
|
+
proc.stdout?.on("data", (data) => {
|
|
842
|
+
if (stdout.length < options.maxBuffer) {
|
|
843
|
+
stdout += data.toString();
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
proc.stderr?.on("data", (data) => {
|
|
847
|
+
if (stderr.length < options.maxBuffer) {
|
|
848
|
+
stderr += data.toString();
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
proc.on("close", () => {
|
|
852
|
+
clearTimeout(timeoutId);
|
|
853
|
+
resolve({ stdout, timedOut });
|
|
854
|
+
});
|
|
855
|
+
proc.on("error", () => {
|
|
856
|
+
clearTimeout(timeoutId);
|
|
857
|
+
resolve({ stdout, timedOut: killed });
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
}
|
|
716
861
|
/**
|
|
717
862
|
* Run npm audit and send results to the monitoring server.
|
|
718
863
|
* This scans the project for known vulnerabilities in dependencies.
|
|
@@ -726,14 +871,11 @@ var MonitorClient = class {
|
|
|
726
871
|
console.warn("[MonitorClient] auditDependencies only works in Node.js server environment");
|
|
727
872
|
return null;
|
|
728
873
|
}
|
|
729
|
-
let execSync;
|
|
730
874
|
let path;
|
|
731
875
|
let fs;
|
|
732
876
|
try {
|
|
733
|
-
const childProcess = await import("child_process");
|
|
734
877
|
const pathModule = await import("path");
|
|
735
878
|
const fsModule = await import("fs");
|
|
736
|
-
execSync = childProcess.execSync;
|
|
737
879
|
path = pathModule;
|
|
738
880
|
fs = fsModule;
|
|
739
881
|
} catch {
|
|
@@ -744,7 +886,7 @@ var MonitorClient = class {
|
|
|
744
886
|
const environment = options.environment || this.environment;
|
|
745
887
|
try {
|
|
746
888
|
let projectPath = options.projectPath || process.cwd();
|
|
747
|
-
if (projectPath.includes("\0") || /[;&|`$(){}[\]
|
|
889
|
+
if (projectPath.includes("\0") || /[;&|`$(){}[\]<>\n\r]/.test(projectPath)) {
|
|
748
890
|
console.error("[MonitorClient] Invalid projectPath: contains forbidden characters");
|
|
749
891
|
return null;
|
|
750
892
|
}
|
|
@@ -771,31 +913,21 @@ var MonitorClient = class {
|
|
|
771
913
|
return null;
|
|
772
914
|
}
|
|
773
915
|
const packageManager = this.detectPackageManager(projectPath, fs, path);
|
|
774
|
-
console.log(`[MonitorClient] Auditing ${projectPath} using ${packageManager}`);
|
|
916
|
+
console.log(`[MonitorClient] Auditing ${this.sanitizePath(projectPath)} using ${packageManager} (timeout: ${this.auditTimeoutMs}ms)`);
|
|
775
917
|
let auditOutput;
|
|
776
918
|
let vulnerabilities;
|
|
777
919
|
let totalDeps = 0;
|
|
778
920
|
if (packageManager === "yarn") {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
} catch (err) {
|
|
788
|
-
const execError = err;
|
|
789
|
-
if (execError.killed) {
|
|
790
|
-
console.error("[MonitorClient] yarn audit timed out");
|
|
791
|
-
return null;
|
|
792
|
-
}
|
|
793
|
-
if (execError.stdout) {
|
|
794
|
-
auditOutput = execError.stdout;
|
|
795
|
-
} else {
|
|
796
|
-
throw err;
|
|
797
|
-
}
|
|
921
|
+
const result2 = await this.runCommandWithTimeout("yarn", ["audit", "--json"], {
|
|
922
|
+
cwd: projectPath,
|
|
923
|
+
timeout: this.auditTimeoutMs,
|
|
924
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
925
|
+
});
|
|
926
|
+
if (result2.timedOut) {
|
|
927
|
+
console.error(`[MonitorClient] yarn audit timed out after ${this.auditTimeoutMs}ms`);
|
|
928
|
+
return null;
|
|
798
929
|
}
|
|
930
|
+
auditOutput = result2.stdout;
|
|
799
931
|
vulnerabilities = this.parseYarnAuditOutput(auditOutput);
|
|
800
932
|
const lines = auditOutput.trim().split("\n");
|
|
801
933
|
for (const line of lines) {
|
|
@@ -810,50 +942,30 @@ var MonitorClient = class {
|
|
|
810
942
|
}
|
|
811
943
|
} else if (packageManager === "pnpm") {
|
|
812
944
|
console.log("[MonitorClient] pnpm detected, using npm audit (pnpm compatible)");
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
} catch (err) {
|
|
822
|
-
const execError = err;
|
|
823
|
-
if (execError.killed) {
|
|
824
|
-
console.error("[MonitorClient] npm audit timed out");
|
|
825
|
-
return null;
|
|
826
|
-
}
|
|
827
|
-
if (execError.stdout) {
|
|
828
|
-
auditOutput = execError.stdout;
|
|
829
|
-
} else {
|
|
830
|
-
throw err;
|
|
831
|
-
}
|
|
945
|
+
const result2 = await this.runCommandWithTimeout("npm", ["audit", "--json"], {
|
|
946
|
+
cwd: projectPath,
|
|
947
|
+
timeout: this.auditTimeoutMs,
|
|
948
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
949
|
+
});
|
|
950
|
+
if (result2.timedOut) {
|
|
951
|
+
console.error(`[MonitorClient] npm audit timed out after ${this.auditTimeoutMs}ms`);
|
|
952
|
+
return null;
|
|
832
953
|
}
|
|
954
|
+
auditOutput = result2.stdout;
|
|
833
955
|
const auditData = JSON.parse(auditOutput);
|
|
834
956
|
vulnerabilities = this.parseNpmAuditOutput(auditData);
|
|
835
957
|
totalDeps = auditData.metadata?.dependencies?.total || 0;
|
|
836
958
|
} else {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
} catch (err) {
|
|
846
|
-
const execError = err;
|
|
847
|
-
if (execError.killed) {
|
|
848
|
-
console.error("[MonitorClient] npm audit timed out");
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
851
|
-
if (execError.stdout) {
|
|
852
|
-
auditOutput = execError.stdout;
|
|
853
|
-
} else {
|
|
854
|
-
throw err;
|
|
855
|
-
}
|
|
959
|
+
const result2 = await this.runCommandWithTimeout("npm", ["audit", "--json"], {
|
|
960
|
+
cwd: projectPath,
|
|
961
|
+
timeout: this.auditTimeoutMs,
|
|
962
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
963
|
+
});
|
|
964
|
+
if (result2.timedOut) {
|
|
965
|
+
console.error(`[MonitorClient] npm audit timed out after ${this.auditTimeoutMs}ms`);
|
|
966
|
+
return null;
|
|
856
967
|
}
|
|
968
|
+
auditOutput = result2.stdout;
|
|
857
969
|
const auditData = JSON.parse(auditOutput);
|
|
858
970
|
vulnerabilities = this.parseNpmAuditOutput(auditData);
|
|
859
971
|
totalDeps = auditData.metadata?.dependencies?.total || 0;
|
|
@@ -897,6 +1009,24 @@ var MonitorClient = class {
|
|
|
897
1009
|
console.warn("[MonitorClient] No auditPaths configured");
|
|
898
1010
|
return null;
|
|
899
1011
|
}
|
|
1012
|
+
console.log(`[MonitorClient] Starting multi-path audit (${this.auditPaths.length} paths)...`);
|
|
1013
|
+
try {
|
|
1014
|
+
const result = await this.withTimeout(
|
|
1015
|
+
() => this.performMultiPathAudit(),
|
|
1016
|
+
CONFIG_LIMITS.AUDIT_MULTI_PATH_TIMEOUT_MS,
|
|
1017
|
+
"Multi-path audit timed out"
|
|
1018
|
+
);
|
|
1019
|
+
if (result) {
|
|
1020
|
+
console.log(`[MonitorClient] Multi-path audit complete: ${result.results.length} paths scanned`);
|
|
1021
|
+
}
|
|
1022
|
+
return result;
|
|
1023
|
+
} catch (err) {
|
|
1024
|
+
console.error("[MonitorClient] Multi-path audit failed:", err instanceof Error ? err.message : String(err));
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
async performMultiPathAudit() {
|
|
1029
|
+
if (!this.auditPaths) return null;
|
|
900
1030
|
const results = [];
|
|
901
1031
|
const totalSummary = {
|
|
902
1032
|
critical: 0,
|
|
@@ -906,7 +1036,7 @@ var MonitorClient = class {
|
|
|
906
1036
|
info: 0
|
|
907
1037
|
};
|
|
908
1038
|
for (const auditPath of this.auditPaths) {
|
|
909
|
-
console.log(`[MonitorClient] Auditing ${auditPath.environment} at ${auditPath.path}`);
|
|
1039
|
+
console.log(`[MonitorClient] Auditing ${auditPath.environment} at ${this.sanitizePath(auditPath.path)}`);
|
|
910
1040
|
try {
|
|
911
1041
|
const summary = await this.auditDependencies({
|
|
912
1042
|
projectPath: auditPath.path,
|
|
@@ -934,7 +1064,6 @@ var MonitorClient = class {
|
|
|
934
1064
|
console.warn("[MonitorClient] No audit results collected from any path");
|
|
935
1065
|
return null;
|
|
936
1066
|
}
|
|
937
|
-
console.log(`[MonitorClient] Multi-path audit complete: ${results.length} paths scanned`);
|
|
938
1067
|
return {
|
|
939
1068
|
results,
|
|
940
1069
|
totalSummary
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,8 @@ var CONFIG_LIMITS = {
|
|
|
11
11
|
// 64KB
|
|
12
12
|
MAX_RETRY_MAP_SIZE: 1e3,
|
|
13
13
|
// Prevent unbounded growth
|
|
14
|
+
RETRY_MAP_CLEANUP_PERCENTAGE: 0.1,
|
|
15
|
+
// Remove 10% of oldest entries when map is full
|
|
14
16
|
AUDIT_MAX_BUFFER: 10 * 1024 * 1024,
|
|
15
17
|
// 10MB
|
|
16
18
|
AUDIT_TIMEOUT_MS: 6e4,
|
|
@@ -21,8 +23,16 @@ var CONFIG_LIMITS = {
|
|
|
21
23
|
// 5 minutes
|
|
22
24
|
REGISTRY_TIMEOUT_MS: 5e3,
|
|
23
25
|
// 5 seconds (default)
|
|
24
|
-
MAX_REGISTRY_TIMEOUT_MS: 3e4
|
|
26
|
+
MAX_REGISTRY_TIMEOUT_MS: 3e4,
|
|
25
27
|
// 30 seconds (max configurable)
|
|
28
|
+
TECH_VERSION_FETCH_TIMEOUT_MS: 3e4,
|
|
29
|
+
// 30 seconds max for all version fetches
|
|
30
|
+
SYNC_DEPENDENCIES_TIMEOUT_MS: 6e4,
|
|
31
|
+
// 60 seconds max for all dependency syncs
|
|
32
|
+
AUDIT_MULTI_PATH_TIMEOUT_MS: 18e4,
|
|
33
|
+
// 3 minutes max for all audit paths
|
|
34
|
+
REGISTRY_CONCURRENCY_LIMIT: 5
|
|
35
|
+
// Limit parallel requests to avoid rate limiting
|
|
26
36
|
};
|
|
27
37
|
var MonitorClient = class {
|
|
28
38
|
constructor(config) {
|
|
@@ -39,8 +49,8 @@ var MonitorClient = class {
|
|
|
39
49
|
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
40
50
|
throw new Error("[MonitorClient] API key is required");
|
|
41
51
|
}
|
|
42
|
-
if (!/^cm_[a-zA-Z0-9_-]
|
|
43
|
-
throw new Error("[MonitorClient] Invalid API key format. Expected format: cm_xxx");
|
|
52
|
+
if (!/^cm_[a-zA-Z0-9_-]{16,256}$/.test(config.apiKey)) {
|
|
53
|
+
throw new Error("[MonitorClient] Invalid API key format. Expected format: cm_xxx (16-256 characters after prefix)");
|
|
44
54
|
}
|
|
45
55
|
if (!config.endpoint || config.endpoint.trim().length === 0) {
|
|
46
56
|
throw new Error("[MonitorClient] Endpoint URL is required");
|
|
@@ -51,6 +61,9 @@ var MonitorClient = class {
|
|
|
51
61
|
throw new Error("[MonitorClient] Endpoint must use HTTP or HTTPS protocol");
|
|
52
62
|
}
|
|
53
63
|
if (url.protocol === "http:" && config.environment !== "development" && config.environment !== "test") {
|
|
64
|
+
if (!config.allowInsecureHttp) {
|
|
65
|
+
throw new Error("[MonitorClient] HTTP endpoints require allowInsecureHttp: true in non-development environments");
|
|
66
|
+
}
|
|
54
67
|
console.warn("[MonitorClient] Warning: Using HTTP in non-development environment is not recommended");
|
|
55
68
|
}
|
|
56
69
|
} catch (err) {
|
|
@@ -80,8 +93,8 @@ var MonitorClient = class {
|
|
|
80
93
|
"@typescript-eslint/*"
|
|
81
94
|
];
|
|
82
95
|
this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
|
|
83
|
-
this.compiledExcludePatterns = this.excludePatterns.map((pattern) => {
|
|
84
|
-
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(
|
|
96
|
+
this.compiledExcludePatterns = this.excludePatterns.filter((pattern) => pattern.length <= 100).map((pattern) => {
|
|
97
|
+
const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*+/g, "[^/]*");
|
|
85
98
|
return new RegExp(`^${regexPattern}$`);
|
|
86
99
|
});
|
|
87
100
|
this.autoAudit = config.autoAudit || false;
|
|
@@ -90,6 +103,7 @@ var MonitorClient = class {
|
|
|
90
103
|
this.versionCheckEnabled = config.versionCheckEnabled ?? true;
|
|
91
104
|
this.auditTimeoutMs = Math.min(CONFIG_LIMITS.MAX_AUDIT_TIMEOUT_MS, Math.max(1e3, config.auditTimeoutMs || CONFIG_LIMITS.AUDIT_TIMEOUT_MS));
|
|
92
105
|
this.registryTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REGISTRY_TIMEOUT_MS, Math.max(1e3, config.registryTimeoutMs || CONFIG_LIMITS.REGISTRY_TIMEOUT_MS));
|
|
106
|
+
this.npmRegistryUrl = (config.npmRegistryUrl || "https://registry.npmjs.org").replace(/\/$/, "");
|
|
93
107
|
this.startFlushTimer();
|
|
94
108
|
if (this.trackDependencies) {
|
|
95
109
|
this.syncDependencies().catch((err) => {
|
|
@@ -130,8 +144,43 @@ var MonitorClient = class {
|
|
|
130
144
|
sanitized = sanitized.replace(/cm_[a-zA-Z0-9_-]+/g, "[REDACTED]");
|
|
131
145
|
sanitized = sanitized.replace(/Bearer\s+[a-zA-Z0-9_.-]+/gi, "Bearer [REDACTED]");
|
|
132
146
|
sanitized = sanitized.replace(/authorization['":\s]+[a-zA-Z0-9_.-]+/gi, "authorization: [REDACTED]");
|
|
147
|
+
sanitized = sanitized.replace(/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "[JWT_REDACTED]");
|
|
148
|
+
sanitized = sanitized.replace(/AKIA[A-Z0-9]{16}/g, "[AWS_KEY_REDACTED]");
|
|
149
|
+
sanitized = sanitized.replace(/(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/g, "[AWS_SECRET_REDACTED]");
|
|
150
|
+
sanitized = sanitized.replace(/gh[pousr]_[A-Za-z0-9_]{36,}/g, "[GITHUB_TOKEN_REDACTED]");
|
|
133
151
|
return sanitized;
|
|
134
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Security: Sanitize file paths in log messages to prevent exposing full filesystem structure
|
|
155
|
+
* Truncates long paths and shows only the last few segments
|
|
156
|
+
*/
|
|
157
|
+
sanitizePath(filePath, maxSegments = 3) {
|
|
158
|
+
if (!filePath) return "";
|
|
159
|
+
const segments = filePath.split(/[/\\]/);
|
|
160
|
+
if (segments.length <= maxSegments) {
|
|
161
|
+
return filePath;
|
|
162
|
+
}
|
|
163
|
+
return ".../" + segments.slice(-maxSegments).join("/");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Execute a promise with a timeout. Properly cleans up the timer and supports cancellation.
|
|
167
|
+
* @param promiseFactory Factory function that receives an AbortSignal and returns a promise
|
|
168
|
+
* @param timeoutMs Timeout in milliseconds
|
|
169
|
+
* @param message Error message if timeout occurs
|
|
170
|
+
*/
|
|
171
|
+
withTimeout(promiseFactory, timeoutMs, message) {
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
let timeoutId;
|
|
174
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
175
|
+
timeoutId = setTimeout(() => {
|
|
176
|
+
controller.abort();
|
|
177
|
+
reject(new Error(message));
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
});
|
|
180
|
+
return Promise.race([promiseFactory(controller.signal), timeoutPromise]).finally(() => {
|
|
181
|
+
clearTimeout(timeoutId);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
135
184
|
async captureError(error, context) {
|
|
136
185
|
if (this.isClosed) return;
|
|
137
186
|
const payload = {
|
|
@@ -186,7 +235,7 @@ var MonitorClient = class {
|
|
|
186
235
|
const retries = this.retryCount.get(key) || 0;
|
|
187
236
|
if (retries < this.maxRetries) {
|
|
188
237
|
if (this.retryCount.size >= CONFIG_LIMITS.MAX_RETRY_MAP_SIZE) {
|
|
189
|
-
const keysToRemove = Array.from(this.retryCount.keys()).slice(0, Math.ceil(CONFIG_LIMITS.MAX_RETRY_MAP_SIZE *
|
|
238
|
+
const keysToRemove = Array.from(this.retryCount.keys()).slice(0, Math.ceil(CONFIG_LIMITS.MAX_RETRY_MAP_SIZE * CONFIG_LIMITS.RETRY_MAP_CLEANUP_PERCENTAGE));
|
|
190
239
|
for (const oldKey of keysToRemove) {
|
|
191
240
|
this.retryCount.delete(oldKey);
|
|
192
241
|
}
|
|
@@ -292,6 +341,8 @@ var MonitorClient = class {
|
|
|
292
341
|
* Uses auditMultiplePaths() if auditPaths is configured, otherwise runs single audit.
|
|
293
342
|
*/
|
|
294
343
|
async runScanAndTrackTime() {
|
|
344
|
+
const startTime = Date.now();
|
|
345
|
+
console.log("[MonitorClient] Starting vulnerability scan...");
|
|
295
346
|
try {
|
|
296
347
|
if (this.auditPaths && this.auditPaths.length > 0) {
|
|
297
348
|
await this.auditMultiplePaths();
|
|
@@ -299,6 +350,8 @@ var MonitorClient = class {
|
|
|
299
350
|
await this.auditDependencies();
|
|
300
351
|
}
|
|
301
352
|
this.lastScanTime = /* @__PURE__ */ new Date();
|
|
353
|
+
const duration = Date.now() - startTime;
|
|
354
|
+
console.log(`[MonitorClient] Vulnerability scan completed in ${duration}ms`);
|
|
302
355
|
} catch (err) {
|
|
303
356
|
console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
|
|
304
357
|
}
|
|
@@ -405,38 +458,56 @@ var MonitorClient = class {
|
|
|
405
458
|
}
|
|
406
459
|
}
|
|
407
460
|
async syncDependencies() {
|
|
461
|
+
console.log("[MonitorClient] Starting technology sync...");
|
|
408
462
|
try {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
463
|
+
await this.withTimeout(
|
|
464
|
+
(signal) => this.performDependencySync(signal),
|
|
465
|
+
CONFIG_LIMITS.SYNC_DEPENDENCIES_TIMEOUT_MS,
|
|
466
|
+
"Technology sync timed out"
|
|
467
|
+
);
|
|
468
|
+
console.log("[MonitorClient] Technology sync completed successfully");
|
|
469
|
+
} catch (err) {
|
|
470
|
+
console.error("[MonitorClient] Technology sync failed:", err instanceof Error ? err.message : String(err));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async performDependencySync(signal) {
|
|
474
|
+
if (this.dependencySources && this.dependencySources.length > 0) {
|
|
475
|
+
for (const source of this.dependencySources) {
|
|
476
|
+
if (signal?.aborted) {
|
|
477
|
+
console.log("[MonitorClient] Technology sync cancelled");
|
|
478
|
+
return;
|
|
416
479
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
console.log(`[MonitorClient] Technology sync completed for ${this.environment}`);
|
|
480
|
+
const technologies = await this.readPackageJsonFromPath(source.path);
|
|
481
|
+
if (technologies.length === 0) continue;
|
|
482
|
+
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies, signal);
|
|
483
|
+
await this.sendTechnologiesWithEnvironment(enrichedTechnologies, source.environment);
|
|
484
|
+
console.log(`[MonitorClient] Technology sync completed for ${source.environment}`);
|
|
423
485
|
}
|
|
424
|
-
}
|
|
425
|
-
|
|
486
|
+
} else {
|
|
487
|
+
const technologies = await this.readPackageJson();
|
|
488
|
+
if (technologies.length === 0) return;
|
|
489
|
+
const enrichedTechnologies = await this.enrichWithLatestVersions(technologies, signal);
|
|
490
|
+
await this.sendTechnologies(enrichedTechnologies);
|
|
491
|
+
console.log(`[MonitorClient] Technology sync completed for ${this.environment}`);
|
|
426
492
|
}
|
|
427
493
|
}
|
|
428
494
|
/**
|
|
429
495
|
* Enrich technologies with latest version information from npm registry.
|
|
430
496
|
* Only runs if versionCheckEnabled is true.
|
|
497
|
+
* @param technologies List of technologies to enrich
|
|
498
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
431
499
|
*/
|
|
432
|
-
async enrichWithLatestVersions(technologies) {
|
|
500
|
+
async enrichWithLatestVersions(technologies, signal) {
|
|
433
501
|
if (!this.versionCheckEnabled) {
|
|
434
502
|
return technologies;
|
|
435
503
|
}
|
|
504
|
+
if (signal?.aborted) {
|
|
505
|
+
return technologies;
|
|
506
|
+
}
|
|
436
507
|
try {
|
|
437
508
|
console.log(`[MonitorClient] Fetching latest versions for ${technologies.length} packages...`);
|
|
438
509
|
const packageNames = technologies.map((t) => t.name);
|
|
439
|
-
const latestVersions = await this.fetchLatestVersions(packageNames);
|
|
510
|
+
const latestVersions = await this.fetchLatestVersions(packageNames, signal);
|
|
440
511
|
const foundCount = [...latestVersions.values()].filter((v) => v !== null).length;
|
|
441
512
|
console.log(`[MonitorClient] Fetched latest versions: ${foundCount}/${technologies.length} packages`);
|
|
442
513
|
return technologies.map((tech) => ({
|
|
@@ -468,7 +539,7 @@ var MonitorClient = class {
|
|
|
468
539
|
const resolvedPath = path.isAbsolute(packagePath) ? packagePath : path.join(baseDir, packagePath);
|
|
469
540
|
const normalizedPath = path.normalize(resolvedPath);
|
|
470
541
|
if (packagePath.includes("\0")) {
|
|
471
|
-
console.warn("[MonitorClient] Suspicious path blocked:", packagePath);
|
|
542
|
+
console.warn("[MonitorClient] Suspicious path blocked:", this.sanitizePath(packagePath));
|
|
472
543
|
return [];
|
|
473
544
|
}
|
|
474
545
|
if (!normalizedPath.endsWith("package.json")) {
|
|
@@ -481,7 +552,7 @@ var MonitorClient = class {
|
|
|
481
552
|
try {
|
|
482
553
|
const realPath = fs.realpathSync(normalizedPath);
|
|
483
554
|
if (!realPath.endsWith("package.json")) {
|
|
484
|
-
console.warn("[MonitorClient] Symlink points to non-package.json file:", packagePath);
|
|
555
|
+
console.warn("[MonitorClient] Symlink points to non-package.json file:", this.sanitizePath(packagePath));
|
|
485
556
|
return [];
|
|
486
557
|
}
|
|
487
558
|
} catch {
|
|
@@ -517,13 +588,17 @@ var MonitorClient = class {
|
|
|
517
588
|
/**
|
|
518
589
|
* Fetch the latest version of a package from npm registry.
|
|
519
590
|
* Returns null if the package cannot be found or the request fails.
|
|
591
|
+
* Uses npmRegistryUrl config option if provided.
|
|
592
|
+
* @param packageName The package name to fetch
|
|
593
|
+
* @param signal Optional AbortSignal to cancel the request
|
|
520
594
|
*/
|
|
521
|
-
async fetchLatestVersion(packageName) {
|
|
595
|
+
async fetchLatestVersion(packageName, signal) {
|
|
522
596
|
try {
|
|
597
|
+
if (signal?.aborted) return null;
|
|
523
598
|
const encodedName = encodeURIComponent(packageName).replace("%40", "@");
|
|
524
|
-
const response = await fetch(
|
|
599
|
+
const response = await fetch(`${this.npmRegistryUrl}/${encodedName}`, {
|
|
525
600
|
headers: { "Accept": "application/json" },
|
|
526
|
-
signal: AbortSignal.timeout(this.registryTimeoutMs)
|
|
601
|
+
signal: signal ?? AbortSignal.timeout(this.registryTimeoutMs)
|
|
527
602
|
});
|
|
528
603
|
if (!response.ok) return null;
|
|
529
604
|
const data = await response.json();
|
|
@@ -534,16 +609,25 @@ var MonitorClient = class {
|
|
|
534
609
|
}
|
|
535
610
|
/**
|
|
536
611
|
* Fetch latest versions for multiple packages in parallel with concurrency limit.
|
|
612
|
+
* @param packageNames List of package names to fetch versions for
|
|
613
|
+
* @param signal Optional AbortSignal to cancel the operation
|
|
537
614
|
*/
|
|
538
|
-
async fetchLatestVersions(packageNames) {
|
|
615
|
+
async fetchLatestVersions(packageNames, signal) {
|
|
539
616
|
const results = /* @__PURE__ */ new Map();
|
|
540
|
-
const concurrencyLimit =
|
|
617
|
+
const concurrencyLimit = CONFIG_LIMITS.REGISTRY_CONCURRENCY_LIMIT;
|
|
618
|
+
const totalBatches = Math.ceil(packageNames.length / concurrencyLimit);
|
|
541
619
|
for (let i = 0; i < packageNames.length; i += concurrencyLimit) {
|
|
620
|
+
if (signal?.aborted) {
|
|
621
|
+
console.log("[MonitorClient] Version fetch cancelled");
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
const batchNumber = Math.floor(i / concurrencyLimit) + 1;
|
|
625
|
+
console.log(`[MonitorClient] Fetching versions batch ${batchNumber}/${totalBatches}...`);
|
|
542
626
|
const batch = packageNames.slice(i, i + concurrencyLimit);
|
|
543
627
|
const batchResults = await Promise.all(
|
|
544
628
|
batch.map(async (name) => ({
|
|
545
629
|
name,
|
|
546
|
-
version: await this.fetchLatestVersion(name)
|
|
630
|
+
version: await this.fetchLatestVersion(name, signal)
|
|
547
631
|
}))
|
|
548
632
|
);
|
|
549
633
|
for (const { name, version } of batchResults) {
|
|
@@ -677,6 +761,67 @@ var MonitorClient = class {
|
|
|
677
761
|
const result = await response.json();
|
|
678
762
|
return result.data;
|
|
679
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* Handle exec errors from audit commands. Returns stdout if available, null if timed out, or rethrows.
|
|
766
|
+
*/
|
|
767
|
+
handleAuditExecError(err, packageManager) {
|
|
768
|
+
const execError = err;
|
|
769
|
+
if (execError.killed) {
|
|
770
|
+
console.error(`[MonitorClient] ${packageManager} audit timed out`);
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
if (execError.stdout) {
|
|
774
|
+
return execError.stdout;
|
|
775
|
+
}
|
|
776
|
+
throw err;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Run a command with a reliable timeout using spawn.
|
|
780
|
+
* More reliable than execSync timeout in containerized environments.
|
|
781
|
+
*/
|
|
782
|
+
async runCommandWithTimeout(command, args, options) {
|
|
783
|
+
const childProcess = await import("child_process");
|
|
784
|
+
const { spawn } = childProcess;
|
|
785
|
+
return new Promise((resolve) => {
|
|
786
|
+
const proc = spawn(command, args, {
|
|
787
|
+
cwd: options.cwd,
|
|
788
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
789
|
+
shell: false
|
|
790
|
+
});
|
|
791
|
+
let stdout = "";
|
|
792
|
+
let stderr = "";
|
|
793
|
+
let timedOut = false;
|
|
794
|
+
let killed = false;
|
|
795
|
+
const timeoutId = setTimeout(() => {
|
|
796
|
+
timedOut = true;
|
|
797
|
+
killed = true;
|
|
798
|
+
proc.kill("SIGTERM");
|
|
799
|
+
setTimeout(() => {
|
|
800
|
+
if (!proc.killed) {
|
|
801
|
+
proc.kill("SIGKILL");
|
|
802
|
+
}
|
|
803
|
+
}, 1e3);
|
|
804
|
+
}, options.timeout);
|
|
805
|
+
proc.stdout?.on("data", (data) => {
|
|
806
|
+
if (stdout.length < options.maxBuffer) {
|
|
807
|
+
stdout += data.toString();
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
proc.stderr?.on("data", (data) => {
|
|
811
|
+
if (stderr.length < options.maxBuffer) {
|
|
812
|
+
stderr += data.toString();
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
proc.on("close", () => {
|
|
816
|
+
clearTimeout(timeoutId);
|
|
817
|
+
resolve({ stdout, timedOut });
|
|
818
|
+
});
|
|
819
|
+
proc.on("error", () => {
|
|
820
|
+
clearTimeout(timeoutId);
|
|
821
|
+
resolve({ stdout, timedOut: killed });
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
}
|
|
680
825
|
/**
|
|
681
826
|
* Run npm audit and send results to the monitoring server.
|
|
682
827
|
* This scans the project for known vulnerabilities in dependencies.
|
|
@@ -690,14 +835,11 @@ var MonitorClient = class {
|
|
|
690
835
|
console.warn("[MonitorClient] auditDependencies only works in Node.js server environment");
|
|
691
836
|
return null;
|
|
692
837
|
}
|
|
693
|
-
let execSync;
|
|
694
838
|
let path;
|
|
695
839
|
let fs;
|
|
696
840
|
try {
|
|
697
|
-
const childProcess = await import("child_process");
|
|
698
841
|
const pathModule = await import("path");
|
|
699
842
|
const fsModule = await import("fs");
|
|
700
|
-
execSync = childProcess.execSync;
|
|
701
843
|
path = pathModule;
|
|
702
844
|
fs = fsModule;
|
|
703
845
|
} catch {
|
|
@@ -708,7 +850,7 @@ var MonitorClient = class {
|
|
|
708
850
|
const environment = options.environment || this.environment;
|
|
709
851
|
try {
|
|
710
852
|
let projectPath = options.projectPath || process.cwd();
|
|
711
|
-
if (projectPath.includes("\0") || /[;&|`$(){}[\]
|
|
853
|
+
if (projectPath.includes("\0") || /[;&|`$(){}[\]<>\n\r]/.test(projectPath)) {
|
|
712
854
|
console.error("[MonitorClient] Invalid projectPath: contains forbidden characters");
|
|
713
855
|
return null;
|
|
714
856
|
}
|
|
@@ -735,31 +877,21 @@ var MonitorClient = class {
|
|
|
735
877
|
return null;
|
|
736
878
|
}
|
|
737
879
|
const packageManager = this.detectPackageManager(projectPath, fs, path);
|
|
738
|
-
console.log(`[MonitorClient] Auditing ${projectPath} using ${packageManager}`);
|
|
880
|
+
console.log(`[MonitorClient] Auditing ${this.sanitizePath(projectPath)} using ${packageManager} (timeout: ${this.auditTimeoutMs}ms)`);
|
|
739
881
|
let auditOutput;
|
|
740
882
|
let vulnerabilities;
|
|
741
883
|
let totalDeps = 0;
|
|
742
884
|
if (packageManager === "yarn") {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
} catch (err) {
|
|
752
|
-
const execError = err;
|
|
753
|
-
if (execError.killed) {
|
|
754
|
-
console.error("[MonitorClient] yarn audit timed out");
|
|
755
|
-
return null;
|
|
756
|
-
}
|
|
757
|
-
if (execError.stdout) {
|
|
758
|
-
auditOutput = execError.stdout;
|
|
759
|
-
} else {
|
|
760
|
-
throw err;
|
|
761
|
-
}
|
|
885
|
+
const result2 = await this.runCommandWithTimeout("yarn", ["audit", "--json"], {
|
|
886
|
+
cwd: projectPath,
|
|
887
|
+
timeout: this.auditTimeoutMs,
|
|
888
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
889
|
+
});
|
|
890
|
+
if (result2.timedOut) {
|
|
891
|
+
console.error(`[MonitorClient] yarn audit timed out after ${this.auditTimeoutMs}ms`);
|
|
892
|
+
return null;
|
|
762
893
|
}
|
|
894
|
+
auditOutput = result2.stdout;
|
|
763
895
|
vulnerabilities = this.parseYarnAuditOutput(auditOutput);
|
|
764
896
|
const lines = auditOutput.trim().split("\n");
|
|
765
897
|
for (const line of lines) {
|
|
@@ -774,50 +906,30 @@ var MonitorClient = class {
|
|
|
774
906
|
}
|
|
775
907
|
} else if (packageManager === "pnpm") {
|
|
776
908
|
console.log("[MonitorClient] pnpm detected, using npm audit (pnpm compatible)");
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
} catch (err) {
|
|
786
|
-
const execError = err;
|
|
787
|
-
if (execError.killed) {
|
|
788
|
-
console.error("[MonitorClient] npm audit timed out");
|
|
789
|
-
return null;
|
|
790
|
-
}
|
|
791
|
-
if (execError.stdout) {
|
|
792
|
-
auditOutput = execError.stdout;
|
|
793
|
-
} else {
|
|
794
|
-
throw err;
|
|
795
|
-
}
|
|
909
|
+
const result2 = await this.runCommandWithTimeout("npm", ["audit", "--json"], {
|
|
910
|
+
cwd: projectPath,
|
|
911
|
+
timeout: this.auditTimeoutMs,
|
|
912
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
913
|
+
});
|
|
914
|
+
if (result2.timedOut) {
|
|
915
|
+
console.error(`[MonitorClient] npm audit timed out after ${this.auditTimeoutMs}ms`);
|
|
916
|
+
return null;
|
|
796
917
|
}
|
|
918
|
+
auditOutput = result2.stdout;
|
|
797
919
|
const auditData = JSON.parse(auditOutput);
|
|
798
920
|
vulnerabilities = this.parseNpmAuditOutput(auditData);
|
|
799
921
|
totalDeps = auditData.metadata?.dependencies?.total || 0;
|
|
800
922
|
} else {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
} catch (err) {
|
|
810
|
-
const execError = err;
|
|
811
|
-
if (execError.killed) {
|
|
812
|
-
console.error("[MonitorClient] npm audit timed out");
|
|
813
|
-
return null;
|
|
814
|
-
}
|
|
815
|
-
if (execError.stdout) {
|
|
816
|
-
auditOutput = execError.stdout;
|
|
817
|
-
} else {
|
|
818
|
-
throw err;
|
|
819
|
-
}
|
|
923
|
+
const result2 = await this.runCommandWithTimeout("npm", ["audit", "--json"], {
|
|
924
|
+
cwd: projectPath,
|
|
925
|
+
timeout: this.auditTimeoutMs,
|
|
926
|
+
maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER
|
|
927
|
+
});
|
|
928
|
+
if (result2.timedOut) {
|
|
929
|
+
console.error(`[MonitorClient] npm audit timed out after ${this.auditTimeoutMs}ms`);
|
|
930
|
+
return null;
|
|
820
931
|
}
|
|
932
|
+
auditOutput = result2.stdout;
|
|
821
933
|
const auditData = JSON.parse(auditOutput);
|
|
822
934
|
vulnerabilities = this.parseNpmAuditOutput(auditData);
|
|
823
935
|
totalDeps = auditData.metadata?.dependencies?.total || 0;
|
|
@@ -861,6 +973,24 @@ var MonitorClient = class {
|
|
|
861
973
|
console.warn("[MonitorClient] No auditPaths configured");
|
|
862
974
|
return null;
|
|
863
975
|
}
|
|
976
|
+
console.log(`[MonitorClient] Starting multi-path audit (${this.auditPaths.length} paths)...`);
|
|
977
|
+
try {
|
|
978
|
+
const result = await this.withTimeout(
|
|
979
|
+
() => this.performMultiPathAudit(),
|
|
980
|
+
CONFIG_LIMITS.AUDIT_MULTI_PATH_TIMEOUT_MS,
|
|
981
|
+
"Multi-path audit timed out"
|
|
982
|
+
);
|
|
983
|
+
if (result) {
|
|
984
|
+
console.log(`[MonitorClient] Multi-path audit complete: ${result.results.length} paths scanned`);
|
|
985
|
+
}
|
|
986
|
+
return result;
|
|
987
|
+
} catch (err) {
|
|
988
|
+
console.error("[MonitorClient] Multi-path audit failed:", err instanceof Error ? err.message : String(err));
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
async performMultiPathAudit() {
|
|
993
|
+
if (!this.auditPaths) return null;
|
|
864
994
|
const results = [];
|
|
865
995
|
const totalSummary = {
|
|
866
996
|
critical: 0,
|
|
@@ -870,7 +1000,7 @@ var MonitorClient = class {
|
|
|
870
1000
|
info: 0
|
|
871
1001
|
};
|
|
872
1002
|
for (const auditPath of this.auditPaths) {
|
|
873
|
-
console.log(`[MonitorClient] Auditing ${auditPath.environment} at ${auditPath.path}`);
|
|
1003
|
+
console.log(`[MonitorClient] Auditing ${auditPath.environment} at ${this.sanitizePath(auditPath.path)}`);
|
|
874
1004
|
try {
|
|
875
1005
|
const summary = await this.auditDependencies({
|
|
876
1006
|
projectPath: auditPath.path,
|
|
@@ -898,7 +1028,6 @@ var MonitorClient = class {
|
|
|
898
1028
|
console.warn("[MonitorClient] No audit results collected from any path");
|
|
899
1029
|
return null;
|
|
900
1030
|
}
|
|
901
|
-
console.log(`[MonitorClient] Multi-path audit complete: ${results.length} paths scanned`);
|
|
902
1031
|
return {
|
|
903
1032
|
results,
|
|
904
1033
|
totalSummary
|
package/package.json
CHANGED