verdaccio-security-filter 1.0.6

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.
@@ -0,0 +1,43 @@
1
+ import { CVECheckConfig, CVECheckResult } from '../types';
2
+ /**
3
+ * CVE Checker that integrates with vulnerability databases
4
+ */
5
+ export declare class CVEChecker {
6
+ private config;
7
+ private cache;
8
+ private lastUpdate;
9
+ constructor(config?: CVECheckConfig);
10
+ /**
11
+ * Check if a package version has known vulnerabilities
12
+ */
13
+ checkPackage(packageName: string, version: string): Promise<CVECheckResult>;
14
+ /**
15
+ * Fetch vulnerabilities from configured databases
16
+ */
17
+ private fetchVulnerabilities;
18
+ /**
19
+ * Fetch vulnerabilities from OSV (Open Source Vulnerabilities) database
20
+ * https://osv.dev/
21
+ */
22
+ private fetchFromOSV;
23
+ /**
24
+ * Map OSV severity to our severity levels
25
+ */
26
+ private mapOSVSeverity;
27
+ /**
28
+ * Check if cache is stale
29
+ */
30
+ private isCacheStale;
31
+ /**
32
+ * Load cache from disk
33
+ */
34
+ private loadCache;
35
+ /**
36
+ * Save cache to disk
37
+ */
38
+ private saveCache;
39
+ /**
40
+ * Clear cache
41
+ */
42
+ clearCache(): void;
43
+ }
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.CVEChecker = void 0;
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ /**
49
+ * CVE Checker that integrates with vulnerability databases
50
+ */
51
+ class CVEChecker {
52
+ constructor(config) {
53
+ this.cache = new Map();
54
+ this.lastUpdate = 0;
55
+ this.config = {
56
+ enabled: (config === null || config === void 0 ? void 0 : config.enabled) || false,
57
+ databases: (config === null || config === void 0 ? void 0 : config.databases) || ['osv'],
58
+ severity: (config === null || config === void 0 ? void 0 : config.severity) || ['high', 'critical'],
59
+ autoBlock: (config === null || config === void 0 ? void 0 : config.autoBlock) || false,
60
+ updateInterval: (config === null || config === void 0 ? void 0 : config.updateInterval) || 24,
61
+ cacheDir: (config === null || config === void 0 ? void 0 : config.cacheDir) || './.security-cache',
62
+ };
63
+ if (this.config.enabled) {
64
+ this.loadCache();
65
+ }
66
+ }
67
+ /**
68
+ * Check if a package version has known vulnerabilities
69
+ */
70
+ checkPackage(packageName, version) {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ if (!this.config.enabled) {
73
+ return {
74
+ package: packageName,
75
+ version,
76
+ vulnerabilities: [],
77
+ isVulnerable: false,
78
+ checkedAt: new Date().toISOString(),
79
+ };
80
+ }
81
+ const cacheKey = `${packageName}@${version}`;
82
+ // Check cache first
83
+ if (this.cache.has(cacheKey) && !this.isCacheStale()) {
84
+ return this.cache.get(cacheKey);
85
+ }
86
+ // Fetch from databases
87
+ const vulnerabilities = yield this.fetchVulnerabilities(packageName, version);
88
+ // Filter by configured severity
89
+ const filteredVulnerabilities = vulnerabilities.filter(vuln => this.config.severity.includes(vuln.severity));
90
+ const result = {
91
+ package: packageName,
92
+ version,
93
+ vulnerabilities: filteredVulnerabilities,
94
+ isVulnerable: filteredVulnerabilities.length > 0,
95
+ checkedAt: new Date().toISOString(),
96
+ };
97
+ // Cache result
98
+ this.cache.set(cacheKey, result);
99
+ this.saveCache();
100
+ return result;
101
+ });
102
+ }
103
+ /**
104
+ * Fetch vulnerabilities from configured databases
105
+ */
106
+ fetchVulnerabilities(packageName, version) {
107
+ return __awaiter(this, void 0, void 0, function* () {
108
+ const vulnerabilities = [];
109
+ for (const database of this.config.databases) {
110
+ try {
111
+ switch (database) {
112
+ case 'osv':
113
+ vulnerabilities.push(...yield this.fetchFromOSV(packageName, version));
114
+ break;
115
+ case 'snyk':
116
+ // Snyk integration would go here (requires API key)
117
+ break;
118
+ case 'github':
119
+ // GitHub Advisory Database integration would go here
120
+ break;
121
+ }
122
+ }
123
+ catch (error) {
124
+ const message = error instanceof Error ? error.message : String(error);
125
+ console.error(`[CVE Checker] Error fetching from ${database}:`, message);
126
+ }
127
+ }
128
+ return vulnerabilities;
129
+ });
130
+ }
131
+ /**
132
+ * Fetch vulnerabilities from OSV (Open Source Vulnerabilities) database
133
+ * https://osv.dev/
134
+ */
135
+ fetchFromOSV(packageName, version) {
136
+ return __awaiter(this, void 0, void 0, function* () {
137
+ try {
138
+ const response = yield fetch('https://api.osv.dev/v1/query', {
139
+ method: 'POST',
140
+ headers: {
141
+ 'Content-Type': 'application/json',
142
+ },
143
+ body: JSON.stringify({
144
+ package: {
145
+ name: packageName,
146
+ ecosystem: 'npm',
147
+ },
148
+ version: version,
149
+ }),
150
+ });
151
+ if (!response.ok) {
152
+ throw new Error(`OSV API returned ${response.status}`);
153
+ }
154
+ const data = yield response.json();
155
+ if (!data.vulns || data.vulns.length === 0) {
156
+ return [];
157
+ }
158
+ return data.vulns.map((vuln) => {
159
+ var _a, _b, _c, _d, _e, _f, _g;
160
+ return ({
161
+ id: vuln.id,
162
+ severity: this.mapOSVSeverity(vuln.severity),
163
+ summary: vuln.summary || vuln.details || 'No description available',
164
+ affectedVersions: ((_a = vuln.affected) === null || _a === void 0 ? void 0 : _a.map((a) => { var _a; return (_a = a.ranges) === null || _a === void 0 ? void 0 : _a.map((r) => { var _a; return (_a = r.events) === null || _a === void 0 ? void 0 : _a.map((e) => e.introduced || e.fixed).filter(Boolean); }).flat(); }).flat()) || [],
165
+ fixedVersion: (_g = (_f = (_e = (_d = (_c = (_b = vuln.affected) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.ranges) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.events) === null || _f === void 0 ? void 0 : _f.find((e) => e.fixed)) === null || _g === void 0 ? void 0 : _g.fixed,
166
+ publishedDate: vuln.published || new Date().toISOString(),
167
+ source: 'osv',
168
+ });
169
+ });
170
+ }
171
+ catch (error) {
172
+ const message = error instanceof Error ? error.message : String(error);
173
+ console.error('[CVE Checker] OSV API error:', message);
174
+ return [];
175
+ }
176
+ });
177
+ }
178
+ /**
179
+ * Map OSV severity to our severity levels
180
+ */
181
+ mapOSVSeverity(osvSeverity) {
182
+ if (!osvSeverity || osvSeverity.length === 0) {
183
+ return 'medium';
184
+ }
185
+ // OSV uses CVSS scores
186
+ const severity = osvSeverity[0];
187
+ if (severity.type === 'CVSS_V3') {
188
+ const score = parseFloat(severity.score);
189
+ if (score >= 9.0)
190
+ return 'critical';
191
+ if (score >= 7.0)
192
+ return 'high';
193
+ if (score >= 4.0)
194
+ return 'medium';
195
+ return 'low';
196
+ }
197
+ return 'medium';
198
+ }
199
+ /**
200
+ * Check if cache is stale
201
+ */
202
+ isCacheStale() {
203
+ const now = Date.now();
204
+ const updateIntervalMs = this.config.updateInterval * 60 * 60 * 1000;
205
+ return now - this.lastUpdate > updateIntervalMs;
206
+ }
207
+ /**
208
+ * Load cache from disk
209
+ */
210
+ loadCache() {
211
+ try {
212
+ const cacheFile = path.join(this.config.cacheDir, 'cve-cache.json');
213
+ if (fs.existsSync(cacheFile)) {
214
+ const data = fs.readFileSync(cacheFile, 'utf8');
215
+ const parsed = JSON.parse(data);
216
+ this.cache = new Map(Object.entries(parsed.cache));
217
+ this.lastUpdate = parsed.lastUpdate || 0;
218
+ }
219
+ }
220
+ catch (error) {
221
+ const message = error instanceof Error ? error.message : String(error);
222
+ console.error('[CVE Checker] Failed to load cache:', message);
223
+ }
224
+ }
225
+ /**
226
+ * Save cache to disk
227
+ */
228
+ saveCache() {
229
+ try {
230
+ const cacheFile = path.join(this.config.cacheDir, 'cve-cache.json');
231
+ const dir = path.dirname(cacheFile);
232
+ if (!fs.existsSync(dir)) {
233
+ fs.mkdirSync(dir, { recursive: true });
234
+ }
235
+ const data = {
236
+ cache: Object.fromEntries(this.cache),
237
+ lastUpdate: Date.now(),
238
+ };
239
+ fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), 'utf8');
240
+ this.lastUpdate = Date.now();
241
+ }
242
+ catch (error) {
243
+ const message = error instanceof Error ? error.message : String(error);
244
+ console.error('[CVE Checker] Failed to save cache:', message);
245
+ }
246
+ }
247
+ /**
248
+ * Clear cache
249
+ */
250
+ clearCache() {
251
+ this.cache.clear();
252
+ this.lastUpdate = 0;
253
+ this.saveCache();
254
+ }
255
+ }
256
+ exports.CVEChecker = CVEChecker;
@@ -0,0 +1,37 @@
1
+ import { LicenseConfig } from '../types';
2
+ import { Version } from '@verdaccio/types';
3
+ /**
4
+ * License checker for validating package licenses
5
+ */
6
+ export declare class LicenseChecker {
7
+ private config;
8
+ constructor(config?: LicenseConfig);
9
+ /**
10
+ * Check if a package version's license is allowed
11
+ */
12
+ checkLicense(versionData: Version): {
13
+ allowed: boolean;
14
+ reason?: string;
15
+ license?: string;
16
+ };
17
+ /**
18
+ * Extract license from version data
19
+ */
20
+ private extractLicense;
21
+ /**
22
+ * Check if license is blocked
23
+ */
24
+ private isLicenseBlocked;
25
+ /**
26
+ * Check if license is allowed
27
+ */
28
+ private isLicenseAllowed;
29
+ /**
30
+ * Get common open source licenses
31
+ */
32
+ static getCommonOpenSourceLicenses(): string[];
33
+ /**
34
+ * Get common copyleft licenses
35
+ */
36
+ static getCopyleftLicenses(): string[];
37
+ }
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LicenseChecker = void 0;
4
+ /**
5
+ * License checker for validating package licenses
6
+ */
7
+ class LicenseChecker {
8
+ constructor(config) {
9
+ this.config = {
10
+ allowed: (config === null || config === void 0 ? void 0 : config.allowed) || [],
11
+ blocked: (config === null || config === void 0 ? void 0 : config.blocked) || [],
12
+ requireLicense: (config === null || config === void 0 ? void 0 : config.requireLicense) !== false,
13
+ };
14
+ }
15
+ /**
16
+ * Check if a package version's license is allowed
17
+ */
18
+ checkLicense(versionData) {
19
+ const license = this.extractLicense(versionData);
20
+ // Check if license is missing
21
+ if (!license || license === 'UNLICENSED' || license === '') {
22
+ if (this.config.requireLicense) {
23
+ return {
24
+ allowed: false,
25
+ reason: 'Package does not specify a license',
26
+ license: license || 'none',
27
+ };
28
+ }
29
+ return { allowed: true };
30
+ }
31
+ // Check blocked list
32
+ if (this.isLicenseBlocked(license)) {
33
+ return {
34
+ allowed: false,
35
+ reason: `License '${license}' is in blocked list`,
36
+ license,
37
+ };
38
+ }
39
+ // Check allowed list (if specified)
40
+ if (this.config.allowed.length > 0 && !this.isLicenseAllowed(license)) {
41
+ return {
42
+ allowed: false,
43
+ reason: `License '${license}' is not in allowed list`,
44
+ license,
45
+ };
46
+ }
47
+ return { allowed: true, license };
48
+ }
49
+ /**
50
+ * Extract license from version data
51
+ */
52
+ extractLicense(versionData) {
53
+ if (!versionData.license) {
54
+ return null;
55
+ }
56
+ // Handle string license
57
+ if (typeof versionData.license === 'string') {
58
+ return versionData.license;
59
+ }
60
+ // Handle SPDX license object
61
+ if (typeof versionData.license === 'object' && 'type' in versionData.license) {
62
+ return versionData.license.type;
63
+ }
64
+ return null;
65
+ }
66
+ /**
67
+ * Check if license is blocked
68
+ */
69
+ isLicenseBlocked(license) {
70
+ return this.config.blocked.some(blocked => {
71
+ // Exact match
72
+ if (blocked === license) {
73
+ return true;
74
+ }
75
+ // Handle SPDX expressions (e.g., "MIT OR Apache-2.0")
76
+ const licenseTokens = license.split(/\s+(?:OR|AND)\s+/i);
77
+ return licenseTokens.some(token => token.trim() === blocked);
78
+ });
79
+ }
80
+ /**
81
+ * Check if license is allowed
82
+ */
83
+ isLicenseAllowed(license) {
84
+ return this.config.allowed.some(allowed => {
85
+ // Exact match
86
+ if (allowed === license) {
87
+ return true;
88
+ }
89
+ // Handle SPDX expressions (e.g., "MIT OR Apache-2.0")
90
+ const licenseTokens = license.split(/\s+(?:OR|AND)\s+/i);
91
+ return licenseTokens.some(token => token.trim() === allowed);
92
+ });
93
+ }
94
+ /**
95
+ * Get common open source licenses
96
+ */
97
+ static getCommonOpenSourceLicenses() {
98
+ return [
99
+ 'MIT',
100
+ 'Apache-2.0',
101
+ 'BSD-2-Clause',
102
+ 'BSD-3-Clause',
103
+ 'ISC',
104
+ '0BSD',
105
+ 'CC0-1.0',
106
+ 'Unlicense',
107
+ ];
108
+ }
109
+ /**
110
+ * Get common copyleft licenses
111
+ */
112
+ static getCopyleftLicenses() {
113
+ return [
114
+ 'GPL-2.0',
115
+ 'GPL-3.0',
116
+ 'AGPL-3.0',
117
+ 'LGPL-2.1',
118
+ 'LGPL-3.0',
119
+ 'MPL-2.0',
120
+ 'EPL-2.0',
121
+ ];
122
+ }
123
+ }
124
+ exports.LicenseChecker = LicenseChecker;
@@ -0,0 +1,42 @@
1
+ import { Logger } from '@verdaccio/types';
2
+ import { LoggerConfig } from '../types';
3
+ /**
4
+ * Enhanced logger wrapper with configurable log levels and formatting
5
+ */
6
+ export declare class SecurityLogger {
7
+ private verdaccioLogger;
8
+ private config;
9
+ private readonly prefix;
10
+ constructor(verdaccioLogger: Logger, config?: LoggerConfig);
11
+ private shouldLog;
12
+ private formatMessage;
13
+ trace(message: string, ...args: any[]): void;
14
+ debug(message: string, ...args: any[]): void;
15
+ info(message: string, ...args: any[]): void;
16
+ warn(message: string, ...args: any[]): void;
17
+ error(message: string, ...args: any[]): void;
18
+ /**
19
+ * Log package blocking event
20
+ */
21
+ logBlock(packageName: string, version: string, reason: string): void;
22
+ /**
23
+ * Log fallback event
24
+ */
25
+ logFallback(packageName: string, fromVersion: string, toVersion: string, reason?: string): void;
26
+ /**
27
+ * Log CVE detection
28
+ */
29
+ logCVE(packageName: string, version: string, cveId: string, severity: string): void;
30
+ /**
31
+ * Log license violation
32
+ */
33
+ logLicense(packageName: string, version: string, license: string, reason: string): void;
34
+ /**
35
+ * Log publish rejection
36
+ */
37
+ logPublishRejected(packageName: string, version: string, reason: string): void;
38
+ /**
39
+ * Log initialization
40
+ */
41
+ logInit(features: string[]): void;
42
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SecurityLogger = void 0;
4
+ /**
5
+ * Enhanced logger wrapper with configurable log levels and formatting
6
+ */
7
+ class SecurityLogger {
8
+ constructor(verdaccioLogger, config) {
9
+ this.prefix = '[Security Filter]';
10
+ this.verdaccioLogger = verdaccioLogger;
11
+ this.config = {
12
+ level: (config === null || config === void 0 ? void 0 : config.level) || 'info',
13
+ enabled: (config === null || config === void 0 ? void 0 : config.enabled) !== false,
14
+ includeTimestamp: (config === null || config === void 0 ? void 0 : config.includeTimestamp) !== false,
15
+ };
16
+ }
17
+ shouldLog(level) {
18
+ if (!this.config.enabled) {
19
+ return false;
20
+ }
21
+ const levels = ['trace', 'debug', 'info', 'warn', 'error'];
22
+ const configLevel = levels.indexOf(this.config.level);
23
+ const messageLevel = levels.indexOf(level);
24
+ return messageLevel >= configLevel;
25
+ }
26
+ formatMessage(message) {
27
+ const timestamp = this.config.includeTimestamp
28
+ ? `[${new Date().toISOString()}] `
29
+ : '';
30
+ return `${timestamp}${this.prefix} ${message}`;
31
+ }
32
+ trace(message, ...args) {
33
+ if (this.shouldLog('trace')) {
34
+ this.verdaccioLogger.trace(this.formatMessage(message), ...args);
35
+ }
36
+ }
37
+ debug(message, ...args) {
38
+ if (this.shouldLog('debug')) {
39
+ this.verdaccioLogger.debug(this.formatMessage(message), ...args);
40
+ }
41
+ }
42
+ info(message, ...args) {
43
+ if (this.shouldLog('info')) {
44
+ this.verdaccioLogger.info(this.formatMessage(message), ...args);
45
+ }
46
+ }
47
+ warn(message, ...args) {
48
+ if (this.shouldLog('warn')) {
49
+ this.verdaccioLogger.warn(this.formatMessage(message), ...args);
50
+ }
51
+ }
52
+ error(message, ...args) {
53
+ if (this.shouldLog('error')) {
54
+ this.verdaccioLogger.error(this.formatMessage(message), ...args);
55
+ }
56
+ }
57
+ /**
58
+ * Log package blocking event
59
+ */
60
+ logBlock(packageName, version, reason) {
61
+ this.warn(`Blocked ${packageName}@${version} - ${reason}`);
62
+ }
63
+ /**
64
+ * Log fallback event
65
+ */
66
+ logFallback(packageName, fromVersion, toVersion, reason) {
67
+ const reasonText = reason ? ` (${reason})` : '';
68
+ this.info(`Fallback applied: ${packageName}@${fromVersion} -> ${toVersion}${reasonText}`);
69
+ }
70
+ /**
71
+ * Log CVE detection
72
+ */
73
+ logCVE(packageName, version, cveId, severity) {
74
+ this.warn(`CVE detected: ${cveId} [${severity}] in ${packageName}@${version}`);
75
+ }
76
+ /**
77
+ * Log license violation
78
+ */
79
+ logLicense(packageName, version, license, reason) {
80
+ this.warn(`License violation: ${packageName}@${version} has ${license} - ${reason}`);
81
+ }
82
+ /**
83
+ * Log publish rejection
84
+ */
85
+ logPublishRejected(packageName, version, reason) {
86
+ this.warn(`Publish rejected: ${packageName}@${version} - ${reason}`);
87
+ }
88
+ /**
89
+ * Log initialization
90
+ */
91
+ logInit(features) {
92
+ this.info(`Plugin initialized with features: ${features.join(', ')}`);
93
+ }
94
+ }
95
+ exports.SecurityLogger = SecurityLogger;
@@ -0,0 +1,54 @@
1
+ import { MetricsConfig, MetricsData } from '../types';
2
+ /**
3
+ * Metrics collector for tracking security events
4
+ */
5
+ export declare class MetricsCollector {
6
+ private config;
7
+ private metricsBuffer;
8
+ private readonly bufferSize;
9
+ constructor(config?: MetricsConfig);
10
+ /**
11
+ * Record a security event
12
+ */
13
+ record(event: MetricsData): void;
14
+ /**
15
+ * Record a block event
16
+ */
17
+ recordBlock(packageName: string, version: string, reason: string, metadata?: Record<string, any>): void;
18
+ /**
19
+ * Record a fallback event
20
+ */
21
+ recordFallback(packageName: string, fromVersion: string, toVersion: string, reason: string): void;
22
+ /**
23
+ * Record a publish rejection
24
+ */
25
+ recordPublishRejected(packageName: string, version: string, reason: string): void;
26
+ /**
27
+ * Record a CVE detection
28
+ */
29
+ recordCVE(packageName: string, version: string, cveId: string, severity: string): void;
30
+ /**
31
+ * Record a license block
32
+ */
33
+ recordLicenseBlock(packageName: string, version: string, license: string, reason: string): void;
34
+ /**
35
+ * Record a package-too-new event
36
+ */
37
+ recordPackageTooNew(packageName: string, version: string, reason: string, metadata?: Record<string, any>): void;
38
+ /**
39
+ * Flush metrics buffer to output
40
+ */
41
+ flush(): void;
42
+ /**
43
+ * Write metrics to file
44
+ */
45
+ private writeToFile;
46
+ /**
47
+ * Get metrics summary
48
+ */
49
+ getSummary(): Record<string, number>;
50
+ /**
51
+ * Cleanup - flush remaining metrics
52
+ */
53
+ cleanup(): void;
54
+ }