@ceon-oy/monitor-sdk 1.0.2 → 1.0.4

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
@@ -96,6 +96,7 @@ interface MonitorClientConfig {
96
96
  environment: string;
97
97
  }[];
98
98
  excludePatterns?: string[]; // Glob patterns to exclude (e.g., '@types/*')
99
+ autoAudit?: boolean; // Enable automatic vulnerability scanning (default: false)
99
100
  }
100
101
  ```
101
102
 
@@ -206,7 +207,29 @@ await monitor.auditDependencies({
206
207
  });
207
208
  ```
208
209
 
209
- #### Scheduled Auditing
210
+ #### Automatic Auditing (Recommended)
211
+
212
+ Enable `autoAudit: true` to let the SDK automatically scan for vulnerabilities based on the interval configured in the Ceon Monitor dashboard:
213
+
214
+ ```typescript
215
+ const monitor = new MonitorClient({
216
+ apiKey: process.env.CEON_MONITOR_API_KEY!,
217
+ endpoint: 'https://monitor.example.com',
218
+ autoAudit: true, // Enable automatic vulnerability scanning
219
+ });
220
+ ```
221
+
222
+ With `autoAudit` enabled:
223
+ - SDK fetches the scan interval from the server on startup
224
+ - Runs an initial vulnerability scan
225
+ - Schedules recurring scans based on server configuration (e.g., every 24 hours)
226
+ - Responds to on-demand "Run Scan" requests from the dashboard (polled every 5 minutes)
227
+
228
+ Configure the scan interval in the Ceon Monitor dashboard under the project's Vulnerabilities page.
229
+
230
+ #### Manual Scheduled Auditing
231
+
232
+ If you prefer manual control over scheduling:
210
233
 
211
234
  ```typescript
212
235
  // Run on app startup
package/dist/index.d.mts CHANGED
@@ -31,6 +31,8 @@ interface MonitorClientConfig {
31
31
  maxRetries?: number;
32
32
  /** Request timeout in ms (default: 10000) */
33
33
  requestTimeoutMs?: number;
34
+ /** Enable automatic vulnerability scanning based on server-configured interval (default: false) */
35
+ autoAudit?: boolean;
34
36
  }
35
37
  interface TechnologyItem {
36
38
  name: string;
@@ -135,12 +137,41 @@ declare class MonitorClient {
135
137
  private retryCount;
136
138
  private isFlushInProgress;
137
139
  private requestTimeoutMs;
140
+ private autoAudit;
141
+ private auditIntervalTimer;
142
+ private settingsPollingTimer;
143
+ private lastScanTime;
144
+ private lastKnownScanRequestedAt;
138
145
  constructor(config: MonitorClientConfig);
139
146
  captureError(error: Error, context?: ErrorContext): Promise<void>;
140
147
  captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>;
141
148
  flush(): Promise<void>;
142
149
  private getErrorKey;
143
150
  close(): Promise<void>;
151
+ private stopAuditIntervalTimer;
152
+ /**
153
+ * Fetch project settings from the monitoring server.
154
+ * Returns configuration including vulnerability scan interval and scan request timestamp.
155
+ */
156
+ fetchProjectSettings(): Promise<{
157
+ name: string;
158
+ vulnerabilityScanIntervalHours: number;
159
+ scanRequestedAt: string | null;
160
+ } | null>;
161
+ /**
162
+ * Setup automatic vulnerability scanning based on server-configured interval.
163
+ * Fetches settings from server and sets up recurring scans.
164
+ * Also sets up polling for on-demand scan requests from the server.
165
+ */
166
+ private setupAutoAudit;
167
+ /**
168
+ * Run a vulnerability scan and track the time it was run.
169
+ */
170
+ private runScanAndTrackTime;
171
+ /**
172
+ * Check if the server has requested an on-demand scan.
173
+ */
174
+ private checkForScanRequest;
144
175
  private enqueue;
145
176
  /**
146
177
  * Fetch with timeout to prevent hanging requests
package/dist/index.d.ts CHANGED
@@ -31,6 +31,8 @@ interface MonitorClientConfig {
31
31
  maxRetries?: number;
32
32
  /** Request timeout in ms (default: 10000) */
33
33
  requestTimeoutMs?: number;
34
+ /** Enable automatic vulnerability scanning based on server-configured interval (default: false) */
35
+ autoAudit?: boolean;
34
36
  }
35
37
  interface TechnologyItem {
36
38
  name: string;
@@ -135,12 +137,41 @@ declare class MonitorClient {
135
137
  private retryCount;
136
138
  private isFlushInProgress;
137
139
  private requestTimeoutMs;
140
+ private autoAudit;
141
+ private auditIntervalTimer;
142
+ private settingsPollingTimer;
143
+ private lastScanTime;
144
+ private lastKnownScanRequestedAt;
138
145
  constructor(config: MonitorClientConfig);
139
146
  captureError(error: Error, context?: ErrorContext): Promise<void>;
140
147
  captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>;
141
148
  flush(): Promise<void>;
142
149
  private getErrorKey;
143
150
  close(): Promise<void>;
151
+ private stopAuditIntervalTimer;
152
+ /**
153
+ * Fetch project settings from the monitoring server.
154
+ * Returns configuration including vulnerability scan interval and scan request timestamp.
155
+ */
156
+ fetchProjectSettings(): Promise<{
157
+ name: string;
158
+ vulnerabilityScanIntervalHours: number;
159
+ scanRequestedAt: string | null;
160
+ } | null>;
161
+ /**
162
+ * Setup automatic vulnerability scanning based on server-configured interval.
163
+ * Fetches settings from server and sets up recurring scans.
164
+ * Also sets up polling for on-demand scan requests from the server.
165
+ */
166
+ private setupAutoAudit;
167
+ /**
168
+ * Run a vulnerability scan and track the time it was run.
169
+ */
170
+ private runScanAndTrackTime;
171
+ /**
172
+ * Check if the server has requested an on-demand scan.
173
+ */
174
+ private checkForScanRequest;
144
175
  private enqueue;
145
176
  /**
146
177
  * Fetch with timeout to prevent hanging requests
package/dist/index.js CHANGED
@@ -42,6 +42,10 @@ var MonitorClient = class {
42
42
  this.isClosed = false;
43
43
  this.retryCount = /* @__PURE__ */ new Map();
44
44
  this.isFlushInProgress = false;
45
+ this.auditIntervalTimer = null;
46
+ this.settingsPollingTimer = null;
47
+ this.lastScanTime = null;
48
+ this.lastKnownScanRequestedAt = null;
45
49
  if (!config.apiKey || config.apiKey.trim().length === 0) {
46
50
  throw new Error("[MonitorClient] API key is required");
47
51
  }
@@ -86,12 +90,18 @@ var MonitorClient = class {
86
90
  "@typescript-eslint/*"
87
91
  ];
88
92
  this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
93
+ this.autoAudit = config.autoAudit || false;
89
94
  this.startFlushTimer();
90
95
  if (this.trackDependencies) {
91
96
  this.syncDependencies().catch((err) => {
92
97
  console.error("[MonitorClient] Failed to sync dependencies:", err instanceof Error ? err.message : String(err));
93
98
  });
94
99
  }
100
+ if (this.autoAudit) {
101
+ this.setupAutoAudit().catch((err) => {
102
+ console.error("[MonitorClient] Failed to setup auto audit:", err instanceof Error ? err.message : String(err));
103
+ });
104
+ }
95
105
  }
96
106
  async captureError(error, context) {
97
107
  if (this.isClosed) return;
@@ -168,8 +178,101 @@ var MonitorClient = class {
168
178
  async close() {
169
179
  this.isClosed = true;
170
180
  this.stopFlushTimer();
181
+ this.stopAuditIntervalTimer();
171
182
  await this.flush();
172
183
  }
184
+ stopAuditIntervalTimer() {
185
+ if (this.auditIntervalTimer) {
186
+ clearInterval(this.auditIntervalTimer);
187
+ this.auditIntervalTimer = null;
188
+ }
189
+ if (this.settingsPollingTimer) {
190
+ clearInterval(this.settingsPollingTimer);
191
+ this.settingsPollingTimer = null;
192
+ }
193
+ }
194
+ /**
195
+ * Fetch project settings from the monitoring server.
196
+ * Returns configuration including vulnerability scan interval and scan request timestamp.
197
+ */
198
+ async fetchProjectSettings() {
199
+ try {
200
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/project/settings`, {
201
+ method: "GET",
202
+ headers: {
203
+ "Authorization": `Bearer ${this.apiKey}`,
204
+ "Content-Type": "application/json"
205
+ }
206
+ });
207
+ if (!response.ok) {
208
+ console.warn("[MonitorClient] Failed to fetch project settings:", response.status);
209
+ return null;
210
+ }
211
+ const result = await response.json();
212
+ return result.data || null;
213
+ } catch (err) {
214
+ console.warn("[MonitorClient] Failed to fetch project settings:", err instanceof Error ? err.message : String(err));
215
+ return null;
216
+ }
217
+ }
218
+ /**
219
+ * Setup automatic vulnerability scanning based on server-configured interval.
220
+ * Fetches settings from server and sets up recurring scans.
221
+ * Also sets up polling for on-demand scan requests from the server.
222
+ */
223
+ async setupAutoAudit() {
224
+ const settings = await this.fetchProjectSettings();
225
+ if (!settings) {
226
+ console.warn("[MonitorClient] Could not fetch project settings, auto audit disabled");
227
+ return;
228
+ }
229
+ if (settings.scanRequestedAt) {
230
+ this.lastKnownScanRequestedAt = new Date(settings.scanRequestedAt);
231
+ }
232
+ const intervalHours = settings.vulnerabilityScanIntervalHours;
233
+ if (intervalHours <= 0) {
234
+ console.log("[MonitorClient] Scheduled vulnerability scanning disabled by server configuration");
235
+ } else {
236
+ console.log(`[MonitorClient] Auto vulnerability scanning enabled (every ${intervalHours} hours)`);
237
+ await this.runScanAndTrackTime();
238
+ const intervalMs = intervalHours * 60 * 60 * 1e3;
239
+ this.auditIntervalTimer = setInterval(() => {
240
+ this.runScanAndTrackTime();
241
+ }, intervalMs);
242
+ }
243
+ console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
244
+ this.settingsPollingTimer = setInterval(() => {
245
+ this.checkForScanRequest();
246
+ }, 5 * 60 * 1e3);
247
+ }
248
+ /**
249
+ * Run a vulnerability scan and track the time it was run.
250
+ */
251
+ async runScanAndTrackTime() {
252
+ try {
253
+ await this.auditDependencies();
254
+ this.lastScanTime = /* @__PURE__ */ new Date();
255
+ } catch (err) {
256
+ console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
257
+ }
258
+ }
259
+ /**
260
+ * Check if the server has requested an on-demand scan.
261
+ */
262
+ async checkForScanRequest() {
263
+ try {
264
+ const settings = await this.fetchProjectSettings();
265
+ if (!settings || !settings.scanRequestedAt) return;
266
+ const scanRequestedAt = new Date(settings.scanRequestedAt);
267
+ if (!this.lastKnownScanRequestedAt || scanRequestedAt > this.lastKnownScanRequestedAt) {
268
+ console.log("[MonitorClient] On-demand scan requested by server");
269
+ this.lastKnownScanRequestedAt = scanRequestedAt;
270
+ await this.runScanAndTrackTime();
271
+ }
272
+ } catch (err) {
273
+ console.error("[MonitorClient] Failed to check for scan request:", err instanceof Error ? err.message : String(err));
274
+ }
275
+ }
173
276
  enqueue(payload) {
174
277
  if (this.queue.length >= this.maxQueueSize) {
175
278
  console.warn("[MonitorClient] Queue full, dropping oldest error");
@@ -183,12 +286,12 @@ var MonitorClient = class {
183
286
  /**
184
287
  * Fetch with timeout to prevent hanging requests
185
288
  */
186
- async fetchWithTimeout(url, options, timeoutMs = this.requestTimeoutMs) {
289
+ async fetchWithTimeout(url, options2, timeoutMs = this.requestTimeoutMs) {
187
290
  const controller = new AbortController();
188
291
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
189
292
  try {
190
293
  const response = await fetch(url, {
191
- ...options,
294
+ ...options2,
192
295
  signal: controller.signal
193
296
  });
194
297
  return response;
@@ -271,13 +374,13 @@ var MonitorClient = class {
271
374
  try {
272
375
  const fsModule = await import("fs");
273
376
  const pathModule = await import("path");
274
- const fs = fsModule.default || fsModule;
275
- const path = pathModule.default || pathModule;
377
+ const fs2 = fsModule.default || fsModule;
378
+ const path2 = pathModule.default || pathModule;
276
379
  const baseDir = process.cwd();
277
- const resolvedPath = path.isAbsolute(packagePath) ? packagePath : path.join(baseDir, packagePath);
278
- const normalizedPath = path.normalize(resolvedPath);
279
- const normalizedBase = path.normalize(baseDir);
280
- if (!path.isAbsolute(packagePath)) {
380
+ const resolvedPath = path2.isAbsolute(packagePath) ? packagePath : path2.join(baseDir, packagePath);
381
+ const normalizedPath = path2.normalize(resolvedPath);
382
+ const normalizedBase = path2.normalize(baseDir);
383
+ if (!path2.isAbsolute(packagePath)) {
281
384
  if (!normalizedPath.startsWith(normalizedBase)) {
282
385
  console.warn("[MonitorClient] Path traversal attempt blocked:", packagePath);
283
386
  return [];
@@ -291,10 +394,10 @@ var MonitorClient = class {
291
394
  console.warn("[MonitorClient] Path must point to package.json");
292
395
  return [];
293
396
  }
294
- if (!fs.existsSync(normalizedPath)) {
397
+ if (!fs2.existsSync(normalizedPath)) {
295
398
  return [];
296
399
  }
297
- const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
400
+ const packageJson = JSON.parse(fs2.readFileSync(normalizedPath, "utf-8"));
298
401
  const technologies = [];
299
402
  const deps = {
300
403
  ...packageJson.dependencies,
@@ -328,7 +431,7 @@ var MonitorClient = class {
328
431
  async sendTechnologies(technologies) {
329
432
  await this.sendTechnologiesWithEnvironment(technologies, this.environment);
330
433
  }
331
- async sendTechnologiesWithEnvironment(technologies, environment) {
434
+ async sendTechnologiesWithEnvironment(technologies, environment2) {
332
435
  const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/technologies/sync`, {
333
436
  method: "POST",
334
437
  headers: {
@@ -336,7 +439,7 @@ var MonitorClient = class {
336
439
  Authorization: `Bearer ${this.apiKey}`
337
440
  },
338
441
  body: JSON.stringify({
339
- environment,
442
+ environment: environment2,
340
443
  technologies
341
444
  })
342
445
  });
@@ -373,74 +476,74 @@ var MonitorClient = class {
373
476
  /**
374
477
  * Capture a login failure event (convenience method)
375
478
  */
376
- async captureLoginFailure(options) {
479
+ async captureLoginFailure(options2) {
377
480
  return this.captureSecurityEvent({
378
- eventType: `login_failed_${options.authMethod || "other"}`,
481
+ eventType: `login_failed_${options2.authMethod || "other"}`,
379
482
  category: "AUTHENTICATION",
380
483
  severity: "MEDIUM",
381
- ip: options.ip,
382
- identifier: options.identifier,
383
- endpoint: options.endpoint,
384
- userAgent: options.userAgent,
385
- metadata: { reason: options.reason, authMethod: options.authMethod }
484
+ ip: options2.ip,
485
+ identifier: options2.identifier,
486
+ endpoint: options2.endpoint,
487
+ userAgent: options2.userAgent,
488
+ metadata: { reason: options2.reason, authMethod: options2.authMethod }
386
489
  });
387
490
  }
388
491
  /**
389
492
  * Capture a successful login event
390
493
  */
391
- async captureLoginSuccess(options) {
494
+ async captureLoginSuccess(options2) {
392
495
  await this.captureSecurityEvent({
393
- eventType: `login_success_${options.authMethod || "other"}`,
496
+ eventType: `login_success_${options2.authMethod || "other"}`,
394
497
  category: "AUTHENTICATION",
395
498
  severity: "LOW",
396
- ip: options.ip,
397
- identifier: options.identifier,
398
- endpoint: options.endpoint,
399
- userAgent: options.userAgent,
400
- metadata: { authMethod: options.authMethod }
499
+ ip: options2.ip,
500
+ identifier: options2.identifier,
501
+ endpoint: options2.endpoint,
502
+ userAgent: options2.userAgent,
503
+ metadata: { authMethod: options2.authMethod }
401
504
  });
402
505
  }
403
506
  /**
404
507
  * Capture a rate limit event
405
508
  */
406
- async captureRateLimit(options) {
509
+ async captureRateLimit(options2) {
407
510
  await this.captureSecurityEvent({
408
511
  eventType: "rate_limit_exceeded",
409
512
  category: "RATE_LIMIT",
410
513
  severity: "MEDIUM",
411
- ip: options.ip,
412
- identifier: options.identifier,
413
- endpoint: options.endpoint,
414
- userAgent: options.userAgent,
415
- metadata: { limit: options.limit, window: options.window }
514
+ ip: options2.ip,
515
+ identifier: options2.identifier,
516
+ endpoint: options2.endpoint,
517
+ userAgent: options2.userAgent,
518
+ metadata: { limit: options2.limit, window: options2.window }
416
519
  });
417
520
  }
418
521
  /**
419
522
  * Capture an authorization failure (user tried to access unauthorized resource)
420
523
  */
421
- async captureAuthorizationFailure(options) {
524
+ async captureAuthorizationFailure(options2) {
422
525
  await this.captureSecurityEvent({
423
526
  eventType: "authorization_denied",
424
527
  category: "AUTHORIZATION",
425
528
  severity: "MEDIUM",
426
- ip: options.ip,
427
- identifier: options.identifier,
428
- endpoint: options.endpoint,
429
- userAgent: options.userAgent,
430
- metadata: { resource: options.resource, action: options.action }
529
+ ip: options2.ip,
530
+ identifier: options2.identifier,
531
+ endpoint: options2.endpoint,
532
+ userAgent: options2.userAgent,
533
+ metadata: { resource: options2.resource, action: options2.action }
431
534
  });
432
535
  }
433
536
  /**
434
537
  * Check if an IP or identifier has triggered brute force detection
435
538
  */
436
- async checkBruteForce(options) {
539
+ async checkBruteForce(options2) {
437
540
  const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/security/detect/brute-force`, {
438
541
  method: "POST",
439
542
  headers: {
440
543
  "Content-Type": "application/json",
441
544
  Authorization: `Bearer ${this.apiKey}`
442
545
  },
443
- body: JSON.stringify(options)
546
+ body: JSON.stringify(options2)
444
547
  });
445
548
  if (!response.ok) {
446
549
  const errorText = await response.text().catch(() => "");
@@ -466,9 +569,10 @@ var MonitorClient = class {
466
569
  let path;
467
570
  let fs;
468
571
  try {
469
- execSync = require("child_process").execSync;
470
- path = require("path");
471
- fs = require("fs");
572
+ const _require = typeof globalThis.__webpack_require__ === "function" ? eval("require") : require;
573
+ execSync = _require("child_process").execSync;
574
+ path = _require("path");
575
+ fs = _require("fs");
472
576
  } catch {
473
577
  console.warn("[MonitorClient] auditDependencies requires Node.js (not available in bundled/browser environments)");
474
578
  return null;
package/dist/index.mjs CHANGED
@@ -13,6 +13,10 @@ var MonitorClient = class {
13
13
  this.isClosed = false;
14
14
  this.retryCount = /* @__PURE__ */ new Map();
15
15
  this.isFlushInProgress = false;
16
+ this.auditIntervalTimer = null;
17
+ this.settingsPollingTimer = null;
18
+ this.lastScanTime = null;
19
+ this.lastKnownScanRequestedAt = null;
16
20
  if (!config.apiKey || config.apiKey.trim().length === 0) {
17
21
  throw new Error("[MonitorClient] API key is required");
18
22
  }
@@ -57,12 +61,18 @@ var MonitorClient = class {
57
61
  "@typescript-eslint/*"
58
62
  ];
59
63
  this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
64
+ this.autoAudit = config.autoAudit || false;
60
65
  this.startFlushTimer();
61
66
  if (this.trackDependencies) {
62
67
  this.syncDependencies().catch((err) => {
63
68
  console.error("[MonitorClient] Failed to sync dependencies:", err instanceof Error ? err.message : String(err));
64
69
  });
65
70
  }
71
+ if (this.autoAudit) {
72
+ this.setupAutoAudit().catch((err) => {
73
+ console.error("[MonitorClient] Failed to setup auto audit:", err instanceof Error ? err.message : String(err));
74
+ });
75
+ }
66
76
  }
67
77
  async captureError(error, context) {
68
78
  if (this.isClosed) return;
@@ -139,8 +149,101 @@ var MonitorClient = class {
139
149
  async close() {
140
150
  this.isClosed = true;
141
151
  this.stopFlushTimer();
152
+ this.stopAuditIntervalTimer();
142
153
  await this.flush();
143
154
  }
155
+ stopAuditIntervalTimer() {
156
+ if (this.auditIntervalTimer) {
157
+ clearInterval(this.auditIntervalTimer);
158
+ this.auditIntervalTimer = null;
159
+ }
160
+ if (this.settingsPollingTimer) {
161
+ clearInterval(this.settingsPollingTimer);
162
+ this.settingsPollingTimer = null;
163
+ }
164
+ }
165
+ /**
166
+ * Fetch project settings from the monitoring server.
167
+ * Returns configuration including vulnerability scan interval and scan request timestamp.
168
+ */
169
+ async fetchProjectSettings() {
170
+ try {
171
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/project/settings`, {
172
+ method: "GET",
173
+ headers: {
174
+ "Authorization": `Bearer ${this.apiKey}`,
175
+ "Content-Type": "application/json"
176
+ }
177
+ });
178
+ if (!response.ok) {
179
+ console.warn("[MonitorClient] Failed to fetch project settings:", response.status);
180
+ return null;
181
+ }
182
+ const result = await response.json();
183
+ return result.data || null;
184
+ } catch (err) {
185
+ console.warn("[MonitorClient] Failed to fetch project settings:", err instanceof Error ? err.message : String(err));
186
+ return null;
187
+ }
188
+ }
189
+ /**
190
+ * Setup automatic vulnerability scanning based on server-configured interval.
191
+ * Fetches settings from server and sets up recurring scans.
192
+ * Also sets up polling for on-demand scan requests from the server.
193
+ */
194
+ async setupAutoAudit() {
195
+ const settings = await this.fetchProjectSettings();
196
+ if (!settings) {
197
+ console.warn("[MonitorClient] Could not fetch project settings, auto audit disabled");
198
+ return;
199
+ }
200
+ if (settings.scanRequestedAt) {
201
+ this.lastKnownScanRequestedAt = new Date(settings.scanRequestedAt);
202
+ }
203
+ const intervalHours = settings.vulnerabilityScanIntervalHours;
204
+ if (intervalHours <= 0) {
205
+ console.log("[MonitorClient] Scheduled vulnerability scanning disabled by server configuration");
206
+ } else {
207
+ console.log(`[MonitorClient] Auto vulnerability scanning enabled (every ${intervalHours} hours)`);
208
+ await this.runScanAndTrackTime();
209
+ const intervalMs = intervalHours * 60 * 60 * 1e3;
210
+ this.auditIntervalTimer = setInterval(() => {
211
+ this.runScanAndTrackTime();
212
+ }, intervalMs);
213
+ }
214
+ console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
215
+ this.settingsPollingTimer = setInterval(() => {
216
+ this.checkForScanRequest();
217
+ }, 5 * 60 * 1e3);
218
+ }
219
+ /**
220
+ * Run a vulnerability scan and track the time it was run.
221
+ */
222
+ async runScanAndTrackTime() {
223
+ try {
224
+ await this.auditDependencies();
225
+ this.lastScanTime = /* @__PURE__ */ new Date();
226
+ } catch (err) {
227
+ console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
228
+ }
229
+ }
230
+ /**
231
+ * Check if the server has requested an on-demand scan.
232
+ */
233
+ async checkForScanRequest() {
234
+ try {
235
+ const settings = await this.fetchProjectSettings();
236
+ if (!settings || !settings.scanRequestedAt) return;
237
+ const scanRequestedAt = new Date(settings.scanRequestedAt);
238
+ if (!this.lastKnownScanRequestedAt || scanRequestedAt > this.lastKnownScanRequestedAt) {
239
+ console.log("[MonitorClient] On-demand scan requested by server");
240
+ this.lastKnownScanRequestedAt = scanRequestedAt;
241
+ await this.runScanAndTrackTime();
242
+ }
243
+ } catch (err) {
244
+ console.error("[MonitorClient] Failed to check for scan request:", err instanceof Error ? err.message : String(err));
245
+ }
246
+ }
144
247
  enqueue(payload) {
145
248
  if (this.queue.length >= this.maxQueueSize) {
146
249
  console.warn("[MonitorClient] Queue full, dropping oldest error");
@@ -154,12 +257,12 @@ var MonitorClient = class {
154
257
  /**
155
258
  * Fetch with timeout to prevent hanging requests
156
259
  */
157
- async fetchWithTimeout(url, options, timeoutMs = this.requestTimeoutMs) {
260
+ async fetchWithTimeout(url, options2, timeoutMs = this.requestTimeoutMs) {
158
261
  const controller = new AbortController();
159
262
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
160
263
  try {
161
264
  const response = await fetch(url, {
162
- ...options,
265
+ ...options2,
163
266
  signal: controller.signal
164
267
  });
165
268
  return response;
@@ -242,13 +345,13 @@ var MonitorClient = class {
242
345
  try {
243
346
  const fsModule = await import("fs");
244
347
  const pathModule = await import("path");
245
- const fs = fsModule.default || fsModule;
246
- const path = pathModule.default || pathModule;
348
+ const fs2 = fsModule.default || fsModule;
349
+ const path2 = pathModule.default || pathModule;
247
350
  const baseDir = process.cwd();
248
- const resolvedPath = path.isAbsolute(packagePath) ? packagePath : path.join(baseDir, packagePath);
249
- const normalizedPath = path.normalize(resolvedPath);
250
- const normalizedBase = path.normalize(baseDir);
251
- if (!path.isAbsolute(packagePath)) {
351
+ const resolvedPath = path2.isAbsolute(packagePath) ? packagePath : path2.join(baseDir, packagePath);
352
+ const normalizedPath = path2.normalize(resolvedPath);
353
+ const normalizedBase = path2.normalize(baseDir);
354
+ if (!path2.isAbsolute(packagePath)) {
252
355
  if (!normalizedPath.startsWith(normalizedBase)) {
253
356
  console.warn("[MonitorClient] Path traversal attempt blocked:", packagePath);
254
357
  return [];
@@ -262,10 +365,10 @@ var MonitorClient = class {
262
365
  console.warn("[MonitorClient] Path must point to package.json");
263
366
  return [];
264
367
  }
265
- if (!fs.existsSync(normalizedPath)) {
368
+ if (!fs2.existsSync(normalizedPath)) {
266
369
  return [];
267
370
  }
268
- const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
371
+ const packageJson = JSON.parse(fs2.readFileSync(normalizedPath, "utf-8"));
269
372
  const technologies = [];
270
373
  const deps = {
271
374
  ...packageJson.dependencies,
@@ -299,7 +402,7 @@ var MonitorClient = class {
299
402
  async sendTechnologies(technologies) {
300
403
  await this.sendTechnologiesWithEnvironment(technologies, this.environment);
301
404
  }
302
- async sendTechnologiesWithEnvironment(technologies, environment) {
405
+ async sendTechnologiesWithEnvironment(technologies, environment2) {
303
406
  const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/technologies/sync`, {
304
407
  method: "POST",
305
408
  headers: {
@@ -307,7 +410,7 @@ var MonitorClient = class {
307
410
  Authorization: `Bearer ${this.apiKey}`
308
411
  },
309
412
  body: JSON.stringify({
310
- environment,
413
+ environment: environment2,
311
414
  technologies
312
415
  })
313
416
  });
@@ -344,74 +447,74 @@ var MonitorClient = class {
344
447
  /**
345
448
  * Capture a login failure event (convenience method)
346
449
  */
347
- async captureLoginFailure(options) {
450
+ async captureLoginFailure(options2) {
348
451
  return this.captureSecurityEvent({
349
- eventType: `login_failed_${options.authMethod || "other"}`,
452
+ eventType: `login_failed_${options2.authMethod || "other"}`,
350
453
  category: "AUTHENTICATION",
351
454
  severity: "MEDIUM",
352
- ip: options.ip,
353
- identifier: options.identifier,
354
- endpoint: options.endpoint,
355
- userAgent: options.userAgent,
356
- metadata: { reason: options.reason, authMethod: options.authMethod }
455
+ ip: options2.ip,
456
+ identifier: options2.identifier,
457
+ endpoint: options2.endpoint,
458
+ userAgent: options2.userAgent,
459
+ metadata: { reason: options2.reason, authMethod: options2.authMethod }
357
460
  });
358
461
  }
359
462
  /**
360
463
  * Capture a successful login event
361
464
  */
362
- async captureLoginSuccess(options) {
465
+ async captureLoginSuccess(options2) {
363
466
  await this.captureSecurityEvent({
364
- eventType: `login_success_${options.authMethod || "other"}`,
467
+ eventType: `login_success_${options2.authMethod || "other"}`,
365
468
  category: "AUTHENTICATION",
366
469
  severity: "LOW",
367
- ip: options.ip,
368
- identifier: options.identifier,
369
- endpoint: options.endpoint,
370
- userAgent: options.userAgent,
371
- metadata: { authMethod: options.authMethod }
470
+ ip: options2.ip,
471
+ identifier: options2.identifier,
472
+ endpoint: options2.endpoint,
473
+ userAgent: options2.userAgent,
474
+ metadata: { authMethod: options2.authMethod }
372
475
  });
373
476
  }
374
477
  /**
375
478
  * Capture a rate limit event
376
479
  */
377
- async captureRateLimit(options) {
480
+ async captureRateLimit(options2) {
378
481
  await this.captureSecurityEvent({
379
482
  eventType: "rate_limit_exceeded",
380
483
  category: "RATE_LIMIT",
381
484
  severity: "MEDIUM",
382
- ip: options.ip,
383
- identifier: options.identifier,
384
- endpoint: options.endpoint,
385
- userAgent: options.userAgent,
386
- metadata: { limit: options.limit, window: options.window }
485
+ ip: options2.ip,
486
+ identifier: options2.identifier,
487
+ endpoint: options2.endpoint,
488
+ userAgent: options2.userAgent,
489
+ metadata: { limit: options2.limit, window: options2.window }
387
490
  });
388
491
  }
389
492
  /**
390
493
  * Capture an authorization failure (user tried to access unauthorized resource)
391
494
  */
392
- async captureAuthorizationFailure(options) {
495
+ async captureAuthorizationFailure(options2) {
393
496
  await this.captureSecurityEvent({
394
497
  eventType: "authorization_denied",
395
498
  category: "AUTHORIZATION",
396
499
  severity: "MEDIUM",
397
- ip: options.ip,
398
- identifier: options.identifier,
399
- endpoint: options.endpoint,
400
- userAgent: options.userAgent,
401
- metadata: { resource: options.resource, action: options.action }
500
+ ip: options2.ip,
501
+ identifier: options2.identifier,
502
+ endpoint: options2.endpoint,
503
+ userAgent: options2.userAgent,
504
+ metadata: { resource: options2.resource, action: options2.action }
402
505
  });
403
506
  }
404
507
  /**
405
508
  * Check if an IP or identifier has triggered brute force detection
406
509
  */
407
- async checkBruteForce(options) {
510
+ async checkBruteForce(options2) {
408
511
  const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/security/detect/brute-force`, {
409
512
  method: "POST",
410
513
  headers: {
411
514
  "Content-Type": "application/json",
412
515
  Authorization: `Bearer ${this.apiKey}`
413
516
  },
414
- body: JSON.stringify(options)
517
+ body: JSON.stringify(options2)
415
518
  });
416
519
  if (!response.ok) {
417
520
  const errorText = await response.text().catch(() => "");
@@ -437,9 +540,10 @@ var MonitorClient = class {
437
540
  let path;
438
541
  let fs;
439
542
  try {
440
- execSync = __require("child_process").execSync;
441
- path = __require("path");
442
- fs = __require("fs");
543
+ const _require = typeof globalThis.__webpack_require__ === "function" ? eval("require") : __require;
544
+ execSync = _require("child_process").execSync;
545
+ path = _require("path");
546
+ fs = _require("fs");
443
547
  } catch {
444
548
  console.warn("[MonitorClient] auditDependencies requires Node.js (not available in bundled/browser environments)");
445
549
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceon-oy/monitor-sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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",