@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 +65 -0
- package/dist/index.d.mts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.js +177 -30
- package/dist/index.mjs +177 -30
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
438
|
-
|
|
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:
|
|
627
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
402
|
-
|
|
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:
|
|
591
|
-
|
|
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