vladx 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/README.md +256 -0
- package/bin/cli.js +486 -0
- package/bin/vlad.js +539 -0
- package/bin/vladpm.js +710 -0
- package/bin/vladx.js +491 -0
- package/package.json +57 -0
- package/src/engine/jit-compiler.js +285 -0
- package/src/engine/vladx-engine.js +941 -0
- package/src/index.js +44 -0
- package/src/interpreter/interpreter.js +2114 -0
- package/src/lexer/lexer.js +658 -0
- package/src/lexer/optimized-lexer.js +106 -0
- package/src/lexer/regex-cache.js +83 -0
- package/src/parser/ast-nodes.js +472 -0
- package/src/parser/parser.js +1408 -0
- package/src/runtime/advanced-type-system.js +209 -0
- package/src/runtime/async-manager.js +252 -0
- package/src/runtime/builtins.js +143 -0
- package/src/runtime/bundler.js +422 -0
- package/src/runtime/cache-manager.js +126 -0
- package/src/runtime/data-structures.js +612 -0
- package/src/runtime/debugger.js +260 -0
- package/src/runtime/enhanced-module-system.js +196 -0
- package/src/runtime/environment-enhanced.js +272 -0
- package/src/runtime/environment.js +140 -0
- package/src/runtime/event-emitter.js +232 -0
- package/src/runtime/formatter.js +280 -0
- package/src/runtime/functional.js +359 -0
- package/src/runtime/io-operations.js +390 -0
- package/src/runtime/linter.js +374 -0
- package/src/runtime/logging.js +314 -0
- package/src/runtime/minifier.js +242 -0
- package/src/runtime/module-system.js +377 -0
- package/src/runtime/network-operations.js +373 -0
- package/src/runtime/profiler.js +295 -0
- package/src/runtime/repl.js +336 -0
- package/src/runtime/security-manager.js +244 -0
- package/src/runtime/source-map-generator.js +208 -0
- package/src/runtime/test-runner.js +394 -0
- package/src/runtime/transformer.js +277 -0
- package/src/runtime/type-system.js +244 -0
- package/src/runtime/vladx-object.js +250 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REPL — Улучшенная интерактивная консоль
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { VladXObject } from './vladx-object.js';
|
|
6
|
+
import { readFileSync, existsSync } from 'fs';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
|
|
9
|
+
export class REPL {
|
|
10
|
+
constructor(engine, options = {}) {
|
|
11
|
+
this.engine = engine;
|
|
12
|
+
this.input = options.input || process.stdin;
|
|
13
|
+
this.output = options.output || process.stdout;
|
|
14
|
+
this.history = [];
|
|
15
|
+
this.historyIndex = -1;
|
|
16
|
+
this.historyFile = options.historyFile || join(process.env.HOME || process.env.USERPROFILE || '', '.vladx_history');
|
|
17
|
+
this.maxHistorySize = options.maxHistorySize || 1000;
|
|
18
|
+
this.prompt = options.prompt || 'vladx> ';
|
|
19
|
+
this.multilinePrompt = options.multilinePrompt || '... ';
|
|
20
|
+
this.specialCommands = {
|
|
21
|
+
'.clear': this.clear.bind(this),
|
|
22
|
+
'.exit': this.exit.bind(this),
|
|
23
|
+
'.help': this.help.bind(this),
|
|
24
|
+
'.history': this.showHistory.bind(this),
|
|
25
|
+
'.load': this.loadFile.bind(this),
|
|
26
|
+
'.save': this.saveFile.bind(this),
|
|
27
|
+
'.debug': this.toggleDebug.bind(this),
|
|
28
|
+
'.profile': this.toggleProfile.bind(this),
|
|
29
|
+
'.version': this.version.bind(this)
|
|
30
|
+
};
|
|
31
|
+
this.debugMode = false;
|
|
32
|
+
this.profileMode = false;
|
|
33
|
+
this.context = {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Запустить REPL
|
|
38
|
+
*/
|
|
39
|
+
async start() {
|
|
40
|
+
const readline = await import('readline');
|
|
41
|
+
this.rl = readline.createInterface({
|
|
42
|
+
input: this.input,
|
|
43
|
+
output: this.output,
|
|
44
|
+
prompt: this.prompt
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.loadHistory();
|
|
48
|
+
|
|
49
|
+
this.rl.on('line', async (line) => {
|
|
50
|
+
await this.handleInput(line.trim());
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.rl.on('close', () => {
|
|
54
|
+
this.saveHistory();
|
|
55
|
+
this.output.write('\nДо встречи!\n');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.rl.on('SIGINT', () => {
|
|
59
|
+
this.output.write('^C\n');
|
|
60
|
+
this.rl.prompt();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
this.rl.setPrompt(this.prompt);
|
|
64
|
+
this.rl.prompt();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Обработка ввода
|
|
69
|
+
*/
|
|
70
|
+
async handleInput(line) {
|
|
71
|
+
if (!line) {
|
|
72
|
+
this.rl.prompt();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Специальные команды
|
|
77
|
+
if (line.startsWith('.')) {
|
|
78
|
+
await this.handleSpecialCommand(line);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// История (стрелки вверх/вниз)
|
|
83
|
+
if (line === '\x1B[A') { // Up arrow
|
|
84
|
+
if (this.historyIndex < this.history.length - 1) {
|
|
85
|
+
this.historyIndex++;
|
|
86
|
+
this.rl.write(null, { ctrl: true, name: 'u' });
|
|
87
|
+
this.rl.write(this.history[this.history.length - 1 - this.historyIndex]);
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (line === '\x1B[B') { // Down arrow
|
|
93
|
+
if (this.historyIndex > -1) {
|
|
94
|
+
this.historyIndex--;
|
|
95
|
+
this.rl.write(null, { ctrl: true, name: 'u' });
|
|
96
|
+
if (this.historyIndex >= 0) {
|
|
97
|
+
this.rl.write(this.history[this.history.length - 1 - this.historyIndex]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Добавить в историю
|
|
104
|
+
this.addToHistory(line);
|
|
105
|
+
this.historyIndex = -1;
|
|
106
|
+
|
|
107
|
+
// Выполнить код
|
|
108
|
+
try {
|
|
109
|
+
const result = await this.engine.execute(line, {
|
|
110
|
+
filename: '<repl>',
|
|
111
|
+
context: this.context
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (result !== undefined) {
|
|
115
|
+
this.output.write(`→ ${this.formatResult(result)}\n`);
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
this.output.write(`\x1b[31mОшибка:\x1b[0m ${this.formatError(error)}\n`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.rl.prompt();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Обработка специальных команд
|
|
126
|
+
*/
|
|
127
|
+
async handleSpecialCommand(line) {
|
|
128
|
+
const parts = line.split(' ');
|
|
129
|
+
const command = parts[0];
|
|
130
|
+
const args = parts.slice(1);
|
|
131
|
+
|
|
132
|
+
if (this.specialCommands[command]) {
|
|
133
|
+
try {
|
|
134
|
+
await this.specialCommands[command](...args);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
this.output.write(`\x1b[31mОшибка:\x1b[0m ${error.message}\n`);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
this.output.write(`Неизвестная команда: ${command}. Введите .help для справки.\n`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.rl.prompt();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Очистить контекст
|
|
147
|
+
*/
|
|
148
|
+
clear() {
|
|
149
|
+
this.context = {};
|
|
150
|
+
this.output.write('Контекст очищен.\n');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Выйти из REPL
|
|
155
|
+
*/
|
|
156
|
+
exit() {
|
|
157
|
+
this.saveHistory();
|
|
158
|
+
this.rl.close();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Показать справку
|
|
163
|
+
*/
|
|
164
|
+
help() {
|
|
165
|
+
const helpText = `
|
|
166
|
+
Доступные команды:
|
|
167
|
+
.clear Очистить контекст REPL
|
|
168
|
+
.exit Выйти из REPL
|
|
169
|
+
.help Показать эту справку
|
|
170
|
+
.history Показать историю команд
|
|
171
|
+
.load [файл] Загрузить и выполнить файл
|
|
172
|
+
.save [файл] Сохранить контекст в файл
|
|
173
|
+
.debug Включить/выключить отладочный режим
|
|
174
|
+
.profile Включить/выключить профилирование
|
|
175
|
+
.version Показать версию VladX
|
|
176
|
+
|
|
177
|
+
Используйте стрелки ↑↓ для навигации по истории.
|
|
178
|
+
`.trim();
|
|
179
|
+
this.output.write(helpText + '\n');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Показать историю
|
|
184
|
+
*/
|
|
185
|
+
showHistory() {
|
|
186
|
+
this.output.write('История команд:\n');
|
|
187
|
+
this.history.forEach((line, i) => {
|
|
188
|
+
this.output.write(` ${i + 1}: ${line}\n`);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Загрузить файл
|
|
194
|
+
*/
|
|
195
|
+
async loadFile(filename) {
|
|
196
|
+
try {
|
|
197
|
+
const result = await this.engine.executeFile(filename);
|
|
198
|
+
this.output.write(`Загружен файл: ${filename}\n`);
|
|
199
|
+
if (result !== undefined) {
|
|
200
|
+
this.output.write(`→ ${this.formatResult(result)}\n`);
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
this.output.write(`\x1b[31mОшибка загрузки файла:\x1b[0m ${error.message}\n`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Сохранить контекст
|
|
209
|
+
*/
|
|
210
|
+
saveFile(filename) {
|
|
211
|
+
try {
|
|
212
|
+
const content = JSON.stringify(this.context, null, 2);
|
|
213
|
+
require('fs').writeFileSync(filename, content, 'utf-8');
|
|
214
|
+
this.output.write(`Контекст сохранен в: ${filename}\n`);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
this.output.write(`\x1b[31mОшибка сохранения:\x1b[0m ${error.message}\n`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Переключить отладочный режим
|
|
222
|
+
*/
|
|
223
|
+
toggleDebug() {
|
|
224
|
+
this.debugMode = !this.debugMode;
|
|
225
|
+
this.engine.debug = this.debugMode;
|
|
226
|
+
this.output.write(`Отладочный режим: ${this.debugMode ? 'включен' : 'выключен'}\n`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Переключить профилирование
|
|
231
|
+
*/
|
|
232
|
+
toggleProfile() {
|
|
233
|
+
this.profileMode = !this.profileMode;
|
|
234
|
+
this.output.write(`Профилирование: ${this.profileMode ? 'включено' : 'выключено'}\n`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Показать версию
|
|
239
|
+
*/
|
|
240
|
+
version() {
|
|
241
|
+
this.output.write('VladX 1.0.0\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Добавить в историю
|
|
246
|
+
*/
|
|
247
|
+
addToHistory(line) {
|
|
248
|
+
if (line.length === 0) return;
|
|
249
|
+
|
|
250
|
+
// Дубликаты не добавляем
|
|
251
|
+
if (this.history.length > 0 && this.history[this.history.length - 1] === line) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.history.push(line);
|
|
256
|
+
|
|
257
|
+
// Ограничение размера
|
|
258
|
+
if (this.history.length > this.maxHistorySize) {
|
|
259
|
+
this.history.shift();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Загрузить историю из файла
|
|
265
|
+
*/
|
|
266
|
+
loadHistory() {
|
|
267
|
+
try {
|
|
268
|
+
if (existsSync(this.historyFile)) {
|
|
269
|
+
const content = readFileSync(this.historyFile, 'utf-8');
|
|
270
|
+
this.history = content.split('\n').filter(line => line.length > 0);
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
// Игнорируем ошибки загрузки истории
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Сохранить историю в файл
|
|
279
|
+
*/
|
|
280
|
+
saveHistory() {
|
|
281
|
+
try {
|
|
282
|
+
require('fs').writeFileSync(this.historyFile, this.history.join('\n'), 'utf-8');
|
|
283
|
+
} catch (error) {
|
|
284
|
+
// Игнорируем ошибки сохранения истории
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Форматировать результат
|
|
290
|
+
*/
|
|
291
|
+
formatResult(result) {
|
|
292
|
+
if (result === null || result === undefined) {
|
|
293
|
+
return 'ничто';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (result && result.type !== undefined) {
|
|
297
|
+
return result.toString();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (typeof result === 'object') {
|
|
301
|
+
return JSON.stringify(result, null, 2);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return String(result);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Форматировать ошибку
|
|
309
|
+
*/
|
|
310
|
+
formatError(error) {
|
|
311
|
+
if (error.stack) {
|
|
312
|
+
return error.stack;
|
|
313
|
+
}
|
|
314
|
+
return error.message || String(error);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Установить пользовательский промпт
|
|
319
|
+
*/
|
|
320
|
+
setPrompt(prompt) {
|
|
321
|
+
this.prompt = prompt;
|
|
322
|
+
this.rl.setPrompt(prompt);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Остановить REPL
|
|
327
|
+
*/
|
|
328
|
+
stop() {
|
|
329
|
+
if (this.rl) {
|
|
330
|
+
this.saveHistory();
|
|
331
|
+
this.rl.close();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export default REPL;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecurityManager — Управление безопасностью и песочницей
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join, resolve, normalize } from 'path';
|
|
6
|
+
import { existsSync, statSync } from 'fs';
|
|
7
|
+
|
|
8
|
+
export class SecurityManager {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.enabled = options.enabled !== false;
|
|
11
|
+
this.allowedPaths = options.allowedPaths || [];
|
|
12
|
+
this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB
|
|
13
|
+
this.maxExecutionTime = options.maxExecutionTime || 30000; // 30s
|
|
14
|
+
this.maxMemory = options.maxMemory || 512 * 1024 * 1024; // 512MB
|
|
15
|
+
this.allowedDomains = options.allowedDomains || [];
|
|
16
|
+
this.blockedDomains = options.blockedDomains || [];
|
|
17
|
+
this.prototypePollutionProtection = options.prototypePollutionProtection !== false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Проверка безопасности пути
|
|
22
|
+
*/
|
|
23
|
+
checkPath(path, basePath = process.cwd()) {
|
|
24
|
+
if (!this.enabled) return true;
|
|
25
|
+
|
|
26
|
+
const resolvedPath = resolve(basePath, path);
|
|
27
|
+
const normalizedPath = normalize(resolvedPath);
|
|
28
|
+
|
|
29
|
+
// Проверка на path traversal
|
|
30
|
+
if (normalizedPath.includes('..')) {
|
|
31
|
+
throw new Error('Доступ к родительским директориям запрещен: ' + path);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Проверка на разрешенные пути
|
|
35
|
+
if (this.allowedPaths.length > 0) {
|
|
36
|
+
const isAllowed = this.allowedPaths.some(allowedPath => {
|
|
37
|
+
const resolvedAllowed = resolve(allowedPath);
|
|
38
|
+
return normalizedPath.startsWith(resolvedAllowed);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!isAllowed) {
|
|
42
|
+
throw new Error('Доступ к пути запрещен: ' + path);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Проверка размера файла
|
|
51
|
+
*/
|
|
52
|
+
checkFileSize(filePath) {
|
|
53
|
+
if (!this.enabled) return true;
|
|
54
|
+
|
|
55
|
+
const stats = statSync(filePath);
|
|
56
|
+
if (stats.size > this.maxFileSize) {
|
|
57
|
+
throw new Error(`Размер файла превышает лимит: ${stats.size} > ${this.maxFileSize}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Проверка безопасности URL
|
|
65
|
+
*/
|
|
66
|
+
checkURL(url) {
|
|
67
|
+
if (!this.enabled) return true;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const urlObj = new URL(url);
|
|
71
|
+
const domain = urlObj.hostname;
|
|
72
|
+
|
|
73
|
+
// Проверка заблокированных доменов
|
|
74
|
+
if (this.blockedDomains.includes(domain)) {
|
|
75
|
+
throw new Error('Домен заблокирован: ' + domain);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Проверка разрешенных доменов
|
|
79
|
+
if (this.allowedDomains.length > 0 && !this.allowedDomains.includes(domain)) {
|
|
80
|
+
throw new Error('Доступ к домену запрещен: ' + domain);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Проверка протокола
|
|
84
|
+
const allowedProtocols = ['http:', 'https:'];
|
|
85
|
+
if (!allowedProtocols.includes(urlObj.protocol)) {
|
|
86
|
+
throw new Error('Протокол не разрешен: ' + urlObj.protocol);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
} catch (e) {
|
|
91
|
+
throw new Error('Неверный URL: ' + url);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Защита от prototype pollution
|
|
97
|
+
*/
|
|
98
|
+
sanitizeObject(obj) {
|
|
99
|
+
if (!this.prototypePollutionProtection) return obj;
|
|
100
|
+
|
|
101
|
+
const sanitized = Array.isArray(obj) ? [] : {};
|
|
102
|
+
|
|
103
|
+
for (const key in obj) {
|
|
104
|
+
// Блокируем опасные ключи
|
|
105
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const value = obj[key];
|
|
110
|
+
|
|
111
|
+
if (value && typeof value === 'object') {
|
|
112
|
+
sanitized[key] = this.sanitizeObject(value);
|
|
113
|
+
} else {
|
|
114
|
+
sanitized[key] = value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return sanitized;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Проверка и очистка JSON данных
|
|
123
|
+
*/
|
|
124
|
+
sanitizeJSON(data) {
|
|
125
|
+
try {
|
|
126
|
+
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
|
127
|
+
return this.sanitizeObject(parsed);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
throw new Error('Неверный JSON: ' + e.message);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Проверка глубины рекурсии
|
|
135
|
+
*/
|
|
136
|
+
checkRecursionDepth(currentDepth, maxDepth = 1000) {
|
|
137
|
+
if (currentDepth > maxDepth) {
|
|
138
|
+
throw new Error(`Превышена глубина рекурсии: ${currentDepth} > ${maxDepth}`);
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Проверка использования памяти
|
|
145
|
+
*/
|
|
146
|
+
checkMemoryUsage() {
|
|
147
|
+
if (!this.enabled) return true;
|
|
148
|
+
|
|
149
|
+
const usage = process.memoryUsage();
|
|
150
|
+
if (usage.heapUsed > this.maxMemory) {
|
|
151
|
+
throw new Error(`Превышен лимит памяти: ${usage.heapUsed} > ${this.maxMemory}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Создание безопасной среды выполнения
|
|
159
|
+
*/
|
|
160
|
+
createSandbox() {
|
|
161
|
+
if (!this.enabled) return null;
|
|
162
|
+
|
|
163
|
+
const sandbox = {
|
|
164
|
+
console: {
|
|
165
|
+
log: (...args) => console.log('[SANDBOX]', ...args),
|
|
166
|
+
error: (...args) => console.error('[SANDBOX]', ...args),
|
|
167
|
+
warn: (...args) => console.warn('[SANDBOX]', ...args)
|
|
168
|
+
},
|
|
169
|
+
setTimeout: setTimeout.bind(null),
|
|
170
|
+
clearTimeout: clearTimeout.bind(null),
|
|
171
|
+
setInterval: setInterval.bind(null),
|
|
172
|
+
clearInterval: clearInterval.bind(null),
|
|
173
|
+
Date,
|
|
174
|
+
Math,
|
|
175
|
+
JSON: {
|
|
176
|
+
parse: (data) => this.sanitizeJSON(data),
|
|
177
|
+
stringify: JSON.stringify.bind(JSON)
|
|
178
|
+
},
|
|
179
|
+
// Ограниченный доступ к процессу
|
|
180
|
+
process: {
|
|
181
|
+
cwd: process.cwd.bind(process),
|
|
182
|
+
env: {}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return sandbox;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Валидация ввода
|
|
191
|
+
*/
|
|
192
|
+
validateInput(input, schema) {
|
|
193
|
+
const errors = [];
|
|
194
|
+
|
|
195
|
+
if (schema.type) {
|
|
196
|
+
const expectedType = schema.type;
|
|
197
|
+
const actualType = Array.isArray(input) ? 'array' : typeof input;
|
|
198
|
+
|
|
199
|
+
if (expectedType !== actualType) {
|
|
200
|
+
errors.push(`Ожидается тип ${expectedType}, получен ${actualType}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (schema.min !== undefined && input < schema.min) {
|
|
205
|
+
errors.push(`Значение должно быть >= ${schema.min}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (schema.max !== undefined && input > schema.max) {
|
|
209
|
+
errors.push(`Значение должно быть <= ${schema.max}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (schema.minLength !== undefined && input.length < schema.minLength) {
|
|
213
|
+
errors.push(`Длина должна быть >= ${schema.minLength}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (schema.maxLength !== undefined && input.length > schema.maxLength) {
|
|
217
|
+
errors.push(`Длина должна быть <= ${schema.maxLength}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (schema.pattern && !schema.pattern.test(input)) {
|
|
221
|
+
errors.push('Значение не соответствует паттерну');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (errors.length > 0) {
|
|
225
|
+
throw new Error('Ошибки валидации: ' + errors.join(', '));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Очистка XSS уязвимостей
|
|
233
|
+
*/
|
|
234
|
+
escapeHTML(str) {
|
|
235
|
+
return String(str)
|
|
236
|
+
.replace(/&/g, '&')
|
|
237
|
+
.replace(/</g, '<')
|
|
238
|
+
.replace(/>/g, '>')
|
|
239
|
+
.replace(/"/g, '"')
|
|
240
|
+
.replace(/'/g, ''');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export default SecurityManager;
|