resafe 1.0.1 → 1.0.2

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/dist/index.cjs CHANGED
@@ -241,41 +241,40 @@ function spectralRadius(matrix) {
241
241
  const n = matrix.length;
242
242
  if (n === 0) return 0;
243
243
  let v = Array(n).fill(1 / Math.sqrt(n));
244
- for (let iter = 0; iter < 100; iter++) {
244
+ const tolerance = 1e-6;
245
+ const maxIterations = 100;
246
+ for (let iter = 0; iter < maxIterations; iter++) {
245
247
  const newV = Array(n).fill(0);
246
248
  for (let i = 0; i < n; i++) {
247
- const row = matrix[i];
248
- if (row) {
249
- for (let j = 0; j < n; j++) {
250
- const cell = row[j];
251
- const vec = v[j];
252
- if (cell !== void 0 && vec !== void 0) {
253
- newV[i] += cell * vec;
254
- }
255
- }
249
+ const row = matrix[i] ?? [];
250
+ for (let j = 0; j < n; j++) {
251
+ const cell = row[j] ?? 0;
252
+ newV[i] += cell * v[j];
256
253
  }
257
254
  }
258
255
  const norm = Math.sqrt(newV.reduce((sum, val) => sum + val * val, 0));
259
256
  if (norm === 0) return 0;
260
- v = newV.map((val) => val / norm);
257
+ const normalizedV = newV.map((val) => val / norm);
258
+ let converged = true;
259
+ for (let i = 0; i < n; i++) {
260
+ const a = normalizedV[i];
261
+ const b = v[i];
262
+ if (a !== void 0 && b !== void 0 && Math.abs(a - b) > tolerance) {
263
+ converged = false;
264
+ break;
265
+ }
266
+ }
267
+ if (converged) break;
268
+ v = normalizedV;
261
269
  }
262
270
  let eigenvalue = 0;
263
271
  for (let i = 0; i < n; i++) {
272
+ const row = matrix[i] ?? [];
264
273
  let sum = 0;
265
- const row = matrix[i];
266
- if (row) {
267
- for (let j = 0; j < n; j++) {
268
- const cell = row[j];
269
- const vec = v[j];
270
- if (cell !== void 0 && vec !== void 0) {
271
- sum += cell * vec;
272
- }
273
- }
274
- }
275
- const vecI = v[i];
276
- if (vecI !== void 0) {
277
- eigenvalue += sum * vecI;
274
+ for (let j = 0; j < n; j++) {
275
+ sum += (row[j] ?? 0) * v[j];
278
276
  }
277
+ eigenvalue += sum * v[i];
279
278
  }
280
279
  return Math.abs(eigenvalue);
281
280
  }
@@ -295,33 +294,90 @@ function analyze(pattern, config = {}) {
295
294
  }
296
295
 
297
296
  // src/utils/logger.ts
298
- var C = {
299
- reset: "\x1B[0m",
300
- yellow: "\x1B[33m",
301
- red: "\x1B[31m",
302
- gray: "\x1B[90m"
297
+ var import_node_process = require("process");
298
+ var ESC = "\x1B[";
299
+ var Formatter = class _Formatter {
300
+ parts = "";
301
+ text;
302
+ constructor(text) {
303
+ this.text = text;
304
+ }
305
+ code(code) {
306
+ this.parts += `${ESC}${code}m`;
307
+ return this;
308
+ }
309
+ static create(text) {
310
+ return new _Formatter(text);
311
+ }
312
+ bold() {
313
+ return this.code("1");
314
+ }
315
+ white() {
316
+ return this.code("97");
317
+ }
318
+ darkGray() {
319
+ return this.code("90");
320
+ }
321
+ red() {
322
+ return this.code("38;2;255;85;85");
323
+ }
324
+ pastelRedBg() {
325
+ return this.code("48;2;255;85;85");
326
+ }
327
+ toString() {
328
+ return `${this.parts}${this.text}${ESC}0m`;
329
+ }
330
+ [Symbol.toPrimitive]() {
331
+ return this.toString();
332
+ }
303
333
  };
304
- var S = { WARN: "!", ERROR: "x" };
334
+ var fmt = (text) => Formatter.create(text);
305
335
  var log = {
306
- warn: (m) => console.log(`${C.yellow}${S.WARN}${C.reset} ${m}`),
307
- error: (m) => console.log(`${C.red}${S.ERROR}${C.reset} ${m}`),
308
- hint: (m) => console.log(`${C.gray}${m}${C.reset}`)
336
+ error: (msg, regex, extra) => {
337
+ let firstLine = `${fmt(" RESAFE ").bold().pastelRedBg().white()} ${fmt(msg).white()}`;
338
+ if (regex) {
339
+ firstLine += ` ${fmt("regex").red()}${fmt("=").darkGray()}${fmt(regex).white()}`;
340
+ }
341
+ import_node_process.stdout.write(`${firstLine}
342
+ `);
343
+ if (extra) log.quote(extra);
344
+ },
345
+ warn: (msg, regex, extra) => {
346
+ let firstLine = `${fmt(" RESAFE ").bold().pastelRedBg().white()} ${fmt(msg).white()}`;
347
+ if (regex) {
348
+ firstLine += ` ${fmt("regex").red()}${fmt("=").darkGray()}${fmt(regex).white()}`;
349
+ }
350
+ import_node_process.stdout.write(`${firstLine}
351
+ `);
352
+ if (extra) log.quote(extra);
353
+ },
354
+ hint: (msg) => {
355
+ const lines = Array.isArray(msg) ? msg : [msg];
356
+ log.quote(lines);
357
+ },
358
+ quote: (lines) => {
359
+ lines.forEach((line) => {
360
+ import_node_process.stdout.write(` ${fmt("\u2502").darkGray()} ${fmt(line).white()}
361
+ `);
362
+ });
363
+ import_node_process.stdout.write("\n");
364
+ }
309
365
  };
310
366
 
311
367
  // src/index.ts
312
368
  function check(regex, options = {}) {
313
369
  const pattern = typeof regex === "string" ? regex : regex.source;
314
370
  const result = analyze(pattern, options);
371
+ const radius = Number(result.radius.toFixed(4));
315
372
  if (!result.safe && !options.silent) {
316
- log.error(`[Resafe] Unsafe pattern: /${pattern}/`);
317
- log.warn(
318
- `Spectral radius: ${result.radius.toFixed(4)} (threshold: ${options.threshold ?? 1})`
319
- );
320
- log.hint("Simplify regex structure to eliminate exponential paths");
373
+ log.error("Unsafe Regex!", `/${pattern}/`, [
374
+ `Spectral radius: ${radius} (threshold: ${options.threshold ?? 1})`,
375
+ "? Consider simplifying quantifiers"
376
+ ]);
321
377
  }
322
378
  if (!result.safe && options.throwErr) {
323
379
  throw new Error(
324
- `[Resafe] Unsafe pattern with spectral radius ${result.radius.toFixed(4)}`
380
+ `Unsafe regex (spectral radius ${radius})`
325
381
  );
326
382
  }
327
383
  return result;
package/dist/index.js CHANGED
@@ -214,41 +214,40 @@ function spectralRadius(matrix) {
214
214
  const n = matrix.length;
215
215
  if (n === 0) return 0;
216
216
  let v = Array(n).fill(1 / Math.sqrt(n));
217
- for (let iter = 0; iter < 100; iter++) {
217
+ const tolerance = 1e-6;
218
+ const maxIterations = 100;
219
+ for (let iter = 0; iter < maxIterations; iter++) {
218
220
  const newV = Array(n).fill(0);
219
221
  for (let i = 0; i < n; i++) {
220
- const row = matrix[i];
221
- if (row) {
222
- for (let j = 0; j < n; j++) {
223
- const cell = row[j];
224
- const vec = v[j];
225
- if (cell !== void 0 && vec !== void 0) {
226
- newV[i] += cell * vec;
227
- }
228
- }
222
+ const row = matrix[i] ?? [];
223
+ for (let j = 0; j < n; j++) {
224
+ const cell = row[j] ?? 0;
225
+ newV[i] += cell * v[j];
229
226
  }
230
227
  }
231
228
  const norm = Math.sqrt(newV.reduce((sum, val) => sum + val * val, 0));
232
229
  if (norm === 0) return 0;
233
- v = newV.map((val) => val / norm);
230
+ const normalizedV = newV.map((val) => val / norm);
231
+ let converged = true;
232
+ for (let i = 0; i < n; i++) {
233
+ const a = normalizedV[i];
234
+ const b = v[i];
235
+ if (a !== void 0 && b !== void 0 && Math.abs(a - b) > tolerance) {
236
+ converged = false;
237
+ break;
238
+ }
239
+ }
240
+ if (converged) break;
241
+ v = normalizedV;
234
242
  }
235
243
  let eigenvalue = 0;
236
244
  for (let i = 0; i < n; i++) {
245
+ const row = matrix[i] ?? [];
237
246
  let sum = 0;
238
- const row = matrix[i];
239
- if (row) {
240
- for (let j = 0; j < n; j++) {
241
- const cell = row[j];
242
- const vec = v[j];
243
- if (cell !== void 0 && vec !== void 0) {
244
- sum += cell * vec;
245
- }
246
- }
247
- }
248
- const vecI = v[i];
249
- if (vecI !== void 0) {
250
- eigenvalue += sum * vecI;
247
+ for (let j = 0; j < n; j++) {
248
+ sum += (row[j] ?? 0) * v[j];
251
249
  }
250
+ eigenvalue += sum * v[i];
252
251
  }
253
252
  return Math.abs(eigenvalue);
254
253
  }
@@ -268,33 +267,90 @@ function analyze(pattern, config = {}) {
268
267
  }
269
268
 
270
269
  // src/utils/logger.ts
271
- var C = {
272
- reset: "\x1B[0m",
273
- yellow: "\x1B[33m",
274
- red: "\x1B[31m",
275
- gray: "\x1B[90m"
270
+ import { stdout } from "process";
271
+ var ESC = "\x1B[";
272
+ var Formatter = class _Formatter {
273
+ parts = "";
274
+ text;
275
+ constructor(text) {
276
+ this.text = text;
277
+ }
278
+ code(code) {
279
+ this.parts += `${ESC}${code}m`;
280
+ return this;
281
+ }
282
+ static create(text) {
283
+ return new _Formatter(text);
284
+ }
285
+ bold() {
286
+ return this.code("1");
287
+ }
288
+ white() {
289
+ return this.code("97");
290
+ }
291
+ darkGray() {
292
+ return this.code("90");
293
+ }
294
+ red() {
295
+ return this.code("38;2;255;85;85");
296
+ }
297
+ pastelRedBg() {
298
+ return this.code("48;2;255;85;85");
299
+ }
300
+ toString() {
301
+ return `${this.parts}${this.text}${ESC}0m`;
302
+ }
303
+ [Symbol.toPrimitive]() {
304
+ return this.toString();
305
+ }
276
306
  };
277
- var S = { WARN: "!", ERROR: "x" };
307
+ var fmt = (text) => Formatter.create(text);
278
308
  var log = {
279
- warn: (m) => console.log(`${C.yellow}${S.WARN}${C.reset} ${m}`),
280
- error: (m) => console.log(`${C.red}${S.ERROR}${C.reset} ${m}`),
281
- hint: (m) => console.log(`${C.gray}${m}${C.reset}`)
309
+ error: (msg, regex, extra) => {
310
+ let firstLine = `${fmt(" RESAFE ").bold().pastelRedBg().white()} ${fmt(msg).white()}`;
311
+ if (regex) {
312
+ firstLine += ` ${fmt("regex").red()}${fmt("=").darkGray()}${fmt(regex).white()}`;
313
+ }
314
+ stdout.write(`${firstLine}
315
+ `);
316
+ if (extra) log.quote(extra);
317
+ },
318
+ warn: (msg, regex, extra) => {
319
+ let firstLine = `${fmt(" RESAFE ").bold().pastelRedBg().white()} ${fmt(msg).white()}`;
320
+ if (regex) {
321
+ firstLine += ` ${fmt("regex").red()}${fmt("=").darkGray()}${fmt(regex).white()}`;
322
+ }
323
+ stdout.write(`${firstLine}
324
+ `);
325
+ if (extra) log.quote(extra);
326
+ },
327
+ hint: (msg) => {
328
+ const lines = Array.isArray(msg) ? msg : [msg];
329
+ log.quote(lines);
330
+ },
331
+ quote: (lines) => {
332
+ lines.forEach((line) => {
333
+ stdout.write(` ${fmt("\u2502").darkGray()} ${fmt(line).white()}
334
+ `);
335
+ });
336
+ stdout.write("\n");
337
+ }
282
338
  };
283
339
 
284
340
  // src/index.ts
285
341
  function check(regex, options = {}) {
286
342
  const pattern = typeof regex === "string" ? regex : regex.source;
287
343
  const result = analyze(pattern, options);
344
+ const radius = Number(result.radius.toFixed(4));
288
345
  if (!result.safe && !options.silent) {
289
- log.error(`[Resafe] Unsafe pattern: /${pattern}/`);
290
- log.warn(
291
- `Spectral radius: ${result.radius.toFixed(4)} (threshold: ${options.threshold ?? 1})`
292
- );
293
- log.hint("Simplify regex structure to eliminate exponential paths");
346
+ log.error("Unsafe Regex!", `/${pattern}/`, [
347
+ `Spectral radius: ${radius} (threshold: ${options.threshold ?? 1})`,
348
+ "? Consider simplifying quantifiers"
349
+ ]);
294
350
  }
295
351
  if (!result.safe && options.throwErr) {
296
352
  throw new Error(
297
- `[Resafe] Unsafe pattern with spectral radius ${result.radius.toFixed(4)}`
353
+ `Unsafe regex (spectral radius ${radius})`
298
354
  );
299
355
  }
300
356
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resafe",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "🛡️ Detects ReDoS vulnerabilities in regexes using Thompson NFA construction and spectral radius analysis",
5
5
  "license": "MIT",
6
6
  "homepage": "https://resafe.js.org",