@ceon-oy/monitor-sdk 1.0.9 → 1.0.11

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 CHANGED
@@ -97,6 +97,10 @@ interface MonitorClientConfig {
97
97
  }[];
98
98
  excludePatterns?: string[]; // Glob patterns to exclude (e.g., '@types/*')
99
99
  autoAudit?: boolean; // Enable automatic vulnerability scanning (default: false)
100
+ auditPaths?: { // Multiple directories for vulnerability scanning
101
+ path: string; // Directory path (e.g., '.', '../client')
102
+ environment: string; // Environment label (e.g., 'server', 'client')
103
+ }[];
100
104
  }
101
105
  ```
102
106
 
@@ -227,6 +231,47 @@ With `autoAudit` enabled:
227
231
 
228
232
  Configure the scan interval in the Ceon Monitor dashboard under the project's Vulnerabilities page.
229
233
 
234
+ #### Multi-Directory Vulnerability Auditing
235
+
236
+ For projects with separate server and client directories (or monorepos), use `auditPaths` to scan multiple directories:
237
+
238
+ ```typescript
239
+ const monitor = new MonitorClient({
240
+ apiKey: process.env.CEON_MONITOR_API_KEY!,
241
+ endpoint: 'https://monitor.example.com',
242
+ autoAudit: true,
243
+ auditPaths: [
244
+ { path: '.', environment: 'server' },
245
+ { path: '../client', environment: 'client' },
246
+ ],
247
+ // Also track dependencies from both
248
+ trackDependencies: true,
249
+ dependencySources: [
250
+ { path: './package.json', environment: 'server' },
251
+ { path: '../client/package.json', environment: 'client' },
252
+ ],
253
+ });
254
+ ```
255
+
256
+ With `auditPaths` configured:
257
+ - Each directory is scanned independently using `npm audit`
258
+ - Results are tagged with the environment label (e.g., 'server', 'client')
259
+ - All vulnerabilities appear in a single project dashboard
260
+ - Use the environment filter in the dashboard to view specific environments
261
+
262
+ You can also manually trigger a multi-directory audit:
263
+
264
+ ```typescript
265
+ const result = await monitor.auditMultiplePaths();
266
+ if (result) {
267
+ console.log('Audit results by environment:');
268
+ for (const env of result.results) {
269
+ console.log(` ${env.environment}: ${env.processed} vulnerabilities`);
270
+ }
271
+ console.log('Total:', result.totalSummary);
272
+ }
273
+ ```
274
+
230
275
  #### Manual Scheduled Auditing
231
276
 
232
277
  If you prefer manual control over scheduling:
@@ -725,6 +770,10 @@ Checks for brute force attacks.
725
770
 
726
771
  Runs npm audit and sends results to the server.
727
772
 
773
+ #### `auditMultiplePaths(): Promise<MultiAuditSummary | null>`
774
+
775
+ Runs npm audit on all directories configured in `auditPaths` and returns a combined summary.
776
+
728
777
  #### `flush(): Promise<void>`
729
778
 
730
779
  Immediately sends all queued errors.
@@ -787,6 +836,22 @@ interface SecurityEvent {
787
836
  identifier?: string;
788
837
  metadata?: Record<string, unknown>;
789
838
  }
839
+
840
+ interface AuditPath {
841
+ path: string; // Directory path (e.g., '.', '../client')
842
+ environment: string; // Environment label (e.g., 'server', 'client')
843
+ }
844
+
845
+ interface MultiAuditSummary {
846
+ results: Array<{
847
+ environment: string;
848
+ scanId: string;
849
+ processed: number;
850
+ resolved: number;
851
+ summary: { critical: number; high: number; moderate: number; low: number; info: number };
852
+ }>;
853
+ totalSummary: { critical: number; high: number; moderate: number; low: number; info: number };
854
+ }
790
855
  ```
791
856
 
792
857
  ## License
package/dist/index.d.mts CHANGED
@@ -6,6 +6,12 @@ interface DependencySource {
6
6
  path: string;
7
7
  environment: string;
8
8
  }
9
+ interface AuditPath {
10
+ /** Directory path to run npm audit in (e.g., '.', '../client') */
11
+ path: string;
12
+ /** Environment label for vulnerabilities from this path (e.g., 'server', 'client') */
13
+ environment: string;
14
+ }
9
15
  interface MonitorClientConfig {
10
16
  /** API key for authentication (required, format: cm_xxx) */
11
17
  apiKey: string;
@@ -33,6 +39,8 @@ interface MonitorClientConfig {
33
39
  requestTimeoutMs?: number;
34
40
  /** Enable automatic vulnerability scanning based on server-configured interval (default: false) */
35
41
  autoAudit?: boolean;
42
+ /** Multiple directories to audit for vulnerabilities (runs npm audit in each) */
43
+ auditPaths?: AuditPath[];
36
44
  }
37
45
  interface TechnologyItem {
38
46
  name: string;
@@ -118,6 +126,28 @@ interface AuditSummary {
118
126
  info: number;
119
127
  };
120
128
  }
129
+ interface MultiAuditSummary {
130
+ results: Array<{
131
+ environment: string;
132
+ scanId: string;
133
+ processed: number;
134
+ resolved: number;
135
+ summary: {
136
+ critical: number;
137
+ high: number;
138
+ moderate: number;
139
+ low: number;
140
+ info: number;
141
+ };
142
+ }>;
143
+ totalSummary: {
144
+ critical: number;
145
+ high: number;
146
+ moderate: number;
147
+ low: number;
148
+ info: number;
149
+ };
150
+ }
121
151
 
122
152
  declare class MonitorClient {
123
153
  private apiKey;
@@ -132,6 +162,8 @@ declare class MonitorClient {
132
162
  private packageJsonPath?;
133
163
  private dependencySources?;
134
164
  private excludePatterns;
165
+ private compiledExcludePatterns;
166
+ private auditPaths?;
135
167
  private maxQueueSize;
136
168
  private maxRetries;
137
169
  private retryCount;
@@ -144,6 +176,15 @@ declare class MonitorClient {
144
176
  private lastKnownScanRequestedAt;
145
177
  private lastKnownTechScanRequestedAt;
146
178
  constructor(config: MonitorClientConfig);
179
+ /**
180
+ * Security: Validate and sanitize metadata to prevent oversized payloads
181
+ */
182
+ private sanitizeMetadata;
183
+ /**
184
+ * Security: Sanitize error response text to prevent sensitive data exposure
185
+ * Removes potential API keys, tokens, and limits response length
186
+ */
187
+ private sanitizeErrorResponse;
147
188
  captureError(error: Error, context?: ErrorContext): Promise<void>;
148
189
  captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>;
149
190
  flush(): Promise<void>;
@@ -168,6 +209,7 @@ declare class MonitorClient {
168
209
  private setupAutoAudit;
169
210
  /**
170
211
  * Run a vulnerability scan and track the time it was run.
212
+ * Uses auditMultiplePaths() if auditPaths is configured, otherwise runs single audit.
171
213
  */
172
214
  private runScanAndTrackTime;
173
215
  /**
@@ -263,6 +305,16 @@ declare class MonitorClient {
263
305
  projectPath?: string;
264
306
  environment?: string;
265
307
  }): Promise<AuditSummary | null>;
308
+ /**
309
+ * Run npm audit on multiple directories and send results to the monitoring server.
310
+ * This is useful for monorepos or projects with separate client/server directories.
311
+ *
312
+ * Uses the auditPaths configuration to determine which directories to scan.
313
+ * Each path is scanned independently and results are tagged with their environment label.
314
+ *
315
+ * @returns Combined summary of all audit results
316
+ */
317
+ auditMultiplePaths(): Promise<MultiAuditSummary | null>;
266
318
  /**
267
319
  * Parse npm audit JSON output into vulnerability items
268
320
  */
@@ -271,4 +323,4 @@ declare class MonitorClient {
271
323
  private getRecommendation;
272
324
  }
273
325
 
274
- export { type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, MonitorClient, type MonitorClientConfig, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
326
+ export { type AuditPath, type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, MonitorClient, type MonitorClientConfig, type MultiAuditSummary, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,12 @@ interface DependencySource {
6
6
  path: string;
7
7
  environment: string;
8
8
  }
9
+ interface AuditPath {
10
+ /** Directory path to run npm audit in (e.g., '.', '../client') */
11
+ path: string;
12
+ /** Environment label for vulnerabilities from this path (e.g., 'server', 'client') */
13
+ environment: string;
14
+ }
9
15
  interface MonitorClientConfig {
10
16
  /** API key for authentication (required, format: cm_xxx) */
11
17
  apiKey: string;
@@ -33,6 +39,8 @@ interface MonitorClientConfig {
33
39
  requestTimeoutMs?: number;
34
40
  /** Enable automatic vulnerability scanning based on server-configured interval (default: false) */
35
41
  autoAudit?: boolean;
42
+ /** Multiple directories to audit for vulnerabilities (runs npm audit in each) */
43
+ auditPaths?: AuditPath[];
36
44
  }
37
45
  interface TechnologyItem {
38
46
  name: string;
@@ -118,6 +126,28 @@ interface AuditSummary {
118
126
  info: number;
119
127
  };
120
128
  }
129
+ interface MultiAuditSummary {
130
+ results: Array<{
131
+ environment: string;
132
+ scanId: string;
133
+ processed: number;
134
+ resolved: number;
135
+ summary: {
136
+ critical: number;
137
+ high: number;
138
+ moderate: number;
139
+ low: number;
140
+ info: number;
141
+ };
142
+ }>;
143
+ totalSummary: {
144
+ critical: number;
145
+ high: number;
146
+ moderate: number;
147
+ low: number;
148
+ info: number;
149
+ };
150
+ }
121
151
 
122
152
  declare class MonitorClient {
123
153
  private apiKey;
@@ -132,6 +162,8 @@ declare class MonitorClient {
132
162
  private packageJsonPath?;
133
163
  private dependencySources?;
134
164
  private excludePatterns;
165
+ private compiledExcludePatterns;
166
+ private auditPaths?;
135
167
  private maxQueueSize;
136
168
  private maxRetries;
137
169
  private retryCount;
@@ -144,6 +176,15 @@ declare class MonitorClient {
144
176
  private lastKnownScanRequestedAt;
145
177
  private lastKnownTechScanRequestedAt;
146
178
  constructor(config: MonitorClientConfig);
179
+ /**
180
+ * Security: Validate and sanitize metadata to prevent oversized payloads
181
+ */
182
+ private sanitizeMetadata;
183
+ /**
184
+ * Security: Sanitize error response text to prevent sensitive data exposure
185
+ * Removes potential API keys, tokens, and limits response length
186
+ */
187
+ private sanitizeErrorResponse;
147
188
  captureError(error: Error, context?: ErrorContext): Promise<void>;
148
189
  captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>;
149
190
  flush(): Promise<void>;
@@ -168,6 +209,7 @@ declare class MonitorClient {
168
209
  private setupAutoAudit;
169
210
  /**
170
211
  * Run a vulnerability scan and track the time it was run.
212
+ * Uses auditMultiplePaths() if auditPaths is configured, otherwise runs single audit.
171
213
  */
172
214
  private runScanAndTrackTime;
173
215
  /**
@@ -263,6 +305,16 @@ declare class MonitorClient {
263
305
  projectPath?: string;
264
306
  environment?: string;
265
307
  }): Promise<AuditSummary | null>;
308
+ /**
309
+ * Run npm audit on multiple directories and send results to the monitoring server.
310
+ * This is useful for monorepos or projects with separate client/server directories.
311
+ *
312
+ * Uses the auditPaths configuration to determine which directories to scan.
313
+ * Each path is scanned independently and results are tagged with their environment label.
314
+ *
315
+ * @returns Combined summary of all audit results
316
+ */
317
+ auditMultiplePaths(): Promise<MultiAuditSummary | null>;
266
318
  /**
267
319
  * Parse npm audit JSON output into vulnerability items
268
320
  */
@@ -271,4 +323,4 @@ declare class MonitorClient {
271
323
  private getRecommendation;
272
324
  }
273
325
 
274
- export { type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, MonitorClient, type MonitorClientConfig, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
326
+ export { type AuditPath, type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, MonitorClient, type MonitorClientConfig, type MultiAuditSummary, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
package/dist/index.js CHANGED
@@ -35,6 +35,25 @@ __export(index_exports, {
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // src/MonitorClient.ts
38
+ var CONFIG_LIMITS = {
39
+ MAX_BATCH_SIZE: 1e3,
40
+ MAX_FLUSH_INTERVAL_MS: 3e5,
41
+ // 5 minutes
42
+ MAX_QUEUE_SIZE: 1e4,
43
+ MAX_RETRIES: 10,
44
+ MAX_REQUEST_TIMEOUT_MS: 6e4,
45
+ // 1 minute
46
+ MAX_METADATA_SIZE_BYTES: 65536,
47
+ // 64KB
48
+ MAX_RETRY_MAP_SIZE: 1e3,
49
+ // Prevent unbounded growth
50
+ AUDIT_MAX_BUFFER: 10 * 1024 * 1024,
51
+ // 10MB
52
+ AUDIT_TIMEOUT_MS: 6e4,
53
+ // 60 seconds
54
+ SETTINGS_POLL_INTERVAL_MS: 5 * 60 * 1e3
55
+ // 5 minutes
56
+ };
38
57
  var MonitorClient = class {
39
58
  constructor(config) {
40
59
  this.queue = [];
@@ -73,14 +92,14 @@ var MonitorClient = class {
73
92
  this.apiKey = config.apiKey;
74
93
  this.endpoint = config.endpoint.replace(/\/$/, "");
75
94
  this.environment = config.environment || "production";
76
- this.batchSize = Math.max(1, config.batchSize || 10);
77
- this.flushIntervalMs = Math.max(1e3, config.flushIntervalMs || 5e3);
95
+ this.batchSize = Math.min(CONFIG_LIMITS.MAX_BATCH_SIZE, Math.max(1, config.batchSize || 10));
96
+ this.flushIntervalMs = Math.min(CONFIG_LIMITS.MAX_FLUSH_INTERVAL_MS, Math.max(1e3, config.flushIntervalMs || 5e3));
78
97
  this.trackDependencies = config.trackDependencies || false;
79
98
  this.packageJsonPath = config.packageJsonPath;
80
99
  this.dependencySources = config.dependencySources;
81
- this.maxQueueSize = config.maxQueueSize || 1e3;
82
- this.maxRetries = config.maxRetries || 3;
83
- this.requestTimeoutMs = config.requestTimeoutMs || 1e4;
100
+ this.maxQueueSize = Math.min(CONFIG_LIMITS.MAX_QUEUE_SIZE, config.maxQueueSize || 1e3);
101
+ this.maxRetries = Math.min(CONFIG_LIMITS.MAX_RETRIES, config.maxRetries || 3);
102
+ this.requestTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REQUEST_TIMEOUT_MS, config.requestTimeoutMs || 1e4);
84
103
  const defaultExcludePatterns = [
85
104
  "@types/*",
86
105
  "eslint*",
@@ -91,7 +110,12 @@ var MonitorClient = class {
91
110
  "@typescript-eslint/*"
92
111
  ];
93
112
  this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
113
+ this.compiledExcludePatterns = this.excludePatterns.map((pattern) => {
114
+ const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
115
+ return new RegExp(`^${regexPattern}$`);
116
+ });
94
117
  this.autoAudit = config.autoAudit || false;
118
+ this.auditPaths = config.auditPaths;
95
119
  this.startFlushTimer();
96
120
  if (this.trackDependencies) {
97
121
  this.syncDependencies().catch((err) => {
@@ -104,6 +128,36 @@ var MonitorClient = class {
104
128
  });
105
129
  }
106
130
  }
131
+ /**
132
+ * Security: Validate and sanitize metadata to prevent oversized payloads
133
+ */
134
+ sanitizeMetadata(metadata) {
135
+ if (!metadata) return void 0;
136
+ try {
137
+ const jsonStr = JSON.stringify(metadata);
138
+ if (jsonStr.length > CONFIG_LIMITS.MAX_METADATA_SIZE_BYTES) {
139
+ console.warn(`[MonitorClient] Metadata exceeds size limit (${CONFIG_LIMITS.MAX_METADATA_SIZE_BYTES} bytes), truncating`);
140
+ return { _truncated: true, _originalSize: jsonStr.length };
141
+ }
142
+ return metadata;
143
+ } catch {
144
+ console.warn("[MonitorClient] Metadata contains non-serializable data, skipping");
145
+ return { _error: "non-serializable" };
146
+ }
147
+ }
148
+ /**
149
+ * Security: Sanitize error response text to prevent sensitive data exposure
150
+ * Removes potential API keys, tokens, and limits response length
151
+ */
152
+ sanitizeErrorResponse(errorText) {
153
+ if (!errorText) return "";
154
+ const maxLength = 500;
155
+ let sanitized = errorText.length > maxLength ? errorText.substring(0, maxLength) + "..." : errorText;
156
+ sanitized = sanitized.replace(/cm_[a-zA-Z0-9_-]+/g, "[REDACTED]");
157
+ sanitized = sanitized.replace(/Bearer\s+[a-zA-Z0-9_.-]+/gi, "Bearer [REDACTED]");
158
+ sanitized = sanitized.replace(/authorization['":\s]+[a-zA-Z0-9_.-]+/gi, "authorization: [REDACTED]");
159
+ return sanitized;
160
+ }
107
161
  async captureError(error, context) {
108
162
  if (this.isClosed) return;
109
163
  const payload = {
@@ -117,7 +171,7 @@ var MonitorClient = class {
117
171
  userAgent: context?.userAgent,
118
172
  ip: context?.ip,
119
173
  requestId: context?.requestId,
120
- metadata: context?.metadata
174
+ metadata: this.sanitizeMetadata(context?.metadata)
121
175
  };
122
176
  this.enqueue(payload);
123
177
  }
@@ -133,7 +187,7 @@ var MonitorClient = class {
133
187
  userAgent: context?.userAgent,
134
188
  ip: context?.ip,
135
189
  requestId: context?.requestId,
136
- metadata: context?.metadata
190
+ metadata: this.sanitizeMetadata(context?.metadata)
137
191
  };
138
192
  this.enqueue(payload);
139
193
  }
@@ -157,6 +211,12 @@ var MonitorClient = class {
157
211
  const key = this.getErrorKey(error);
158
212
  const retries = this.retryCount.get(key) || 0;
159
213
  if (retries < this.maxRetries) {
214
+ if (this.retryCount.size >= CONFIG_LIMITS.MAX_RETRY_MAP_SIZE) {
215
+ const keysToRemove = Array.from(this.retryCount.keys()).slice(0, Math.ceil(CONFIG_LIMITS.MAX_RETRY_MAP_SIZE * 0.1));
216
+ for (const oldKey of keysToRemove) {
217
+ this.retryCount.delete(oldKey);
218
+ }
219
+ }
160
220
  this.retryCount.set(key, retries + 1);
161
221
  if (this.queue.length < this.maxQueueSize) {
162
222
  this.queue.push(error);
@@ -241,20 +301,29 @@ var MonitorClient = class {
241
301
  await this.runScanAndTrackTime();
242
302
  const intervalMs = intervalHours * 60 * 60 * 1e3;
243
303
  this.auditIntervalTimer = setInterval(() => {
244
- this.runScanAndTrackTime();
304
+ this.runScanAndTrackTime().catch((err) => {
305
+ console.error("[MonitorClient] Auto audit scan failed:", err);
306
+ });
245
307
  }, intervalMs);
246
308
  }
247
309
  console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
248
310
  this.settingsPollingTimer = setInterval(() => {
249
- this.checkForScanRequest();
250
- }, 5 * 60 * 1e3);
311
+ this.checkForScanRequest().catch((err) => {
312
+ console.error("[MonitorClient] Scan request check failed:", err);
313
+ });
314
+ }, CONFIG_LIMITS.SETTINGS_POLL_INTERVAL_MS);
251
315
  }
252
316
  /**
253
317
  * Run a vulnerability scan and track the time it was run.
318
+ * Uses auditMultiplePaths() if auditPaths is configured, otherwise runs single audit.
254
319
  */
255
320
  async runScanAndTrackTime() {
256
321
  try {
257
- await this.auditDependencies();
322
+ if (this.auditPaths && this.auditPaths.length > 0) {
323
+ await this.auditMultiplePaths();
324
+ } else {
325
+ await this.auditDependencies();
326
+ }
258
327
  this.lastScanTime = /* @__PURE__ */ new Date();
259
328
  } catch (err) {
260
329
  console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
@@ -294,7 +363,9 @@ var MonitorClient = class {
294
363
  }
295
364
  this.queue.push(payload);
296
365
  if (this.queue.length >= this.batchSize) {
297
- this.flush();
366
+ this.flush().catch((err) => {
367
+ console.error("[MonitorClient] Flush failed:", err);
368
+ });
298
369
  }
299
370
  }
300
371
  /**
@@ -329,7 +400,7 @@ var MonitorClient = class {
329
400
  });
330
401
  if (!response.ok) {
331
402
  const errorText = await response.text().catch(() => "");
332
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
403
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
333
404
  }
334
405
  }
335
406
  async sendBatch(errors) {
@@ -343,12 +414,14 @@ var MonitorClient = class {
343
414
  });
344
415
  if (!response.ok) {
345
416
  const errorText = await response.text().catch(() => "");
346
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
417
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
347
418
  }
348
419
  }
349
420
  startFlushTimer() {
350
421
  this.flushTimer = setInterval(() => {
351
- this.flush();
422
+ this.flush().catch((err) => {
423
+ console.error("[MonitorClient] Scheduled flush failed:", err);
424
+ });
352
425
  }, this.flushIntervalMs);
353
426
  }
354
427
  stopFlushTimer() {
@@ -393,14 +466,7 @@ var MonitorClient = class {
393
466
  const baseDir = process.cwd();
394
467
  const resolvedPath = path.isAbsolute(packagePath) ? packagePath : path.join(baseDir, packagePath);
395
468
  const normalizedPath = path.normalize(resolvedPath);
396
- const normalizedBase = path.normalize(baseDir);
397
- if (!path.isAbsolute(packagePath)) {
398
- if (!normalizedPath.startsWith(normalizedBase)) {
399
- console.warn("[MonitorClient] Path traversal attempt blocked:", packagePath);
400
- return [];
401
- }
402
- }
403
- if (packagePath.includes("\0") || /\.\.[\\/]/.test(packagePath)) {
469
+ if (packagePath.includes("\0")) {
404
470
  console.warn("[MonitorClient] Suspicious path blocked:", packagePath);
405
471
  return [];
406
472
  }
@@ -411,6 +477,15 @@ var MonitorClient = class {
411
477
  if (!fs.existsSync(normalizedPath)) {
412
478
  return [];
413
479
  }
480
+ try {
481
+ const realPath = fs.realpathSync(normalizedPath);
482
+ if (!realPath.endsWith("package.json")) {
483
+ console.warn("[MonitorClient] Symlink points to non-package.json file:", packagePath);
484
+ return [];
485
+ }
486
+ } catch {
487
+ return [];
488
+ }
414
489
  const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
415
490
  const technologies = [];
416
491
  const deps = {
@@ -434,9 +509,8 @@ var MonitorClient = class {
434
509
  }
435
510
  }
436
511
  shouldExclude(packageName) {
437
- for (const pattern of this.excludePatterns) {
438
- const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
439
- if (new RegExp(`^${regexPattern}$`).test(packageName)) {
512
+ for (const regex of this.compiledExcludePatterns) {
513
+ if (regex.test(packageName)) {
440
514
  return true;
441
515
  }
442
516
  }
@@ -459,7 +533,7 @@ var MonitorClient = class {
459
533
  });
460
534
  if (!response.ok) {
461
535
  const errorText = await response.text().catch(() => "");
462
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
536
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
463
537
  }
464
538
  }
465
539
  /**
@@ -470,6 +544,7 @@ var MonitorClient = class {
470
544
  if (this.isClosed) return {};
471
545
  const payload = {
472
546
  ...input,
547
+ metadata: this.sanitizeMetadata(input.metadata),
473
548
  environment: this.environment
474
549
  };
475
550
  const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/security`, {
@@ -482,7 +557,7 @@ var MonitorClient = class {
482
557
  });
483
558
  if (!response.ok) {
484
559
  const errorText = await response.text().catch(() => "");
485
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
560
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
486
561
  }
487
562
  const result = await response.json();
488
563
  return { warning: result.warning };
@@ -561,7 +636,7 @@ var MonitorClient = class {
561
636
  });
562
637
  if (!response.ok) {
563
638
  const errorText = await response.text().catch(() => "");
564
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
639
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
565
640
  }
566
641
  const result = await response.json();
567
642
  return result.data;
@@ -602,6 +677,12 @@ var MonitorClient = class {
602
677
  return null;
603
678
  }
604
679
  projectPath = path.resolve(projectPath);
680
+ try {
681
+ projectPath = fs.realpathSync(projectPath);
682
+ } catch {
683
+ console.error("[MonitorClient] projectPath does not exist or is inaccessible");
684
+ return null;
685
+ }
605
686
  try {
606
687
  const stats = fs.statSync(projectPath);
607
688
  if (!stats.isDirectory()) {
@@ -612,21 +693,19 @@ var MonitorClient = class {
612
693
  console.error("[MonitorClient] projectPath does not exist");
613
694
  return null;
614
695
  }
615
- const packageJsonPath = path.join(projectPath, "package.json");
616
- if (!fs.existsSync(packageJsonPath)) {
617
- console.error("[MonitorClient] No package.json found in projectPath");
618
- return null;
619
- }
620
696
  let auditOutput;
621
697
  try {
698
+ const packageJsonPath = path.join(projectPath, "package.json");
699
+ if (!fs.existsSync(packageJsonPath)) {
700
+ console.error("[MonitorClient] No package.json found in projectPath");
701
+ return null;
702
+ }
622
703
  auditOutput = execSync("npm audit --json", {
623
704
  cwd: projectPath,
624
705
  encoding: "utf-8",
625
706
  stdio: ["pipe", "pipe", "pipe"],
626
- maxBuffer: 10 * 1024 * 1024,
627
- // 10MB buffer for large outputs
628
- timeout: 6e4
629
- // 60 second timeout
707
+ maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
708
+ timeout: CONFIG_LIMITS.AUDIT_TIMEOUT_MS
630
709
  });
631
710
  } catch (err) {
632
711
  const execError = err;
@@ -668,6 +747,63 @@ var MonitorClient = class {
668
747
  return null;
669
748
  }
670
749
  }
750
+ /**
751
+ * Run npm audit on multiple directories and send results to the monitoring server.
752
+ * This is useful for monorepos or projects with separate client/server directories.
753
+ *
754
+ * Uses the auditPaths configuration to determine which directories to scan.
755
+ * Each path is scanned independently and results are tagged with their environment label.
756
+ *
757
+ * @returns Combined summary of all audit results
758
+ */
759
+ async auditMultiplePaths() {
760
+ if (!this.auditPaths || this.auditPaths.length === 0) {
761
+ console.warn("[MonitorClient] No auditPaths configured");
762
+ return null;
763
+ }
764
+ const results = [];
765
+ const totalSummary = {
766
+ critical: 0,
767
+ high: 0,
768
+ moderate: 0,
769
+ low: 0,
770
+ info: 0
771
+ };
772
+ for (const auditPath of this.auditPaths) {
773
+ console.log(`[MonitorClient] Auditing ${auditPath.environment} at ${auditPath.path}`);
774
+ try {
775
+ const summary = await this.auditDependencies({
776
+ projectPath: auditPath.path,
777
+ environment: auditPath.environment
778
+ });
779
+ if (summary) {
780
+ results.push({
781
+ environment: auditPath.environment,
782
+ scanId: summary.scanId,
783
+ processed: summary.processed,
784
+ resolved: summary.resolved,
785
+ summary: summary.summary
786
+ });
787
+ totalSummary.critical += summary.summary.critical;
788
+ totalSummary.high += summary.summary.high;
789
+ totalSummary.moderate += summary.summary.moderate;
790
+ totalSummary.low += summary.summary.low;
791
+ totalSummary.info += summary.summary.info;
792
+ }
793
+ } catch (err) {
794
+ console.error(`[MonitorClient] Failed to audit ${auditPath.environment}:`, err instanceof Error ? err.message : String(err));
795
+ }
796
+ }
797
+ if (results.length === 0) {
798
+ console.warn("[MonitorClient] No audit results collected from any path");
799
+ return null;
800
+ }
801
+ console.log(`[MonitorClient] Multi-path audit complete: ${results.length} paths scanned`);
802
+ return {
803
+ results,
804
+ totalSummary
805
+ };
806
+ }
671
807
  /**
672
808
  * Parse npm audit JSON output into vulnerability items
673
809
  */
@@ -677,6 +813,9 @@ var MonitorClient = class {
677
813
  return vulnerabilities;
678
814
  }
679
815
  for (const [packageName, vuln] of Object.entries(auditData.vulnerabilities)) {
816
+ if (!vuln.via || !Array.isArray(vuln.via)) {
817
+ continue;
818
+ }
680
819
  const viaDetails = vuln.via.find(
681
820
  (v) => typeof v === "object" && "title" in v
682
821
  );
package/dist/index.mjs CHANGED
@@ -1,4 +1,23 @@
1
1
  // src/MonitorClient.ts
2
+ var CONFIG_LIMITS = {
3
+ MAX_BATCH_SIZE: 1e3,
4
+ MAX_FLUSH_INTERVAL_MS: 3e5,
5
+ // 5 minutes
6
+ MAX_QUEUE_SIZE: 1e4,
7
+ MAX_RETRIES: 10,
8
+ MAX_REQUEST_TIMEOUT_MS: 6e4,
9
+ // 1 minute
10
+ MAX_METADATA_SIZE_BYTES: 65536,
11
+ // 64KB
12
+ MAX_RETRY_MAP_SIZE: 1e3,
13
+ // Prevent unbounded growth
14
+ AUDIT_MAX_BUFFER: 10 * 1024 * 1024,
15
+ // 10MB
16
+ AUDIT_TIMEOUT_MS: 6e4,
17
+ // 60 seconds
18
+ SETTINGS_POLL_INTERVAL_MS: 5 * 60 * 1e3
19
+ // 5 minutes
20
+ };
2
21
  var MonitorClient = class {
3
22
  constructor(config) {
4
23
  this.queue = [];
@@ -37,14 +56,14 @@ var MonitorClient = class {
37
56
  this.apiKey = config.apiKey;
38
57
  this.endpoint = config.endpoint.replace(/\/$/, "");
39
58
  this.environment = config.environment || "production";
40
- this.batchSize = Math.max(1, config.batchSize || 10);
41
- this.flushIntervalMs = Math.max(1e3, config.flushIntervalMs || 5e3);
59
+ this.batchSize = Math.min(CONFIG_LIMITS.MAX_BATCH_SIZE, Math.max(1, config.batchSize || 10));
60
+ this.flushIntervalMs = Math.min(CONFIG_LIMITS.MAX_FLUSH_INTERVAL_MS, Math.max(1e3, config.flushIntervalMs || 5e3));
42
61
  this.trackDependencies = config.trackDependencies || false;
43
62
  this.packageJsonPath = config.packageJsonPath;
44
63
  this.dependencySources = config.dependencySources;
45
- this.maxQueueSize = config.maxQueueSize || 1e3;
46
- this.maxRetries = config.maxRetries || 3;
47
- this.requestTimeoutMs = config.requestTimeoutMs || 1e4;
64
+ this.maxQueueSize = Math.min(CONFIG_LIMITS.MAX_QUEUE_SIZE, config.maxQueueSize || 1e3);
65
+ this.maxRetries = Math.min(CONFIG_LIMITS.MAX_RETRIES, config.maxRetries || 3);
66
+ this.requestTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REQUEST_TIMEOUT_MS, config.requestTimeoutMs || 1e4);
48
67
  const defaultExcludePatterns = [
49
68
  "@types/*",
50
69
  "eslint*",
@@ -55,7 +74,12 @@ var MonitorClient = class {
55
74
  "@typescript-eslint/*"
56
75
  ];
57
76
  this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
77
+ this.compiledExcludePatterns = this.excludePatterns.map((pattern) => {
78
+ const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
79
+ return new RegExp(`^${regexPattern}$`);
80
+ });
58
81
  this.autoAudit = config.autoAudit || false;
82
+ this.auditPaths = config.auditPaths;
59
83
  this.startFlushTimer();
60
84
  if (this.trackDependencies) {
61
85
  this.syncDependencies().catch((err) => {
@@ -68,6 +92,36 @@ var MonitorClient = class {
68
92
  });
69
93
  }
70
94
  }
95
+ /**
96
+ * Security: Validate and sanitize metadata to prevent oversized payloads
97
+ */
98
+ sanitizeMetadata(metadata) {
99
+ if (!metadata) return void 0;
100
+ try {
101
+ const jsonStr = JSON.stringify(metadata);
102
+ if (jsonStr.length > CONFIG_LIMITS.MAX_METADATA_SIZE_BYTES) {
103
+ console.warn(`[MonitorClient] Metadata exceeds size limit (${CONFIG_LIMITS.MAX_METADATA_SIZE_BYTES} bytes), truncating`);
104
+ return { _truncated: true, _originalSize: jsonStr.length };
105
+ }
106
+ return metadata;
107
+ } catch {
108
+ console.warn("[MonitorClient] Metadata contains non-serializable data, skipping");
109
+ return { _error: "non-serializable" };
110
+ }
111
+ }
112
+ /**
113
+ * Security: Sanitize error response text to prevent sensitive data exposure
114
+ * Removes potential API keys, tokens, and limits response length
115
+ */
116
+ sanitizeErrorResponse(errorText) {
117
+ if (!errorText) return "";
118
+ const maxLength = 500;
119
+ let sanitized = errorText.length > maxLength ? errorText.substring(0, maxLength) + "..." : errorText;
120
+ sanitized = sanitized.replace(/cm_[a-zA-Z0-9_-]+/g, "[REDACTED]");
121
+ sanitized = sanitized.replace(/Bearer\s+[a-zA-Z0-9_.-]+/gi, "Bearer [REDACTED]");
122
+ sanitized = sanitized.replace(/authorization['":\s]+[a-zA-Z0-9_.-]+/gi, "authorization: [REDACTED]");
123
+ return sanitized;
124
+ }
71
125
  async captureError(error, context) {
72
126
  if (this.isClosed) return;
73
127
  const payload = {
@@ -81,7 +135,7 @@ var MonitorClient = class {
81
135
  userAgent: context?.userAgent,
82
136
  ip: context?.ip,
83
137
  requestId: context?.requestId,
84
- metadata: context?.metadata
138
+ metadata: this.sanitizeMetadata(context?.metadata)
85
139
  };
86
140
  this.enqueue(payload);
87
141
  }
@@ -97,7 +151,7 @@ var MonitorClient = class {
97
151
  userAgent: context?.userAgent,
98
152
  ip: context?.ip,
99
153
  requestId: context?.requestId,
100
- metadata: context?.metadata
154
+ metadata: this.sanitizeMetadata(context?.metadata)
101
155
  };
102
156
  this.enqueue(payload);
103
157
  }
@@ -121,6 +175,12 @@ var MonitorClient = class {
121
175
  const key = this.getErrorKey(error);
122
176
  const retries = this.retryCount.get(key) || 0;
123
177
  if (retries < this.maxRetries) {
178
+ if (this.retryCount.size >= CONFIG_LIMITS.MAX_RETRY_MAP_SIZE) {
179
+ const keysToRemove = Array.from(this.retryCount.keys()).slice(0, Math.ceil(CONFIG_LIMITS.MAX_RETRY_MAP_SIZE * 0.1));
180
+ for (const oldKey of keysToRemove) {
181
+ this.retryCount.delete(oldKey);
182
+ }
183
+ }
124
184
  this.retryCount.set(key, retries + 1);
125
185
  if (this.queue.length < this.maxQueueSize) {
126
186
  this.queue.push(error);
@@ -205,20 +265,29 @@ var MonitorClient = class {
205
265
  await this.runScanAndTrackTime();
206
266
  const intervalMs = intervalHours * 60 * 60 * 1e3;
207
267
  this.auditIntervalTimer = setInterval(() => {
208
- this.runScanAndTrackTime();
268
+ this.runScanAndTrackTime().catch((err) => {
269
+ console.error("[MonitorClient] Auto audit scan failed:", err);
270
+ });
209
271
  }, intervalMs);
210
272
  }
211
273
  console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
212
274
  this.settingsPollingTimer = setInterval(() => {
213
- this.checkForScanRequest();
214
- }, 5 * 60 * 1e3);
275
+ this.checkForScanRequest().catch((err) => {
276
+ console.error("[MonitorClient] Scan request check failed:", err);
277
+ });
278
+ }, CONFIG_LIMITS.SETTINGS_POLL_INTERVAL_MS);
215
279
  }
216
280
  /**
217
281
  * Run a vulnerability scan and track the time it was run.
282
+ * Uses auditMultiplePaths() if auditPaths is configured, otherwise runs single audit.
218
283
  */
219
284
  async runScanAndTrackTime() {
220
285
  try {
221
- await this.auditDependencies();
286
+ if (this.auditPaths && this.auditPaths.length > 0) {
287
+ await this.auditMultiplePaths();
288
+ } else {
289
+ await this.auditDependencies();
290
+ }
222
291
  this.lastScanTime = /* @__PURE__ */ new Date();
223
292
  } catch (err) {
224
293
  console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
@@ -258,7 +327,9 @@ var MonitorClient = class {
258
327
  }
259
328
  this.queue.push(payload);
260
329
  if (this.queue.length >= this.batchSize) {
261
- this.flush();
330
+ this.flush().catch((err) => {
331
+ console.error("[MonitorClient] Flush failed:", err);
332
+ });
262
333
  }
263
334
  }
264
335
  /**
@@ -293,7 +364,7 @@ var MonitorClient = class {
293
364
  });
294
365
  if (!response.ok) {
295
366
  const errorText = await response.text().catch(() => "");
296
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
367
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
297
368
  }
298
369
  }
299
370
  async sendBatch(errors) {
@@ -307,12 +378,14 @@ var MonitorClient = class {
307
378
  });
308
379
  if (!response.ok) {
309
380
  const errorText = await response.text().catch(() => "");
310
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
381
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
311
382
  }
312
383
  }
313
384
  startFlushTimer() {
314
385
  this.flushTimer = setInterval(() => {
315
- this.flush();
386
+ this.flush().catch((err) => {
387
+ console.error("[MonitorClient] Scheduled flush failed:", err);
388
+ });
316
389
  }, this.flushIntervalMs);
317
390
  }
318
391
  stopFlushTimer() {
@@ -357,14 +430,7 @@ var MonitorClient = class {
357
430
  const baseDir = process.cwd();
358
431
  const resolvedPath = path.isAbsolute(packagePath) ? packagePath : path.join(baseDir, packagePath);
359
432
  const normalizedPath = path.normalize(resolvedPath);
360
- const normalizedBase = path.normalize(baseDir);
361
- if (!path.isAbsolute(packagePath)) {
362
- if (!normalizedPath.startsWith(normalizedBase)) {
363
- console.warn("[MonitorClient] Path traversal attempt blocked:", packagePath);
364
- return [];
365
- }
366
- }
367
- if (packagePath.includes("\0") || /\.\.[\\/]/.test(packagePath)) {
433
+ if (packagePath.includes("\0")) {
368
434
  console.warn("[MonitorClient] Suspicious path blocked:", packagePath);
369
435
  return [];
370
436
  }
@@ -375,6 +441,15 @@ var MonitorClient = class {
375
441
  if (!fs.existsSync(normalizedPath)) {
376
442
  return [];
377
443
  }
444
+ try {
445
+ const realPath = fs.realpathSync(normalizedPath);
446
+ if (!realPath.endsWith("package.json")) {
447
+ console.warn("[MonitorClient] Symlink points to non-package.json file:", packagePath);
448
+ return [];
449
+ }
450
+ } catch {
451
+ return [];
452
+ }
378
453
  const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
379
454
  const technologies = [];
380
455
  const deps = {
@@ -398,9 +473,8 @@ var MonitorClient = class {
398
473
  }
399
474
  }
400
475
  shouldExclude(packageName) {
401
- for (const pattern of this.excludePatterns) {
402
- const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
403
- if (new RegExp(`^${regexPattern}$`).test(packageName)) {
476
+ for (const regex of this.compiledExcludePatterns) {
477
+ if (regex.test(packageName)) {
404
478
  return true;
405
479
  }
406
480
  }
@@ -423,7 +497,7 @@ var MonitorClient = class {
423
497
  });
424
498
  if (!response.ok) {
425
499
  const errorText = await response.text().catch(() => "");
426
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
500
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
427
501
  }
428
502
  }
429
503
  /**
@@ -434,6 +508,7 @@ var MonitorClient = class {
434
508
  if (this.isClosed) return {};
435
509
  const payload = {
436
510
  ...input,
511
+ metadata: this.sanitizeMetadata(input.metadata),
437
512
  environment: this.environment
438
513
  };
439
514
  const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/security`, {
@@ -446,7 +521,7 @@ var MonitorClient = class {
446
521
  });
447
522
  if (!response.ok) {
448
523
  const errorText = await response.text().catch(() => "");
449
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
524
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
450
525
  }
451
526
  const result = await response.json();
452
527
  return { warning: result.warning };
@@ -525,7 +600,7 @@ var MonitorClient = class {
525
600
  });
526
601
  if (!response.ok) {
527
602
  const errorText = await response.text().catch(() => "");
528
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
603
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
529
604
  }
530
605
  const result = await response.json();
531
606
  return result.data;
@@ -566,6 +641,12 @@ var MonitorClient = class {
566
641
  return null;
567
642
  }
568
643
  projectPath = path.resolve(projectPath);
644
+ try {
645
+ projectPath = fs.realpathSync(projectPath);
646
+ } catch {
647
+ console.error("[MonitorClient] projectPath does not exist or is inaccessible");
648
+ return null;
649
+ }
569
650
  try {
570
651
  const stats = fs.statSync(projectPath);
571
652
  if (!stats.isDirectory()) {
@@ -576,21 +657,19 @@ var MonitorClient = class {
576
657
  console.error("[MonitorClient] projectPath does not exist");
577
658
  return null;
578
659
  }
579
- const packageJsonPath = path.join(projectPath, "package.json");
580
- if (!fs.existsSync(packageJsonPath)) {
581
- console.error("[MonitorClient] No package.json found in projectPath");
582
- return null;
583
- }
584
660
  let auditOutput;
585
661
  try {
662
+ const packageJsonPath = path.join(projectPath, "package.json");
663
+ if (!fs.existsSync(packageJsonPath)) {
664
+ console.error("[MonitorClient] No package.json found in projectPath");
665
+ return null;
666
+ }
586
667
  auditOutput = execSync("npm audit --json", {
587
668
  cwd: projectPath,
588
669
  encoding: "utf-8",
589
670
  stdio: ["pipe", "pipe", "pipe"],
590
- maxBuffer: 10 * 1024 * 1024,
591
- // 10MB buffer for large outputs
592
- timeout: 6e4
593
- // 60 second timeout
671
+ maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
672
+ timeout: CONFIG_LIMITS.AUDIT_TIMEOUT_MS
594
673
  });
595
674
  } catch (err) {
596
675
  const execError = err;
@@ -632,6 +711,63 @@ var MonitorClient = class {
632
711
  return null;
633
712
  }
634
713
  }
714
+ /**
715
+ * Run npm audit on multiple directories and send results to the monitoring server.
716
+ * This is useful for monorepos or projects with separate client/server directories.
717
+ *
718
+ * Uses the auditPaths configuration to determine which directories to scan.
719
+ * Each path is scanned independently and results are tagged with their environment label.
720
+ *
721
+ * @returns Combined summary of all audit results
722
+ */
723
+ async auditMultiplePaths() {
724
+ if (!this.auditPaths || this.auditPaths.length === 0) {
725
+ console.warn("[MonitorClient] No auditPaths configured");
726
+ return null;
727
+ }
728
+ const results = [];
729
+ const totalSummary = {
730
+ critical: 0,
731
+ high: 0,
732
+ moderate: 0,
733
+ low: 0,
734
+ info: 0
735
+ };
736
+ for (const auditPath of this.auditPaths) {
737
+ console.log(`[MonitorClient] Auditing ${auditPath.environment} at ${auditPath.path}`);
738
+ try {
739
+ const summary = await this.auditDependencies({
740
+ projectPath: auditPath.path,
741
+ environment: auditPath.environment
742
+ });
743
+ if (summary) {
744
+ results.push({
745
+ environment: auditPath.environment,
746
+ scanId: summary.scanId,
747
+ processed: summary.processed,
748
+ resolved: summary.resolved,
749
+ summary: summary.summary
750
+ });
751
+ totalSummary.critical += summary.summary.critical;
752
+ totalSummary.high += summary.summary.high;
753
+ totalSummary.moderate += summary.summary.moderate;
754
+ totalSummary.low += summary.summary.low;
755
+ totalSummary.info += summary.summary.info;
756
+ }
757
+ } catch (err) {
758
+ console.error(`[MonitorClient] Failed to audit ${auditPath.environment}:`, err instanceof Error ? err.message : String(err));
759
+ }
760
+ }
761
+ if (results.length === 0) {
762
+ console.warn("[MonitorClient] No audit results collected from any path");
763
+ return null;
764
+ }
765
+ console.log(`[MonitorClient] Multi-path audit complete: ${results.length} paths scanned`);
766
+ return {
767
+ results,
768
+ totalSummary
769
+ };
770
+ }
635
771
  /**
636
772
  * Parse npm audit JSON output into vulnerability items
637
773
  */
@@ -641,6 +777,9 @@ var MonitorClient = class {
641
777
  return vulnerabilities;
642
778
  }
643
779
  for (const [packageName, vuln] of Object.entries(auditData.vulnerabilities)) {
780
+ if (!vuln.via || !Array.isArray(vuln.via)) {
781
+ continue;
782
+ }
644
783
  const viaDetails = vuln.via.find(
645
784
  (v) => typeof v === "object" && "title" in v
646
785
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceon-oy/monitor-sdk",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Client SDK for Ceon Monitor - Error tracking, health monitoring, security events, and vulnerability scanning",
5
5
  "author": "Ceon",
6
6
  "license": "MIT",