@dimzxzzx07/file-watcher 1.0.0
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/.env +13 -0
- package/.eslintrc.json +128 -0
- package/.prettierrc +18 -0
- package/Dimzxzzx07.png +0 -0
- package/README.md +1024 -0
- package/dist/core/BackupManager.d.ts +25 -0
- package/dist/core/BackupManager.d.ts.map +1 -0
- package/dist/core/BackupManager.js +290 -0
- package/dist/core/BackupManager.js.map +1 -0
- package/dist/core/IntegrityValidator.d.ts +18 -0
- package/dist/core/IntegrityValidator.d.ts.map +1 -0
- package/dist/core/IntegrityValidator.js +212 -0
- package/dist/core/IntegrityValidator.js.map +1 -0
- package/dist/core/SecurityManager.d.ts +40 -0
- package/dist/core/SecurityManager.d.ts.map +1 -0
- package/dist/core/SecurityManager.js +320 -0
- package/dist/core/SecurityManager.js.map +1 -0
- package/dist/core/WatcherEngine.d.ts +44 -0
- package/dist/core/WatcherEngine.d.ts.map +1 -0
- package/dist/core/WatcherEngine.js +470 -0
- package/dist/core/WatcherEngine.js.map +1 -0
- package/dist/crypto/HashGenerator.d.ts +26 -0
- package/dist/crypto/HashGenerator.d.ts.map +1 -0
- package/dist/crypto/HashGenerator.js +220 -0
- package/dist/crypto/HashGenerator.js.map +1 -0
- package/dist/crypto/KeyManager.d.ts +30 -0
- package/dist/crypto/KeyManager.d.ts.map +1 -0
- package/dist/crypto/KeyManager.js +235 -0
- package/dist/crypto/KeyManager.js.map +1 -0
- package/dist/crypto/SignatureValidator.d.ts +11 -0
- package/dist/crypto/SignatureValidator.d.ts.map +1 -0
- package/dist/crypto/SignatureValidator.js +102 -0
- package/dist/crypto/SignatureValidator.js.map +1 -0
- package/dist/detectors/AnomalyDetector.d.ts +24 -0
- package/dist/detectors/AnomalyDetector.d.ts.map +1 -0
- package/dist/detectors/AnomalyDetector.js +209 -0
- package/dist/detectors/AnomalyDetector.js.map +1 -0
- package/dist/detectors/InjectionDetector.d.ts +14 -0
- package/dist/detectors/InjectionDetector.d.ts.map +1 -0
- package/dist/detectors/InjectionDetector.js +204 -0
- package/dist/detectors/InjectionDetector.js.map +1 -0
- package/dist/detectors/PatternMatcher.d.ts +28 -0
- package/dist/detectors/PatternMatcher.d.ts.map +1 -0
- package/dist/detectors/PatternMatcher.js +283 -0
- package/dist/detectors/PatternMatcher.js.map +1 -0
- package/dist/guards/FileGuard.d.ts +35 -0
- package/dist/guards/FileGuard.d.ts.map +1 -0
- package/dist/guards/FileGuard.js +357 -0
- package/dist/guards/FileGuard.js.map +1 -0
- package/dist/guards/MemoryGuard.d.ts +28 -0
- package/dist/guards/MemoryGuard.d.ts.map +1 -0
- package/dist/guards/MemoryGuard.js +256 -0
- package/dist/guards/MemoryGuard.js.map +1 -0
- package/dist/guards/ProcessGuard.d.ts +25 -0
- package/dist/guards/ProcessGuard.d.ts.map +1 -0
- package/dist/guards/ProcessGuard.js +221 -0
- package/dist/guards/ProcessGuard.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +186 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +69 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/Constants.d.ts +407 -0
- package/dist/utils/Constants.d.ts.map +1 -0
- package/dist/utils/Constants.js +505 -0
- package/dist/utils/Constants.js.map +1 -0
- package/dist/utils/Logger.d.ts +45 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +285 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/Validator.d.ts +27 -0
- package/dist/utils/Validator.d.ts.map +1 -0
- package/dist/utils/Validator.js +245 -0
- package/dist/utils/Validator.js.map +1 -0
- package/favicon.png +0 -0
- package/jest.config.js +69 -0
- package/package.json +69 -0
- package/src/core/BackupManager.ts +305 -0
- package/src/core/IntegrityValidator.ts +200 -0
- package/src/core/SecurityManager.ts +348 -0
- package/src/core/WatcherEngine.ts +537 -0
- package/src/crypto/HashGenerator.ts +234 -0
- package/src/crypto/KeyManager.ts +249 -0
- package/src/crypto/SignatureValidator.ts +76 -0
- package/src/detectors/AnomalyDetector.ts +247 -0
- package/src/detectors/InjectionDetector.ts +233 -0
- package/src/detectors/PatternMatcher.ts +319 -0
- package/src/guards/FileGuard.ts +385 -0
- package/src/guards/MemoryGuard.ts +263 -0
- package/src/guards/ProcessGuard.ts +219 -0
- package/src/index.ts +189 -0
- package/src/types/index.ts +72 -0
- package/src/utils/Constants.ts +532 -0
- package/src/utils/Logger.ts +279 -0
- package/src/utils/Validator.ts +248 -0
- package/tests/setup.ts +80 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
export enum LogLevel {
|
|
5
|
+
DEBUG = 0,
|
|
6
|
+
INFO = 1,
|
|
7
|
+
WARNING = 2,
|
|
8
|
+
ERROR = 3,
|
|
9
|
+
CRITICAL = 4,
|
|
10
|
+
EMERGENCY = 5
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Logger {
|
|
14
|
+
private static instance: Logger;
|
|
15
|
+
private readonly logDir: string;
|
|
16
|
+
private readonly logFile: string;
|
|
17
|
+
private readonly errorLogFile: string;
|
|
18
|
+
private readonly securityLogFile: string;
|
|
19
|
+
private currentLogLevel: LogLevel = LogLevel.INFO;
|
|
20
|
+
private readonly maxLogSize: number = 10 * 1024 * 1024;
|
|
21
|
+
private readonly maxLogFiles: number = 5;
|
|
22
|
+
private logQueue: string[] = [];
|
|
23
|
+
private isWriting: boolean = false;
|
|
24
|
+
|
|
25
|
+
private constructor() {
|
|
26
|
+
this.logDir = path.join(process.cwd(), 'logs');
|
|
27
|
+
this.logFile = path.join(this.logDir, 'app.log');
|
|
28
|
+
this.errorLogFile = path.join(this.logDir, 'error.log');
|
|
29
|
+
this.securityLogFile = path.join(this.logDir, 'security.log');
|
|
30
|
+
|
|
31
|
+
this.initializeLogging();
|
|
32
|
+
this.startLogProcessor();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static getInstance(): Logger {
|
|
36
|
+
if (!Logger.instance) {
|
|
37
|
+
Logger.instance = new Logger();
|
|
38
|
+
}
|
|
39
|
+
return Logger.instance;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async initializeLogging(): Promise<void> {
|
|
43
|
+
try {
|
|
44
|
+
await fs.mkdir(this.logDir, { recursive: true, mode: 0o700 });
|
|
45
|
+
|
|
46
|
+
await this.initializeLogFile(this.logFile);
|
|
47
|
+
await this.initializeLogFile(this.errorLogFile);
|
|
48
|
+
await this.initializeLogFile(this.securityLogFile);
|
|
49
|
+
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to initialize logging:', error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private async initializeLogFile(filePath: string): Promise<void> {
|
|
56
|
+
try {
|
|
57
|
+
const exists = await this.fileExists(filePath);
|
|
58
|
+
if (!exists) {
|
|
59
|
+
const header = `# Log file created at ${new Date().toISOString()}\n`;
|
|
60
|
+
await fs.writeFile(filePath, header);
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`Failed to initialize log file: ${filePath}`, error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private startLogProcessor(): void {
|
|
68
|
+
setInterval(() => {
|
|
69
|
+
this.processLogQueue();
|
|
70
|
+
}, 1000);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private async processLogQueue(): Promise<void> {
|
|
74
|
+
if (this.isWriting || this.logQueue.length === 0) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.isWriting = true;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const entries = [...this.logQueue];
|
|
82
|
+
this.logQueue = [];
|
|
83
|
+
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
await this.writeLog(entry);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await this.rotateLogIfNeeded(this.logFile);
|
|
89
|
+
await this.rotateLogIfNeeded(this.errorLogFile);
|
|
90
|
+
await this.rotateLogIfNeeded(this.securityLogFile);
|
|
91
|
+
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Failed to process log queue:', error);
|
|
94
|
+
} finally {
|
|
95
|
+
this.isWriting = false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private async writeLog(entry: string): Promise<void> {
|
|
100
|
+
try {
|
|
101
|
+
let logFile = this.logFile;
|
|
102
|
+
|
|
103
|
+
if (entry.includes('"level":"ERROR"') || entry.includes('"level":"CRITICAL"')) {
|
|
104
|
+
logFile = this.errorLogFile;
|
|
105
|
+
} else if (entry.includes('"level":"WARNING"')) {
|
|
106
|
+
logFile = this.securityLogFile;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
await fs.appendFile(logFile, entry + '\n');
|
|
110
|
+
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Failed to write log:', error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async rotateLogIfNeeded(logFile: string): Promise<void> {
|
|
117
|
+
try {
|
|
118
|
+
const stats = await fs.stat(logFile);
|
|
119
|
+
|
|
120
|
+
if (stats.size > this.maxLogSize) {
|
|
121
|
+
await this.rotateLog(logFile);
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private async rotateLog(logFile: string): Promise<void> {
|
|
128
|
+
for (let i = this.maxLogFiles - 1; i > 0; i--) {
|
|
129
|
+
const oldFile = `${logFile}.${i}`;
|
|
130
|
+
const newFile = `${logFile}.${i + 1}`;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
await fs.rename(oldFile, newFile);
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await fs.rename(logFile, `${logFile}.1`);
|
|
139
|
+
await this.initializeLogFile(logFile);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private formatLogEntry(level: LogLevel, message: string, metadata?: any): string {
|
|
143
|
+
const entry = {
|
|
144
|
+
timestamp: new Date().toISOString(),
|
|
145
|
+
level: LogLevel[level],
|
|
146
|
+
pid: process.pid,
|
|
147
|
+
message,
|
|
148
|
+
metadata: metadata || {}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return JSON.stringify(entry);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private queueLog(level: LogLevel, message: string, metadata?: any): void {
|
|
155
|
+
const entry = this.formatLogEntry(level, message, metadata);
|
|
156
|
+
this.logQueue.push(entry);
|
|
157
|
+
|
|
158
|
+
if (process.env.NODE_ENV === 'development') {
|
|
159
|
+
const consoleMethod = this.getConsoleMethod(level);
|
|
160
|
+
console[consoleMethod](`[${LogLevel[level]}] ${message}`, metadata || '');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private getConsoleMethod(level: LogLevel): 'log' | 'info' | 'warn' | 'error' {
|
|
165
|
+
switch (level) {
|
|
166
|
+
case LogLevel.DEBUG:
|
|
167
|
+
case LogLevel.INFO:
|
|
168
|
+
return 'log';
|
|
169
|
+
case LogLevel.WARNING:
|
|
170
|
+
return 'warn';
|
|
171
|
+
case LogLevel.ERROR:
|
|
172
|
+
case LogLevel.CRITICAL:
|
|
173
|
+
case LogLevel.EMERGENCY:
|
|
174
|
+
return 'error';
|
|
175
|
+
default:
|
|
176
|
+
return 'log';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public debug(message: string, metadata?: any): void {
|
|
181
|
+
if (this.currentLogLevel <= LogLevel.DEBUG) {
|
|
182
|
+
this.queueLog(LogLevel.DEBUG, message, metadata);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public info(message: string, metadata?: any): void {
|
|
187
|
+
if (this.currentLogLevel <= LogLevel.INFO) {
|
|
188
|
+
this.queueLog(LogLevel.INFO, message, metadata);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public warning(message: string, metadata?: any): void {
|
|
193
|
+
if (this.currentLogLevel <= LogLevel.WARNING) {
|
|
194
|
+
this.queueLog(LogLevel.WARNING, message, metadata);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public error(message: string, metadata?: any): void {
|
|
199
|
+
if (this.currentLogLevel <= LogLevel.ERROR) {
|
|
200
|
+
this.queueLog(LogLevel.ERROR, message, metadata);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public critical(message: string, metadata?: any): void {
|
|
205
|
+
if (this.currentLogLevel <= LogLevel.CRITICAL) {
|
|
206
|
+
this.queueLog(LogLevel.CRITICAL, message, metadata);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
public emergency(message: string, metadata?: any): void {
|
|
211
|
+
if (this.currentLogLevel <= LogLevel.EMERGENCY) {
|
|
212
|
+
this.queueLog(LogLevel.EMERGENCY, message, metadata);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public setLogLevel(level: LogLevel): void {
|
|
217
|
+
this.currentLogLevel = level;
|
|
218
|
+
this.info(`Log level changed to ${LogLevel[level]}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public async getLogs(level?: LogLevel, limit: number = 100): Promise<string[]> {
|
|
222
|
+
try {
|
|
223
|
+
const content = await fs.readFile(this.logFile, 'utf8');
|
|
224
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
225
|
+
|
|
226
|
+
if (level !== undefined) {
|
|
227
|
+
const levelName = LogLevel[level];
|
|
228
|
+
return lines
|
|
229
|
+
.filter(line => line.includes(`"level":"${levelName}"`))
|
|
230
|
+
.slice(-limit);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return lines.slice(-limit);
|
|
234
|
+
} catch {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public async getSecurityLogs(limit: number = 100): Promise<string[]> {
|
|
240
|
+
try {
|
|
241
|
+
const content = await fs.readFile(this.securityLogFile, 'utf8');
|
|
242
|
+
return content.split('\n').filter(line => line.trim()).slice(-limit);
|
|
243
|
+
} catch {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public async clearLogs(): Promise<void> {
|
|
249
|
+
try {
|
|
250
|
+
await fs.writeFile(this.logFile, '');
|
|
251
|
+
await fs.writeFile(this.errorLogFile, '');
|
|
252
|
+
await fs.writeFile(this.securityLogFile, '');
|
|
253
|
+
|
|
254
|
+
this.info('Logs cleared');
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('Failed to clear logs:', error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private async fileExists(filePath: string): Promise<boolean> {
|
|
261
|
+
try {
|
|
262
|
+
await fs.access(filePath);
|
|
263
|
+
return true;
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public getStatus(): any {
|
|
270
|
+
return {
|
|
271
|
+
logLevel: LogLevel[this.currentLogLevel],
|
|
272
|
+
logDir: this.logDir,
|
|
273
|
+
queueSize: this.logQueue.length,
|
|
274
|
+
isWriting: this.isWriting,
|
|
275
|
+
maxLogSize: this.maxLogSize,
|
|
276
|
+
maxLogFiles: this.maxLogFiles
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { SecurityConfig } from '../types';
|
|
4
|
+
import { Logger } from './Logger';
|
|
5
|
+
|
|
6
|
+
export class Validator {
|
|
7
|
+
private static instance: Validator;
|
|
8
|
+
private readonly logger: Logger;
|
|
9
|
+
private readonly config: SecurityConfig;
|
|
10
|
+
|
|
11
|
+
private constructor(config: SecurityConfig) {
|
|
12
|
+
this.logger = Logger.getInstance();
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public static getInstance(config?: SecurityConfig): Validator {
|
|
17
|
+
if (!Validator.instance && config) {
|
|
18
|
+
Validator.instance = new Validator(config);
|
|
19
|
+
}
|
|
20
|
+
return Validator.instance;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public validateFilePath(filePath: string): boolean {
|
|
24
|
+
try {
|
|
25
|
+
const normalized = path.normalize(filePath);
|
|
26
|
+
|
|
27
|
+
if (normalized.includes('..')) {
|
|
28
|
+
this.logger.warning('Path traversal detected', { filePath });
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (filePath.includes('\0')) {
|
|
33
|
+
this.logger.warning('Null byte in path', { filePath });
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (/[\x00-\x1F\x7F]/.test(filePath)) {
|
|
38
|
+
this.logger.warning('Control characters in path', { filePath });
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const suspiciousPatterns = [
|
|
43
|
+
/^\./,
|
|
44
|
+
/^\/etc/,
|
|
45
|
+
/^\/var/,
|
|
46
|
+
/^\/proc/,
|
|
47
|
+
/^\/dev/,
|
|
48
|
+
/^\/sys/,
|
|
49
|
+
/^\/boot/,
|
|
50
|
+
/^\/root/,
|
|
51
|
+
/^\/bin/,
|
|
52
|
+
/^\/sbin/,
|
|
53
|
+
/^\/usr/,
|
|
54
|
+
/^\/lib/
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
for (const pattern of suspiciousPatterns) {
|
|
58
|
+
if (pattern.test(filePath)) {
|
|
59
|
+
this.logger.warning('Suspicious path pattern', { filePath, pattern });
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
this.logger.error('Path validation error', { error, filePath });
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public validateFileName(fileName: string): boolean {
|
|
72
|
+
if (fileName.length === 0 || fileName.length > 255) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const invalidChars = /[<>:"/\\|?*\x00-\x1F]/g;
|
|
77
|
+
if (invalidChars.test(fileName)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
|
|
82
|
+
if (reservedNames.test(fileName)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (fileName.startsWith('.') && !fileName.startsWith('.')) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (/[\u200B-\u200D\uFEFF]/.test(fileName)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public validateFileExtension(extension: string): boolean {
|
|
98
|
+
const ext = extension.toLowerCase();
|
|
99
|
+
|
|
100
|
+
if (this.config.allowedExtensions.length > 0) {
|
|
101
|
+
return this.config.allowedExtensions.includes(ext);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const dangerousExtensions = [
|
|
105
|
+
'.exe', '.dll', '.so', '.dylib', '.bin',
|
|
106
|
+
'.sh', '.bash', '.cmd', '.bat', '.ps1',
|
|
107
|
+
'.vbs', '.js', '.jar', '.class', '.py',
|
|
108
|
+
'.rb', '.pl', '.php', '.asp', '.aspx',
|
|
109
|
+
'.jsp', '.cgi', '.swf', '.apk', '.app',
|
|
110
|
+
'.deb', '.rpm', '.msi'
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
return !dangerousExtensions.includes(ext);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public validateFileSize(size: number): boolean {
|
|
117
|
+
if (size <= 0) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (size > this.config.maxFileSize) {
|
|
122
|
+
this.logger.warning('File too large', { size, max: this.config.maxFileSize });
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public validateHash(hash: string, algorithm: string = 'sha512'): boolean {
|
|
130
|
+
const hashLengths: Record<string, number> = {
|
|
131
|
+
md5: 32,
|
|
132
|
+
sha1: 40,
|
|
133
|
+
sha256: 64,
|
|
134
|
+
sha384: 96,
|
|
135
|
+
sha512: 128
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const expectedLength = hashLengths[algorithm];
|
|
139
|
+
if (!expectedLength) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (hash.length !== expectedLength) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return /^[0-9a-f]+$/i.test(hash);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public validateEmail(email: string): boolean {
|
|
151
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
152
|
+
return emailRegex.test(email);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
public validateUrl(url: string): boolean {
|
|
156
|
+
try {
|
|
157
|
+
new URL(url);
|
|
158
|
+
return true;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
public validateIpAddress(ip: string): boolean {
|
|
165
|
+
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
166
|
+
const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
|
167
|
+
|
|
168
|
+
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public validatePort(port: number): boolean {
|
|
172
|
+
return port > 0 && port < 65536;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public validateTimestamp(timestamp: number): boolean {
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
const oneYearAgo = now - 365 * 24 * 60 * 60 * 1000;
|
|
178
|
+
const oneYearFromNow = now + 365 * 24 * 60 * 60 * 1000;
|
|
179
|
+
|
|
180
|
+
return timestamp > oneYearAgo && timestamp < oneYearFromNow;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public validatePermission(mode: number): boolean {
|
|
184
|
+
return mode >= 0 && mode <= 0o777;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public validateJson(json: string): boolean {
|
|
188
|
+
try {
|
|
189
|
+
JSON.parse(json);
|
|
190
|
+
return true;
|
|
191
|
+
} catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public validateBase64(str: string): boolean {
|
|
197
|
+
try {
|
|
198
|
+
const encoded = Buffer.from(str, 'base64').toString('base64');
|
|
199
|
+
return encoded === str;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public validateHex(str: string): boolean {
|
|
206
|
+
return /^[0-9a-fA-F]+$/.test(str);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public validateAscii(str: string): boolean {
|
|
210
|
+
return /^[\x00-\x7F]*$/.test(str);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public validateUtf8(buffer: Buffer): boolean {
|
|
214
|
+
try {
|
|
215
|
+
buffer.toString('utf8');
|
|
216
|
+
return true;
|
|
217
|
+
} catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public validateChecksum(data: Buffer, checksum: string): boolean {
|
|
223
|
+
const hash = crypto.createHash('sha256')
|
|
224
|
+
.update(data)
|
|
225
|
+
.digest('hex');
|
|
226
|
+
|
|
227
|
+
return hash === checksum;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public validateConfig(config: any): boolean {
|
|
231
|
+
const requiredFields = [
|
|
232
|
+
'watchDir',
|
|
233
|
+
'hashAlgorithm',
|
|
234
|
+
'backupDir',
|
|
235
|
+
'maxFileSize',
|
|
236
|
+
'scanInterval'
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
for (const field of requiredFields) {
|
|
240
|
+
if (!(field in config)) {
|
|
241
|
+
this.logger.error(`Missing required config field: ${field}`);
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
}
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import { Logger } from '../src/utils/Logger';
|
|
4
|
+
jest.setTimeout(30000);
|
|
5
|
+
jest.mock('../src/utils/Logger', () => {
|
|
6
|
+
return {
|
|
7
|
+
Logger: {
|
|
8
|
+
getInstance: jest.fn().mockReturnValue({
|
|
9
|
+
debug: jest.fn(),
|
|
10
|
+
info: jest.fn(),
|
|
11
|
+
warning: jest.fn(),
|
|
12
|
+
error: jest.fn(),
|
|
13
|
+
critical: jest.fn(),
|
|
14
|
+
emergency: jest.fn(),
|
|
15
|
+
setLogLevel: jest.fn(),
|
|
16
|
+
getLogs: jest.fn().mockResolvedValue([]),
|
|
17
|
+
getSecurityLogs: jest.fn().mockResolvedValue([]),
|
|
18
|
+
clearLogs: jest.fn().mockResolvedValue(undefined),
|
|
19
|
+
getStatus: jest.fn().mockReturnValue({})
|
|
20
|
+
})
|
|
21
|
+
},
|
|
22
|
+
LogLevel: {
|
|
23
|
+
DEBUG: 0,
|
|
24
|
+
INFO: 1,
|
|
25
|
+
WARNING: 2,
|
|
26
|
+
ERROR: 3,
|
|
27
|
+
CRITICAL: 4,
|
|
28
|
+
EMERGENCY: 5
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const testDir = path.join(__dirname, 'temp');
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
try {
|
|
37
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Failed to create test directory:', error);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterAll(async () => {
|
|
44
|
+
try {
|
|
45
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Failed to remove test directory:', error);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
global.createTestFile = async (filename: string, content: string = 'test') => {
|
|
52
|
+
const filePath = path.join(testDir, filename);
|
|
53
|
+
await fs.writeFile(filePath, content);
|
|
54
|
+
return filePath;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
global.createTestDirectory = async (dirname: string) => {
|
|
58
|
+
const dirPath = path.join(testDir, dirname);
|
|
59
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
60
|
+
return dirPath;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
global.cleanupTestFiles = async () => {
|
|
64
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
65
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
declare global {
|
|
69
|
+
function createTestFile(filename: string, content?: string): Promise<string>;
|
|
70
|
+
function createTestDirectory(dirname: string): Promise<string>;
|
|
71
|
+
function cleanupTestFiles(): Promise<void>;
|
|
72
|
+
|
|
73
|
+
namespace NodeJS {
|
|
74
|
+
interface Global {
|
|
75
|
+
createTestFile: typeof createTestFile;
|
|
76
|
+
createTestDirectory: typeof createTestDirectory;
|
|
77
|
+
cleanupTestFiles: typeof cleanupTestFiles;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitAny": true,
|
|
10
|
+
"strictNullChecks": true,
|
|
11
|
+
"strictFunctionTypes": true,
|
|
12
|
+
"strictBindCallApply": true,
|
|
13
|
+
"strictPropertyInitialization": true,
|
|
14
|
+
"noImplicitThis": true,
|
|
15
|
+
"alwaysStrict": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noImplicitReturns": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"noUncheckedIndexedAccess": true,
|
|
21
|
+
"noImplicitOverride": true,
|
|
22
|
+
"allowUnreachableCode": false,
|
|
23
|
+
"allowUnusedLabels": false,
|
|
24
|
+
"esModuleInterop": true,
|
|
25
|
+
"skipLibCheck": true,
|
|
26
|
+
"forceConsistentCasingInFileNames": true,
|
|
27
|
+
"resolveJsonModule": true,
|
|
28
|
+
"declaration": true,
|
|
29
|
+
"declarationMap": true,
|
|
30
|
+
"sourceMap": true,
|
|
31
|
+
"inlineSources": true,
|
|
32
|
+
"experimentalDecorators": true,
|
|
33
|
+
"emitDecoratorMetadata": true,
|
|
34
|
+
"moduleResolution": "node",
|
|
35
|
+
"allowSyntheticDefaultImports": true,
|
|
36
|
+
"removeComments": false,
|
|
37
|
+
"preserveConstEnums": true,
|
|
38
|
+
"stripInternal": true
|
|
39
|
+
},
|
|
40
|
+
"include": ["src/**/*"],
|
|
41
|
+
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts"]
|
|
42
|
+
}
|