@ceon-oy/monitor-sdk 1.0.9 → 1.0.10

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() {
@@ -411,6 +484,16 @@ var MonitorClient = class {
411
484
  if (!fs.existsSync(normalizedPath)) {
412
485
  return [];
413
486
  }
487
+ try {
488
+ const realPath = fs.realpathSync(normalizedPath);
489
+ const realBase = fs.realpathSync(normalizedBase);
490
+ if (!path.isAbsolute(packagePath) && !realPath.startsWith(realBase)) {
491
+ console.warn("[MonitorClient] Symlink points outside allowed directory:", packagePath);
492
+ return [];
493
+ }
494
+ } catch {
495
+ return [];
496
+ }
414
497
  const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
415
498
  const technologies = [];
416
499
  const deps = {
@@ -434,9 +517,8 @@ var MonitorClient = class {
434
517
  }
435
518
  }
436
519
  shouldExclude(packageName) {
437
- for (const pattern of this.excludePatterns) {
438
- const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
439
- if (new RegExp(`^${regexPattern}$`).test(packageName)) {
520
+ for (const regex of this.compiledExcludePatterns) {
521
+ if (regex.test(packageName)) {
440
522
  return true;
441
523
  }
442
524
  }
@@ -459,7 +541,7 @@ var MonitorClient = class {
459
541
  });
460
542
  if (!response.ok) {
461
543
  const errorText = await response.text().catch(() => "");
462
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
544
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
463
545
  }
464
546
  }
465
547
  /**
@@ -470,6 +552,7 @@ var MonitorClient = class {
470
552
  if (this.isClosed) return {};
471
553
  const payload = {
472
554
  ...input,
555
+ metadata: this.sanitizeMetadata(input.metadata),
473
556
  environment: this.environment
474
557
  };
475
558
  const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/security`, {
@@ -482,7 +565,7 @@ var MonitorClient = class {
482
565
  });
483
566
  if (!response.ok) {
484
567
  const errorText = await response.text().catch(() => "");
485
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
568
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
486
569
  }
487
570
  const result = await response.json();
488
571
  return { warning: result.warning };
@@ -561,7 +644,7 @@ var MonitorClient = class {
561
644
  });
562
645
  if (!response.ok) {
563
646
  const errorText = await response.text().catch(() => "");
564
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
647
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
565
648
  }
566
649
  const result = await response.json();
567
650
  return result.data;
@@ -602,6 +685,12 @@ var MonitorClient = class {
602
685
  return null;
603
686
  }
604
687
  projectPath = path.resolve(projectPath);
688
+ try {
689
+ projectPath = fs.realpathSync(projectPath);
690
+ } catch {
691
+ console.error("[MonitorClient] projectPath does not exist or is inaccessible");
692
+ return null;
693
+ }
605
694
  try {
606
695
  const stats = fs.statSync(projectPath);
607
696
  if (!stats.isDirectory()) {
@@ -612,21 +701,19 @@ var MonitorClient = class {
612
701
  console.error("[MonitorClient] projectPath does not exist");
613
702
  return null;
614
703
  }
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
704
  let auditOutput;
621
705
  try {
706
+ const packageJsonPath = path.join(projectPath, "package.json");
707
+ if (!fs.existsSync(packageJsonPath)) {
708
+ console.error("[MonitorClient] No package.json found in projectPath");
709
+ return null;
710
+ }
622
711
  auditOutput = execSync("npm audit --json", {
623
712
  cwd: projectPath,
624
713
  encoding: "utf-8",
625
714
  stdio: ["pipe", "pipe", "pipe"],
626
- maxBuffer: 10 * 1024 * 1024,
627
- // 10MB buffer for large outputs
628
- timeout: 6e4
629
- // 60 second timeout
715
+ maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
716
+ timeout: CONFIG_LIMITS.AUDIT_TIMEOUT_MS
630
717
  });
631
718
  } catch (err) {
632
719
  const execError = err;
@@ -668,6 +755,63 @@ var MonitorClient = class {
668
755
  return null;
669
756
  }
670
757
  }
758
+ /**
759
+ * Run npm audit on multiple directories and send results to the monitoring server.
760
+ * This is useful for monorepos or projects with separate client/server directories.
761
+ *
762
+ * Uses the auditPaths configuration to determine which directories to scan.
763
+ * Each path is scanned independently and results are tagged with their environment label.
764
+ *
765
+ * @returns Combined summary of all audit results
766
+ */
767
+ async auditMultiplePaths() {
768
+ if (!this.auditPaths || this.auditPaths.length === 0) {
769
+ console.warn("[MonitorClient] No auditPaths configured");
770
+ return null;
771
+ }
772
+ const results = [];
773
+ const totalSummary = {
774
+ critical: 0,
775
+ high: 0,
776
+ moderate: 0,
777
+ low: 0,
778
+ info: 0
779
+ };
780
+ for (const auditPath of this.auditPaths) {
781
+ console.log(`[MonitorClient] Auditing ${auditPath.environment} at ${auditPath.path}`);
782
+ try {
783
+ const summary = await this.auditDependencies({
784
+ projectPath: auditPath.path,
785
+ environment: auditPath.environment
786
+ });
787
+ if (summary) {
788
+ results.push({
789
+ environment: auditPath.environment,
790
+ scanId: summary.scanId,
791
+ processed: summary.processed,
792
+ resolved: summary.resolved,
793
+ summary: summary.summary
794
+ });
795
+ totalSummary.critical += summary.summary.critical;
796
+ totalSummary.high += summary.summary.high;
797
+ totalSummary.moderate += summary.summary.moderate;
798
+ totalSummary.low += summary.summary.low;
799
+ totalSummary.info += summary.summary.info;
800
+ }
801
+ } catch (err) {
802
+ console.error(`[MonitorClient] Failed to audit ${auditPath.environment}:`, err instanceof Error ? err.message : String(err));
803
+ }
804
+ }
805
+ if (results.length === 0) {
806
+ console.warn("[MonitorClient] No audit results collected from any path");
807
+ return null;
808
+ }
809
+ console.log(`[MonitorClient] Multi-path audit complete: ${results.length} paths scanned`);
810
+ return {
811
+ results,
812
+ totalSummary
813
+ };
814
+ }
671
815
  /**
672
816
  * Parse npm audit JSON output into vulnerability items
673
817
  */
@@ -677,6 +821,9 @@ var MonitorClient = class {
677
821
  return vulnerabilities;
678
822
  }
679
823
  for (const [packageName, vuln] of Object.entries(auditData.vulnerabilities)) {
824
+ if (!vuln.via || !Array.isArray(vuln.via)) {
825
+ continue;
826
+ }
680
827
  const viaDetails = vuln.via.find(
681
828
  (v) => typeof v === "object" && "title" in v
682
829
  );
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() {
@@ -375,6 +448,16 @@ var MonitorClient = class {
375
448
  if (!fs.existsSync(normalizedPath)) {
376
449
  return [];
377
450
  }
451
+ try {
452
+ const realPath = fs.realpathSync(normalizedPath);
453
+ const realBase = fs.realpathSync(normalizedBase);
454
+ if (!path.isAbsolute(packagePath) && !realPath.startsWith(realBase)) {
455
+ console.warn("[MonitorClient] Symlink points outside allowed directory:", packagePath);
456
+ return [];
457
+ }
458
+ } catch {
459
+ return [];
460
+ }
378
461
  const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
379
462
  const technologies = [];
380
463
  const deps = {
@@ -398,9 +481,8 @@ var MonitorClient = class {
398
481
  }
399
482
  }
400
483
  shouldExclude(packageName) {
401
- for (const pattern of this.excludePatterns) {
402
- const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
403
- if (new RegExp(`^${regexPattern}$`).test(packageName)) {
484
+ for (const regex of this.compiledExcludePatterns) {
485
+ if (regex.test(packageName)) {
404
486
  return true;
405
487
  }
406
488
  }
@@ -423,7 +505,7 @@ var MonitorClient = class {
423
505
  });
424
506
  if (!response.ok) {
425
507
  const errorText = await response.text().catch(() => "");
426
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
508
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
427
509
  }
428
510
  }
429
511
  /**
@@ -434,6 +516,7 @@ var MonitorClient = class {
434
516
  if (this.isClosed) return {};
435
517
  const payload = {
436
518
  ...input,
519
+ metadata: this.sanitizeMetadata(input.metadata),
437
520
  environment: this.environment
438
521
  };
439
522
  const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/security`, {
@@ -446,7 +529,7 @@ var MonitorClient = class {
446
529
  });
447
530
  if (!response.ok) {
448
531
  const errorText = await response.text().catch(() => "");
449
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
532
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
450
533
  }
451
534
  const result = await response.json();
452
535
  return { warning: result.warning };
@@ -525,7 +608,7 @@ var MonitorClient = class {
525
608
  });
526
609
  if (!response.ok) {
527
610
  const errorText = await response.text().catch(() => "");
528
- throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
611
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
529
612
  }
530
613
  const result = await response.json();
531
614
  return result.data;
@@ -566,6 +649,12 @@ var MonitorClient = class {
566
649
  return null;
567
650
  }
568
651
  projectPath = path.resolve(projectPath);
652
+ try {
653
+ projectPath = fs.realpathSync(projectPath);
654
+ } catch {
655
+ console.error("[MonitorClient] projectPath does not exist or is inaccessible");
656
+ return null;
657
+ }
569
658
  try {
570
659
  const stats = fs.statSync(projectPath);
571
660
  if (!stats.isDirectory()) {
@@ -576,21 +665,19 @@ var MonitorClient = class {
576
665
  console.error("[MonitorClient] projectPath does not exist");
577
666
  return null;
578
667
  }
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
668
  let auditOutput;
585
669
  try {
670
+ const packageJsonPath = path.join(projectPath, "package.json");
671
+ if (!fs.existsSync(packageJsonPath)) {
672
+ console.error("[MonitorClient] No package.json found in projectPath");
673
+ return null;
674
+ }
586
675
  auditOutput = execSync("npm audit --json", {
587
676
  cwd: projectPath,
588
677
  encoding: "utf-8",
589
678
  stdio: ["pipe", "pipe", "pipe"],
590
- maxBuffer: 10 * 1024 * 1024,
591
- // 10MB buffer for large outputs
592
- timeout: 6e4
593
- // 60 second timeout
679
+ maxBuffer: CONFIG_LIMITS.AUDIT_MAX_BUFFER,
680
+ timeout: CONFIG_LIMITS.AUDIT_TIMEOUT_MS
594
681
  });
595
682
  } catch (err) {
596
683
  const execError = err;
@@ -632,6 +719,63 @@ var MonitorClient = class {
632
719
  return null;
633
720
  }
634
721
  }
722
+ /**
723
+ * Run npm audit on multiple directories and send results to the monitoring server.
724
+ * This is useful for monorepos or projects with separate client/server directories.
725
+ *
726
+ * Uses the auditPaths configuration to determine which directories to scan.
727
+ * Each path is scanned independently and results are tagged with their environment label.
728
+ *
729
+ * @returns Combined summary of all audit results
730
+ */
731
+ async auditMultiplePaths() {
732
+ if (!this.auditPaths || this.auditPaths.length === 0) {
733
+ console.warn("[MonitorClient] No auditPaths configured");
734
+ return null;
735
+ }
736
+ const results = [];
737
+ const totalSummary = {
738
+ critical: 0,
739
+ high: 0,
740
+ moderate: 0,
741
+ low: 0,
742
+ info: 0
743
+ };
744
+ for (const auditPath of this.auditPaths) {
745
+ console.log(`[MonitorClient] Auditing ${auditPath.environment} at ${auditPath.path}`);
746
+ try {
747
+ const summary = await this.auditDependencies({
748
+ projectPath: auditPath.path,
749
+ environment: auditPath.environment
750
+ });
751
+ if (summary) {
752
+ results.push({
753
+ environment: auditPath.environment,
754
+ scanId: summary.scanId,
755
+ processed: summary.processed,
756
+ resolved: summary.resolved,
757
+ summary: summary.summary
758
+ });
759
+ totalSummary.critical += summary.summary.critical;
760
+ totalSummary.high += summary.summary.high;
761
+ totalSummary.moderate += summary.summary.moderate;
762
+ totalSummary.low += summary.summary.low;
763
+ totalSummary.info += summary.summary.info;
764
+ }
765
+ } catch (err) {
766
+ console.error(`[MonitorClient] Failed to audit ${auditPath.environment}:`, err instanceof Error ? err.message : String(err));
767
+ }
768
+ }
769
+ if (results.length === 0) {
770
+ console.warn("[MonitorClient] No audit results collected from any path");
771
+ return null;
772
+ }
773
+ console.log(`[MonitorClient] Multi-path audit complete: ${results.length} paths scanned`);
774
+ return {
775
+ results,
776
+ totalSummary
777
+ };
778
+ }
635
779
  /**
636
780
  * Parse npm audit JSON output into vulnerability items
637
781
  */
@@ -641,6 +785,9 @@ var MonitorClient = class {
641
785
  return vulnerabilities;
642
786
  }
643
787
  for (const [packageName, vuln] of Object.entries(auditData.vulnerabilities)) {
788
+ if (!vuln.via || !Array.isArray(vuln.via)) {
789
+ continue;
790
+ }
644
791
  const viaDetails = vuln.via.find(
645
792
  (v) => typeof v === "object" && "title" in v
646
793
  );
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.10",
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",