@utilix-tech/sdk 0.18.0 → 0.19.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/CHANGELOG.md +3 -0
- package/README.md +6 -1
- package/dist/{chunk-L53LAVAD.js → chunk-LXKIPPDG.js} +266 -1
- package/dist/{code-Cn9uOIAh.d.cts → code-DaRvff2v.d.cts} +33 -2
- package/dist/{code-Cn9uOIAh.d.ts → code-DaRvff2v.d.ts} +33 -2
- package/dist/index.cjs +265 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/code.cjs +265 -0
- package/dist/tools/code.d.cts +1 -1
- package/dist/tools/code.d.ts +1 -1
- package/dist/tools/code.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Full release notes for all Utilix surfaces (browser tools, REST API, SDKs, MCP server) live at [utilix.tech/changelog](https://utilix.tech/changelog). This file tracks just this package.
|
|
4
4
|
|
|
5
|
+
## 0.19.0
|
|
6
|
+
- Add `detectRedos` (regex-redos-detector): statically flag catastrophic-backtracking risk (nested quantifiers, overlapping alternation, adjacent overlapping quantifiers).
|
|
7
|
+
|
|
5
8
|
## 0.18.0
|
|
6
9
|
- Added Present Value / Future Value Calculator (`calculateTimeValue`) and Vector Similarity Calculator (`vectorSimilarity`).
|
|
7
10
|
|
package/README.md
CHANGED
|
@@ -389,7 +389,8 @@ generateCspHeader({ "default-src": ["'self'"], "script-src": ["'self'", "cdn.exa
|
|
|
389
389
|
|
|
390
390
|
```ts
|
|
391
391
|
import { formatSql, minifySql, testRegex, formatHtml, formatGql,
|
|
392
|
-
detectDialect, splitStatements, lookupMime, parseGithubUrl
|
|
392
|
+
detectDialect, splitStatements, lookupMime, parseGithubUrl,
|
|
393
|
+
detectRedos } from "@utilix-tech/sdk/code";
|
|
393
394
|
|
|
394
395
|
// SQL
|
|
395
396
|
formatSql("SELECT id,name FROM users WHERE active=1 ORDER BY name");
|
|
@@ -421,6 +422,10 @@ lookupMime("image/jpeg"); // { kind: "mime", matches: [{ extension: "jpg", ...
|
|
|
421
422
|
// GitHub URL parsing
|
|
422
423
|
parseGithubUrl("https://github.com/facebook/react/pull/1234");
|
|
423
424
|
// { owner: "facebook", repo: "react", type: "pull", number: 1234 }
|
|
425
|
+
|
|
426
|
+
// Regex ReDoS (catastrophic-backtracking) detection
|
|
427
|
+
detectRedos("(a+)+");
|
|
428
|
+
// { risk: "high", safe: false, findings: [{ type: "nested-quantifier", ... }] }
|
|
424
429
|
```
|
|
425
430
|
|
|
426
431
|
---
|
|
@@ -18,6 +18,7 @@ __export(code_exports, {
|
|
|
18
18
|
countRules: () => countRules,
|
|
19
19
|
detectDialect: () => detectDialect,
|
|
20
20
|
detectOperationType: () => detectOperationType,
|
|
21
|
+
detectRedos: () => detectRedos,
|
|
21
22
|
envToExport: () => envToExport,
|
|
22
23
|
envToJson: () => envToJson,
|
|
23
24
|
formatCss: () => formatCss,
|
|
@@ -2475,5 +2476,269 @@ function parseGithubUrl(input) {
|
|
|
2475
2476
|
}
|
|
2476
2477
|
return { error: `Unsupported GitHub URL path: /${rest.join("/")}` };
|
|
2477
2478
|
}
|
|
2479
|
+
var _REDOS_UNBOUNDED = /* @__PURE__ */ new Set(["*", "+"]);
|
|
2480
|
+
function _redosParseQuant(src, i) {
|
|
2481
|
+
const ch = src[i];
|
|
2482
|
+
if (ch === "*" || ch === "+" || ch === "?") {
|
|
2483
|
+
let end = i + 1;
|
|
2484
|
+
if (src[end] === "?" || src[end] === "+") end++;
|
|
2485
|
+
return { unbounded: _REDOS_UNBOUNDED.has(ch), raw: src.slice(i, end), end };
|
|
2486
|
+
}
|
|
2487
|
+
if (ch === "{") {
|
|
2488
|
+
const close = src.indexOf("}", i);
|
|
2489
|
+
if (close === -1) return null;
|
|
2490
|
+
const inner = src.slice(i + 1, close);
|
|
2491
|
+
if (!/^\d+(,\d*)?$/.test(inner)) return null;
|
|
2492
|
+
let end = close + 1;
|
|
2493
|
+
if (src[end] === "?" || src[end] === "+") end++;
|
|
2494
|
+
const unbounded = /^\d+,$/.test(inner);
|
|
2495
|
+
return { unbounded, raw: src.slice(i, end), end };
|
|
2496
|
+
}
|
|
2497
|
+
return null;
|
|
2498
|
+
}
|
|
2499
|
+
function _redosParseAlternation(src, start, endExclusive) {
|
|
2500
|
+
const alternatives = [];
|
|
2501
|
+
let current = [];
|
|
2502
|
+
let i = start;
|
|
2503
|
+
while (i < endExclusive) {
|
|
2504
|
+
const ch = src[i];
|
|
2505
|
+
if (ch === "|") {
|
|
2506
|
+
alternatives.push(current);
|
|
2507
|
+
current = [];
|
|
2508
|
+
i++;
|
|
2509
|
+
continue;
|
|
2510
|
+
}
|
|
2511
|
+
let node;
|
|
2512
|
+
if (ch === "\\") {
|
|
2513
|
+
const atomEnd = i + 2 <= endExclusive ? i + 2 : i + 1;
|
|
2514
|
+
node = { kind: "char", start: i, end: atomEnd, quant: null, quantEnd: atomEnd, raw: src.slice(i, atomEnd) };
|
|
2515
|
+
i = atomEnd;
|
|
2516
|
+
} else if (ch === "[") {
|
|
2517
|
+
let j = i + 1;
|
|
2518
|
+
if (src[j] === "^") j++;
|
|
2519
|
+
if (src[j] === "]") j++;
|
|
2520
|
+
while (j < endExclusive && src[j] !== "]") {
|
|
2521
|
+
j += src[j] === "\\" ? 2 : 1;
|
|
2522
|
+
}
|
|
2523
|
+
const classEnd = j < endExclusive ? j + 1 : endExclusive;
|
|
2524
|
+
node = { kind: "class", start: i, end: classEnd, quant: null, quantEnd: classEnd, raw: src.slice(i, classEnd) };
|
|
2525
|
+
i = classEnd;
|
|
2526
|
+
} else if (ch === "(") {
|
|
2527
|
+
let depth = 1;
|
|
2528
|
+
let j = i + 1;
|
|
2529
|
+
while (j < endExclusive && depth > 0) {
|
|
2530
|
+
const cj = src[j];
|
|
2531
|
+
if (cj === "\\") {
|
|
2532
|
+
j += 2;
|
|
2533
|
+
continue;
|
|
2534
|
+
}
|
|
2535
|
+
if (cj === "[") {
|
|
2536
|
+
j++;
|
|
2537
|
+
if (src[j] === "^") j++;
|
|
2538
|
+
if (src[j] === "]") j++;
|
|
2539
|
+
while (j < endExclusive && src[j] !== "]") {
|
|
2540
|
+
j += src[j] === "\\" ? 2 : 1;
|
|
2541
|
+
}
|
|
2542
|
+
j++;
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
if (cj === "(") depth++;
|
|
2546
|
+
else if (cj === ")") depth--;
|
|
2547
|
+
j++;
|
|
2548
|
+
}
|
|
2549
|
+
const closeIdx = depth === 0 ? j - 1 : endExclusive;
|
|
2550
|
+
let contentStart = i + 1;
|
|
2551
|
+
let lookaround = false;
|
|
2552
|
+
if (src[i + 1] === "?") {
|
|
2553
|
+
const after = src[i + 2];
|
|
2554
|
+
if (after === ":") contentStart = i + 3;
|
|
2555
|
+
else if (after === "=" || after === "!") {
|
|
2556
|
+
contentStart = i + 3;
|
|
2557
|
+
lookaround = true;
|
|
2558
|
+
} else if (after === "<") {
|
|
2559
|
+
const a3 = src[i + 3];
|
|
2560
|
+
if (a3 === "=" || a3 === "!") {
|
|
2561
|
+
contentStart = i + 4;
|
|
2562
|
+
lookaround = true;
|
|
2563
|
+
} else {
|
|
2564
|
+
const gt = src.indexOf(">", i + 2);
|
|
2565
|
+
contentStart = gt !== -1 && gt < closeIdx ? gt + 1 : i + 3;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
node = {
|
|
2570
|
+
kind: "group",
|
|
2571
|
+
start: i,
|
|
2572
|
+
end: closeIdx + 1,
|
|
2573
|
+
quant: null,
|
|
2574
|
+
quantEnd: closeIdx + 1,
|
|
2575
|
+
raw: src.slice(i, closeIdx + 1),
|
|
2576
|
+
alternatives: _redosParseAlternation(src, contentStart, closeIdx),
|
|
2577
|
+
lookaround
|
|
2578
|
+
};
|
|
2579
|
+
i = closeIdx + 1;
|
|
2580
|
+
} else if (ch === ".") {
|
|
2581
|
+
node = { kind: "dot", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: "." };
|
|
2582
|
+
i++;
|
|
2583
|
+
} else if (ch === "^" || ch === "$") {
|
|
2584
|
+
node = { kind: "anchor", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: ch };
|
|
2585
|
+
i++;
|
|
2586
|
+
} else {
|
|
2587
|
+
node = { kind: "char", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: ch };
|
|
2588
|
+
i++;
|
|
2589
|
+
}
|
|
2590
|
+
if (node.kind !== "anchor") {
|
|
2591
|
+
const q = _redosParseQuant(src, i);
|
|
2592
|
+
if (q) {
|
|
2593
|
+
node.quant = q;
|
|
2594
|
+
node.quantEnd = q.end;
|
|
2595
|
+
i = q.end;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
current.push(node);
|
|
2599
|
+
}
|
|
2600
|
+
alternatives.push(current);
|
|
2601
|
+
return alternatives;
|
|
2602
|
+
}
|
|
2603
|
+
var _REDOS_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_ .,-@/:;#%&=";
|
|
2604
|
+
function _redosFirstMatcher(node) {
|
|
2605
|
+
if (node.kind === "dot") return (c) => c !== "\n";
|
|
2606
|
+
if (node.kind === "char") {
|
|
2607
|
+
if (node.raw.startsWith("\\")) {
|
|
2608
|
+
const body = node.raw.slice(1);
|
|
2609
|
+
if (/^[dDwWsS]$/.test(body)) {
|
|
2610
|
+
try {
|
|
2611
|
+
const re = new RegExp("\\" + body);
|
|
2612
|
+
return (c) => re.test(c);
|
|
2613
|
+
} catch {
|
|
2614
|
+
return null;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
const literal = { n: "\n", t: " ", r: "\r", f: "\f", v: "\v" }[body] ?? body;
|
|
2618
|
+
return (c) => c === literal;
|
|
2619
|
+
}
|
|
2620
|
+
const lit = node.raw;
|
|
2621
|
+
return (c) => c === lit;
|
|
2622
|
+
}
|
|
2623
|
+
if (node.kind === "class") {
|
|
2624
|
+
try {
|
|
2625
|
+
const re = new RegExp(node.raw);
|
|
2626
|
+
return (c) => re.test(c);
|
|
2627
|
+
} catch {
|
|
2628
|
+
return null;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
if (node.kind === "group") {
|
|
2632
|
+
const first = node.alternatives?.[0]?.[0];
|
|
2633
|
+
return first ? _redosFirstMatcher(first) : null;
|
|
2634
|
+
}
|
|
2635
|
+
return null;
|
|
2636
|
+
}
|
|
2637
|
+
function _redosOverlaps(a, b) {
|
|
2638
|
+
const fa = _redosFirstMatcher(a);
|
|
2639
|
+
const fb = _redosFirstMatcher(b);
|
|
2640
|
+
if (!fa || !fb) return false;
|
|
2641
|
+
for (const c of _REDOS_ALPHABET) {
|
|
2642
|
+
if (fa(c) && fb(c)) return true;
|
|
2643
|
+
}
|
|
2644
|
+
return false;
|
|
2645
|
+
}
|
|
2646
|
+
function _redosAnalyzeGroup(node, findings) {
|
|
2647
|
+
const alts = node.alternatives ?? [];
|
|
2648
|
+
const outerUnbounded = node.quant?.unbounded === true && !node.lookaround;
|
|
2649
|
+
if (outerUnbounded) {
|
|
2650
|
+
for (const alt of alts) {
|
|
2651
|
+
const meaningful = alt.filter((n) => n.kind !== "anchor");
|
|
2652
|
+
if (meaningful.length === 1 && meaningful[0].quant?.unbounded) {
|
|
2653
|
+
findings.push({
|
|
2654
|
+
type: "nested-quantifier",
|
|
2655
|
+
severity: "high",
|
|
2656
|
+
message: "Nested unbounded quantifiers: a repeated group whose body is itself unbounded-repeated can match the same input in exponentially many ways, causing catastrophic backtracking.",
|
|
2657
|
+
snippet: node.raw + (node.quant?.raw ?? ""),
|
|
2658
|
+
index: node.start
|
|
2659
|
+
});
|
|
2660
|
+
break;
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
if (alts.length > 1) {
|
|
2664
|
+
const firsts = alts.map((alt) => alt.find((n) => n.kind !== "anchor")).filter((n) => !!n);
|
|
2665
|
+
let flagged = false;
|
|
2666
|
+
for (let x = 0; x < firsts.length && !flagged; x++) {
|
|
2667
|
+
for (let y = x + 1; y < firsts.length; y++) {
|
|
2668
|
+
if (_redosOverlaps(firsts[x], firsts[y])) {
|
|
2669
|
+
findings.push({
|
|
2670
|
+
type: "overlapping-alternation",
|
|
2671
|
+
severity: "medium",
|
|
2672
|
+
message: "Overlapping alternation under a repetition: two branches can match the same starting input, so the engine has multiple ways to split each repeat and may backtrack heavily.",
|
|
2673
|
+
snippet: node.raw + (node.quant?.raw ?? ""),
|
|
2674
|
+
index: node.start
|
|
2675
|
+
});
|
|
2676
|
+
flagged = true;
|
|
2677
|
+
break;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
for (const alt of alts) {
|
|
2684
|
+
_redosAnalyzeSequence(alt, findings);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
function _redosAnalyzeSequence(seq, findings) {
|
|
2688
|
+
for (let k = 0; k + 1 < seq.length; k++) {
|
|
2689
|
+
const a = seq[k];
|
|
2690
|
+
const b = seq[k + 1];
|
|
2691
|
+
if (a.quant?.unbounded && b.quant?.unbounded && _redosOverlaps(a, b)) {
|
|
2692
|
+
findings.push({
|
|
2693
|
+
type: "adjacent-quantifier",
|
|
2694
|
+
severity: "medium",
|
|
2695
|
+
message: "Adjacent unbounded quantifiers over overlapping characters: the engine can distribute the same input between them in many ways, causing quadratic backtracking.",
|
|
2696
|
+
snippet: seq[k].raw + (a.quant?.raw ?? "") + seq[k + 1].raw + (b.quant?.raw ?? ""),
|
|
2697
|
+
index: a.start
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
for (const node of seq) {
|
|
2702
|
+
if (node.kind === "group") _redosAnalyzeGroup(node, findings);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
function detectRedos(pattern) {
|
|
2706
|
+
if (typeof pattern !== "string" || pattern === "") {
|
|
2707
|
+
return { error: "Empty pattern" };
|
|
2708
|
+
}
|
|
2709
|
+
if (pattern.length > 5e3) {
|
|
2710
|
+
return { error: "Pattern too long (max 5,000 chars)" };
|
|
2711
|
+
}
|
|
2712
|
+
let valid = true;
|
|
2713
|
+
let compileError;
|
|
2714
|
+
try {
|
|
2715
|
+
new RegExp(pattern);
|
|
2716
|
+
} catch (e) {
|
|
2717
|
+
valid = false;
|
|
2718
|
+
compileError = e instanceof Error ? e.message : String(e);
|
|
2719
|
+
}
|
|
2720
|
+
const findings = [];
|
|
2721
|
+
const alternatives = _redosParseAlternation(pattern, 0, pattern.length);
|
|
2722
|
+
for (const alt of alternatives) {
|
|
2723
|
+
_redosAnalyzeSequence(alt, findings);
|
|
2724
|
+
}
|
|
2725
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2726
|
+
const unique = findings.filter((f) => {
|
|
2727
|
+
const key = `${f.type}:${f.index}`;
|
|
2728
|
+
if (seen.has(key)) return false;
|
|
2729
|
+
seen.add(key);
|
|
2730
|
+
return true;
|
|
2731
|
+
});
|
|
2732
|
+
unique.sort((a, b) => a.index - b.index);
|
|
2733
|
+
const risk = unique.some((f) => f.severity === "high") ? "high" : unique.length > 0 ? "medium" : "none";
|
|
2734
|
+
return {
|
|
2735
|
+
pattern,
|
|
2736
|
+
valid,
|
|
2737
|
+
...compileError ? { compileError } : {},
|
|
2738
|
+
risk,
|
|
2739
|
+
safe: unique.length === 0,
|
|
2740
|
+
findings: unique
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2478
2743
|
|
|
2479
|
-
export { ALL_FLAGS, CHEATSHEET, MIME_TABLE, OPERATIONS, SQL_DEFAULT_OPTIONS, buildDockerPull, buildDockerRun, buildGitCommand, code_exports, collapseHtmlWhitespace, collapseJsWhitespace, countProperties, countRules, detectDialect, detectOperationType, envToExport, envToJson, formatCss, formatGql, formatHtml, formatSql, getEndpointsByTag, getHtmlStats, getJsStats, getMethodColor, isValidTag, lookupMime, minifyCss, minifyGql, minifyHtml, minifyJs, minifySql, normalizeRef, parseDockerImage, parseEnv, parseGitCommand, parseGithubUrl, parseSpec, removeConsoleCalls, removeEmptyAttributes, removeHtmlComments, removeJsComments, removeRedundantAttributes, searchCheatsheet, searchEndpoints, splitStatements, testPattern, testRegex, validateEnvKey };
|
|
2744
|
+
export { ALL_FLAGS, CHEATSHEET, MIME_TABLE, OPERATIONS, SQL_DEFAULT_OPTIONS, buildDockerPull, buildDockerRun, buildGitCommand, code_exports, collapseHtmlWhitespace, collapseJsWhitespace, countProperties, countRules, detectDialect, detectOperationType, detectRedos, envToExport, envToJson, formatCss, formatGql, formatHtml, formatSql, getEndpointsByTag, getHtmlStats, getJsStats, getMethodColor, isValidTag, lookupMime, minifyCss, minifyGql, minifyHtml, minifyJs, minifySql, normalizeRef, parseDockerImage, parseEnv, parseGitCommand, parseGithubUrl, parseSpec, removeConsoleCalls, removeEmptyAttributes, removeHtmlComments, removeJsComments, removeRedundantAttributes, searchCheatsheet, searchEndpoints, splitStatements, testPattern, testRegex, validateEnvKey };
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* - css-minifier (minifyCss, formatCss, countRules, countProperties)
|
|
20
20
|
* - mime-lookup (lookupMime, MIME_TABLE)
|
|
21
21
|
* - github-url-parser (parseGithubUrl)
|
|
22
|
+
* - redos-detector (detectRedos)
|
|
22
23
|
*/
|
|
23
24
|
type RegexFlag = 'g' | 'i' | 'm' | 's' | 'u';
|
|
24
25
|
declare const ALL_FLAGS: RegexFlag[];
|
|
@@ -307,6 +308,32 @@ interface GithubUrlInfo {
|
|
|
307
308
|
declare function parseGithubUrl(input: string): GithubUrlInfo | {
|
|
308
309
|
error: string;
|
|
309
310
|
};
|
|
311
|
+
type RedosSeverity = 'high' | 'medium';
|
|
312
|
+
interface RedosFinding {
|
|
313
|
+
type: 'nested-quantifier' | 'overlapping-alternation' | 'adjacent-quantifier';
|
|
314
|
+
severity: RedosSeverity;
|
|
315
|
+
message: string;
|
|
316
|
+
snippet: string;
|
|
317
|
+
index: number;
|
|
318
|
+
}
|
|
319
|
+
interface RedosResult {
|
|
320
|
+
pattern: string;
|
|
321
|
+
valid: boolean;
|
|
322
|
+
compileError?: string;
|
|
323
|
+
risk: 'none' | 'medium' | 'high';
|
|
324
|
+
safe: boolean;
|
|
325
|
+
findings: RedosFinding[];
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Statically analyze a regex pattern for catastrophic-backtracking (ReDoS)
|
|
329
|
+
* risk. Pass the pattern body only (no delimiters or flags). This is a
|
|
330
|
+
* heuristic linter focused on the classic exponential/quadratic constructs
|
|
331
|
+
* (nested quantifiers, overlapping alternation under a repeat, adjacent
|
|
332
|
+
* overlapping quantifiers), not a formal guarantee.
|
|
333
|
+
*/
|
|
334
|
+
declare function detectRedos(pattern: string): RedosResult | {
|
|
335
|
+
error: string;
|
|
336
|
+
};
|
|
310
337
|
|
|
311
338
|
declare const code_ALL_FLAGS: typeof ALL_FLAGS;
|
|
312
339
|
type code_ApiEndpoint = ApiEndpoint;
|
|
@@ -334,6 +361,9 @@ type code_MimeEntry = MimeEntry;
|
|
|
334
361
|
type code_MimeLookupResult = MimeLookupResult;
|
|
335
362
|
declare const code_OPERATIONS: typeof OPERATIONS;
|
|
336
363
|
type code_ParsedSpec = ParsedSpec;
|
|
364
|
+
type code_RedosFinding = RedosFinding;
|
|
365
|
+
type code_RedosResult = RedosResult;
|
|
366
|
+
type code_RedosSeverity = RedosSeverity;
|
|
337
367
|
type code_RegexError = RegexError;
|
|
338
368
|
type code_RegexFlag = RegexFlag;
|
|
339
369
|
type code_RegexMatch = RegexMatch;
|
|
@@ -354,6 +384,7 @@ declare const code_countProperties: typeof countProperties;
|
|
|
354
384
|
declare const code_countRules: typeof countRules;
|
|
355
385
|
declare const code_detectDialect: typeof detectDialect;
|
|
356
386
|
declare const code_detectOperationType: typeof detectOperationType;
|
|
387
|
+
declare const code_detectRedos: typeof detectRedos;
|
|
357
388
|
declare const code_envToExport: typeof envToExport;
|
|
358
389
|
declare const code_envToJson: typeof envToJson;
|
|
359
390
|
declare const code_formatCss: typeof formatCss;
|
|
@@ -389,7 +420,7 @@ declare const code_testPattern: typeof testPattern;
|
|
|
389
420
|
declare const code_testRegex: typeof testRegex;
|
|
390
421
|
declare const code_validateEnvKey: typeof validateEnvKey;
|
|
391
422
|
declare namespace code {
|
|
392
|
-
export { code_ALL_FLAGS as ALL_FLAGS, type code_ApiEndpoint as ApiEndpoint, type code_ApiInfo as ApiInfo, code_CHEATSHEET as CHEATSHEET, type code_CheatsheetEntry as CheatsheetEntry, type code_CssResult as CssResult, type code_DockerImageRef as DockerImageRef, type code_EnvEntry as EnvEntry, type code_EnvParseResult as EnvParseResult, type code_GitCommandConfig as GitCommandConfig, type code_GitCommandResult as GitCommandResult, type code_GitOperation as GitOperation, type code_GithubUrlInfo as GithubUrlInfo, type code_GithubUrlType as GithubUrlType, type code_GqlFormatResult as GqlFormatResult, type code_HtmlFormatResult as HtmlFormatResult, type code_HtmlMinifyOptions as HtmlMinifyOptions, type code_HtmlMinifyResult as HtmlMinifyResult, type code_JsMinifyOptions as JsMinifyOptions, type code_JsMinifyResult as JsMinifyResult, code_MIME_TABLE as MIME_TABLE, type code_MatchResult as MatchResult, type code_MimeEntry as MimeEntry, type code_MimeLookupResult as MimeLookupResult, code_OPERATIONS as OPERATIONS, type code_ParsedSpec as ParsedSpec, type code_RegexError as RegexError, type code_RegexFlag as RegexFlag, type code_RegexMatch as RegexMatch, type code_RegexResult as RegexResult, type code_RegexSuccess as RegexSuccess, code_SQL_DEFAULT_OPTIONS as SQL_DEFAULT_OPTIONS, type code_Segment as Segment, type code_SpecVersion as SpecVersion, type code_SqlDialect as SqlDialect, type code_SqlFormatOptions as SqlFormatOptions, type code_TestPatternResult as TestPatternResult, code_buildDockerPull as buildDockerPull, code_buildDockerRun as buildDockerRun, code_buildGitCommand as buildGitCommand, code_collapseHtmlWhitespace as collapseHtmlWhitespace, code_collapseJsWhitespace as collapseJsWhitespace, code_countProperties as countProperties, code_countRules as countRules, code_detectDialect as detectDialect, code_detectOperationType as detectOperationType, code_envToExport as envToExport, code_envToJson as envToJson, code_formatCss as formatCss, code_formatGql as formatGql, code_formatHtml as formatHtml, code_formatSql as formatSql, code_getEndpointsByTag as getEndpointsByTag, code_getHtmlStats as getHtmlStats, code_getJsStats as getJsStats, code_getMethodColor as getMethodColor, code_isValidTag as isValidTag, code_lookupMime as lookupMime, code_minifyCss as minifyCss, code_minifyGql as minifyGql, code_minifyHtml as minifyHtml, code_minifyJs as minifyJs, code_minifySql as minifySql, code_normalizeRef as normalizeRef, code_parseDockerImage as parseDockerImage, code_parseEnv as parseEnv, code_parseGitCommand as parseGitCommand, code_parseGithubUrl as parseGithubUrl, code_parseSpec as parseSpec, code_removeConsoleCalls as removeConsoleCalls, code_removeEmptyAttributes as removeEmptyAttributes, code_removeHtmlComments as removeHtmlComments, code_removeJsComments as removeJsComments, code_removeRedundantAttributes as removeRedundantAttributes, code_searchCheatsheet as searchCheatsheet, code_searchEndpoints as searchEndpoints, code_splitStatements as splitStatements, code_testPattern as testPattern, code_testRegex as testRegex, code_validateEnvKey as validateEnvKey };
|
|
423
|
+
export { code_ALL_FLAGS as ALL_FLAGS, type code_ApiEndpoint as ApiEndpoint, type code_ApiInfo as ApiInfo, code_CHEATSHEET as CHEATSHEET, type code_CheatsheetEntry as CheatsheetEntry, type code_CssResult as CssResult, type code_DockerImageRef as DockerImageRef, type code_EnvEntry as EnvEntry, type code_EnvParseResult as EnvParseResult, type code_GitCommandConfig as GitCommandConfig, type code_GitCommandResult as GitCommandResult, type code_GitOperation as GitOperation, type code_GithubUrlInfo as GithubUrlInfo, type code_GithubUrlType as GithubUrlType, type code_GqlFormatResult as GqlFormatResult, type code_HtmlFormatResult as HtmlFormatResult, type code_HtmlMinifyOptions as HtmlMinifyOptions, type code_HtmlMinifyResult as HtmlMinifyResult, type code_JsMinifyOptions as JsMinifyOptions, type code_JsMinifyResult as JsMinifyResult, code_MIME_TABLE as MIME_TABLE, type code_MatchResult as MatchResult, type code_MimeEntry as MimeEntry, type code_MimeLookupResult as MimeLookupResult, code_OPERATIONS as OPERATIONS, type code_ParsedSpec as ParsedSpec, type code_RedosFinding as RedosFinding, type code_RedosResult as RedosResult, type code_RedosSeverity as RedosSeverity, type code_RegexError as RegexError, type code_RegexFlag as RegexFlag, type code_RegexMatch as RegexMatch, type code_RegexResult as RegexResult, type code_RegexSuccess as RegexSuccess, code_SQL_DEFAULT_OPTIONS as SQL_DEFAULT_OPTIONS, type code_Segment as Segment, type code_SpecVersion as SpecVersion, type code_SqlDialect as SqlDialect, type code_SqlFormatOptions as SqlFormatOptions, type code_TestPatternResult as TestPatternResult, code_buildDockerPull as buildDockerPull, code_buildDockerRun as buildDockerRun, code_buildGitCommand as buildGitCommand, code_collapseHtmlWhitespace as collapseHtmlWhitespace, code_collapseJsWhitespace as collapseJsWhitespace, code_countProperties as countProperties, code_countRules as countRules, code_detectDialect as detectDialect, code_detectOperationType as detectOperationType, code_detectRedos as detectRedos, code_envToExport as envToExport, code_envToJson as envToJson, code_formatCss as formatCss, code_formatGql as formatGql, code_formatHtml as formatHtml, code_formatSql as formatSql, code_getEndpointsByTag as getEndpointsByTag, code_getHtmlStats as getHtmlStats, code_getJsStats as getJsStats, code_getMethodColor as getMethodColor, code_isValidTag as isValidTag, code_lookupMime as lookupMime, code_minifyCss as minifyCss, code_minifyGql as minifyGql, code_minifyHtml as minifyHtml, code_minifyJs as minifyJs, code_minifySql as minifySql, code_normalizeRef as normalizeRef, code_parseDockerImage as parseDockerImage, code_parseEnv as parseEnv, code_parseGitCommand as parseGitCommand, code_parseGithubUrl as parseGithubUrl, code_parseSpec as parseSpec, code_removeConsoleCalls as removeConsoleCalls, code_removeEmptyAttributes as removeEmptyAttributes, code_removeHtmlComments as removeHtmlComments, code_removeJsComments as removeJsComments, code_removeRedundantAttributes as removeRedundantAttributes, code_searchCheatsheet as searchCheatsheet, code_searchEndpoints as searchEndpoints, code_splitStatements as splitStatements, code_testPattern as testPattern, code_testRegex as testRegex, code_validateEnvKey as validateEnvKey };
|
|
393
424
|
}
|
|
394
425
|
|
|
395
|
-
export {
|
|
426
|
+
export { formatCss as $, ALL_FLAGS as A, type SqlDialect as B, CHEATSHEET as C, type DockerImageRef as D, type EnvEntry as E, type SqlFormatOptions as F, type GitCommandConfig as G, type HtmlFormatResult as H, buildDockerPull as I, type JsMinifyOptions as J, buildDockerRun as K, buildGitCommand as L, MIME_TABLE as M, collapseHtmlWhitespace as N, OPERATIONS as O, type ParsedSpec as P, collapseJsWhitespace as Q, type RedosFinding as R, SQL_DEFAULT_OPTIONS as S, type TestPatternResult as T, countProperties as U, countRules as V, detectDialect as W, detectOperationType as X, detectRedos as Y, envToExport as Z, envToJson as _, type ApiEndpoint as a, formatGql as a0, formatHtml as a1, formatSql as a2, getEndpointsByTag as a3, getHtmlStats as a4, getJsStats as a5, getMethodColor as a6, isValidTag as a7, lookupMime as a8, minifyCss as a9, minifyGql as aa, minifyHtml as ab, minifyJs as ac, minifySql as ad, normalizeRef as ae, parseDockerImage as af, parseEnv as ag, parseGitCommand as ah, parseGithubUrl as ai, parseSpec as aj, removeConsoleCalls as ak, removeEmptyAttributes as al, removeHtmlComments as am, removeJsComments as an, removeRedundantAttributes as ao, searchCheatsheet as ap, searchEndpoints as aq, splitStatements as ar, testPattern as as, testRegex as at, validateEnvKey as au, type ApiInfo as b, code as c, type CheatsheetEntry as d, type CssResult as e, type EnvParseResult as f, type GitCommandResult as g, type GitOperation as h, type GithubUrlInfo as i, type GithubUrlType as j, type GqlFormatResult as k, type HtmlMinifyOptions as l, type HtmlMinifyResult as m, type JsMinifyResult as n, type MatchResult as o, type MimeEntry as p, type MimeLookupResult as q, type RedosResult as r, type RedosSeverity as s, type RegexError as t, type RegexFlag as u, type RegexMatch as v, type RegexResult as w, type RegexSuccess as x, type Segment as y, type SpecVersion as z };
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* - css-minifier (minifyCss, formatCss, countRules, countProperties)
|
|
20
20
|
* - mime-lookup (lookupMime, MIME_TABLE)
|
|
21
21
|
* - github-url-parser (parseGithubUrl)
|
|
22
|
+
* - redos-detector (detectRedos)
|
|
22
23
|
*/
|
|
23
24
|
type RegexFlag = 'g' | 'i' | 'm' | 's' | 'u';
|
|
24
25
|
declare const ALL_FLAGS: RegexFlag[];
|
|
@@ -307,6 +308,32 @@ interface GithubUrlInfo {
|
|
|
307
308
|
declare function parseGithubUrl(input: string): GithubUrlInfo | {
|
|
308
309
|
error: string;
|
|
309
310
|
};
|
|
311
|
+
type RedosSeverity = 'high' | 'medium';
|
|
312
|
+
interface RedosFinding {
|
|
313
|
+
type: 'nested-quantifier' | 'overlapping-alternation' | 'adjacent-quantifier';
|
|
314
|
+
severity: RedosSeverity;
|
|
315
|
+
message: string;
|
|
316
|
+
snippet: string;
|
|
317
|
+
index: number;
|
|
318
|
+
}
|
|
319
|
+
interface RedosResult {
|
|
320
|
+
pattern: string;
|
|
321
|
+
valid: boolean;
|
|
322
|
+
compileError?: string;
|
|
323
|
+
risk: 'none' | 'medium' | 'high';
|
|
324
|
+
safe: boolean;
|
|
325
|
+
findings: RedosFinding[];
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Statically analyze a regex pattern for catastrophic-backtracking (ReDoS)
|
|
329
|
+
* risk. Pass the pattern body only (no delimiters or flags). This is a
|
|
330
|
+
* heuristic linter focused on the classic exponential/quadratic constructs
|
|
331
|
+
* (nested quantifiers, overlapping alternation under a repeat, adjacent
|
|
332
|
+
* overlapping quantifiers), not a formal guarantee.
|
|
333
|
+
*/
|
|
334
|
+
declare function detectRedos(pattern: string): RedosResult | {
|
|
335
|
+
error: string;
|
|
336
|
+
};
|
|
310
337
|
|
|
311
338
|
declare const code_ALL_FLAGS: typeof ALL_FLAGS;
|
|
312
339
|
type code_ApiEndpoint = ApiEndpoint;
|
|
@@ -334,6 +361,9 @@ type code_MimeEntry = MimeEntry;
|
|
|
334
361
|
type code_MimeLookupResult = MimeLookupResult;
|
|
335
362
|
declare const code_OPERATIONS: typeof OPERATIONS;
|
|
336
363
|
type code_ParsedSpec = ParsedSpec;
|
|
364
|
+
type code_RedosFinding = RedosFinding;
|
|
365
|
+
type code_RedosResult = RedosResult;
|
|
366
|
+
type code_RedosSeverity = RedosSeverity;
|
|
337
367
|
type code_RegexError = RegexError;
|
|
338
368
|
type code_RegexFlag = RegexFlag;
|
|
339
369
|
type code_RegexMatch = RegexMatch;
|
|
@@ -354,6 +384,7 @@ declare const code_countProperties: typeof countProperties;
|
|
|
354
384
|
declare const code_countRules: typeof countRules;
|
|
355
385
|
declare const code_detectDialect: typeof detectDialect;
|
|
356
386
|
declare const code_detectOperationType: typeof detectOperationType;
|
|
387
|
+
declare const code_detectRedos: typeof detectRedos;
|
|
357
388
|
declare const code_envToExport: typeof envToExport;
|
|
358
389
|
declare const code_envToJson: typeof envToJson;
|
|
359
390
|
declare const code_formatCss: typeof formatCss;
|
|
@@ -389,7 +420,7 @@ declare const code_testPattern: typeof testPattern;
|
|
|
389
420
|
declare const code_testRegex: typeof testRegex;
|
|
390
421
|
declare const code_validateEnvKey: typeof validateEnvKey;
|
|
391
422
|
declare namespace code {
|
|
392
|
-
export { code_ALL_FLAGS as ALL_FLAGS, type code_ApiEndpoint as ApiEndpoint, type code_ApiInfo as ApiInfo, code_CHEATSHEET as CHEATSHEET, type code_CheatsheetEntry as CheatsheetEntry, type code_CssResult as CssResult, type code_DockerImageRef as DockerImageRef, type code_EnvEntry as EnvEntry, type code_EnvParseResult as EnvParseResult, type code_GitCommandConfig as GitCommandConfig, type code_GitCommandResult as GitCommandResult, type code_GitOperation as GitOperation, type code_GithubUrlInfo as GithubUrlInfo, type code_GithubUrlType as GithubUrlType, type code_GqlFormatResult as GqlFormatResult, type code_HtmlFormatResult as HtmlFormatResult, type code_HtmlMinifyOptions as HtmlMinifyOptions, type code_HtmlMinifyResult as HtmlMinifyResult, type code_JsMinifyOptions as JsMinifyOptions, type code_JsMinifyResult as JsMinifyResult, code_MIME_TABLE as MIME_TABLE, type code_MatchResult as MatchResult, type code_MimeEntry as MimeEntry, type code_MimeLookupResult as MimeLookupResult, code_OPERATIONS as OPERATIONS, type code_ParsedSpec as ParsedSpec, type code_RegexError as RegexError, type code_RegexFlag as RegexFlag, type code_RegexMatch as RegexMatch, type code_RegexResult as RegexResult, type code_RegexSuccess as RegexSuccess, code_SQL_DEFAULT_OPTIONS as SQL_DEFAULT_OPTIONS, type code_Segment as Segment, type code_SpecVersion as SpecVersion, type code_SqlDialect as SqlDialect, type code_SqlFormatOptions as SqlFormatOptions, type code_TestPatternResult as TestPatternResult, code_buildDockerPull as buildDockerPull, code_buildDockerRun as buildDockerRun, code_buildGitCommand as buildGitCommand, code_collapseHtmlWhitespace as collapseHtmlWhitespace, code_collapseJsWhitespace as collapseJsWhitespace, code_countProperties as countProperties, code_countRules as countRules, code_detectDialect as detectDialect, code_detectOperationType as detectOperationType, code_envToExport as envToExport, code_envToJson as envToJson, code_formatCss as formatCss, code_formatGql as formatGql, code_formatHtml as formatHtml, code_formatSql as formatSql, code_getEndpointsByTag as getEndpointsByTag, code_getHtmlStats as getHtmlStats, code_getJsStats as getJsStats, code_getMethodColor as getMethodColor, code_isValidTag as isValidTag, code_lookupMime as lookupMime, code_minifyCss as minifyCss, code_minifyGql as minifyGql, code_minifyHtml as minifyHtml, code_minifyJs as minifyJs, code_minifySql as minifySql, code_normalizeRef as normalizeRef, code_parseDockerImage as parseDockerImage, code_parseEnv as parseEnv, code_parseGitCommand as parseGitCommand, code_parseGithubUrl as parseGithubUrl, code_parseSpec as parseSpec, code_removeConsoleCalls as removeConsoleCalls, code_removeEmptyAttributes as removeEmptyAttributes, code_removeHtmlComments as removeHtmlComments, code_removeJsComments as removeJsComments, code_removeRedundantAttributes as removeRedundantAttributes, code_searchCheatsheet as searchCheatsheet, code_searchEndpoints as searchEndpoints, code_splitStatements as splitStatements, code_testPattern as testPattern, code_testRegex as testRegex, code_validateEnvKey as validateEnvKey };
|
|
423
|
+
export { code_ALL_FLAGS as ALL_FLAGS, type code_ApiEndpoint as ApiEndpoint, type code_ApiInfo as ApiInfo, code_CHEATSHEET as CHEATSHEET, type code_CheatsheetEntry as CheatsheetEntry, type code_CssResult as CssResult, type code_DockerImageRef as DockerImageRef, type code_EnvEntry as EnvEntry, type code_EnvParseResult as EnvParseResult, type code_GitCommandConfig as GitCommandConfig, type code_GitCommandResult as GitCommandResult, type code_GitOperation as GitOperation, type code_GithubUrlInfo as GithubUrlInfo, type code_GithubUrlType as GithubUrlType, type code_GqlFormatResult as GqlFormatResult, type code_HtmlFormatResult as HtmlFormatResult, type code_HtmlMinifyOptions as HtmlMinifyOptions, type code_HtmlMinifyResult as HtmlMinifyResult, type code_JsMinifyOptions as JsMinifyOptions, type code_JsMinifyResult as JsMinifyResult, code_MIME_TABLE as MIME_TABLE, type code_MatchResult as MatchResult, type code_MimeEntry as MimeEntry, type code_MimeLookupResult as MimeLookupResult, code_OPERATIONS as OPERATIONS, type code_ParsedSpec as ParsedSpec, type code_RedosFinding as RedosFinding, type code_RedosResult as RedosResult, type code_RedosSeverity as RedosSeverity, type code_RegexError as RegexError, type code_RegexFlag as RegexFlag, type code_RegexMatch as RegexMatch, type code_RegexResult as RegexResult, type code_RegexSuccess as RegexSuccess, code_SQL_DEFAULT_OPTIONS as SQL_DEFAULT_OPTIONS, type code_Segment as Segment, type code_SpecVersion as SpecVersion, type code_SqlDialect as SqlDialect, type code_SqlFormatOptions as SqlFormatOptions, type code_TestPatternResult as TestPatternResult, code_buildDockerPull as buildDockerPull, code_buildDockerRun as buildDockerRun, code_buildGitCommand as buildGitCommand, code_collapseHtmlWhitespace as collapseHtmlWhitespace, code_collapseJsWhitespace as collapseJsWhitespace, code_countProperties as countProperties, code_countRules as countRules, code_detectDialect as detectDialect, code_detectOperationType as detectOperationType, code_detectRedos as detectRedos, code_envToExport as envToExport, code_envToJson as envToJson, code_formatCss as formatCss, code_formatGql as formatGql, code_formatHtml as formatHtml, code_formatSql as formatSql, code_getEndpointsByTag as getEndpointsByTag, code_getHtmlStats as getHtmlStats, code_getJsStats as getJsStats, code_getMethodColor as getMethodColor, code_isValidTag as isValidTag, code_lookupMime as lookupMime, code_minifyCss as minifyCss, code_minifyGql as minifyGql, code_minifyHtml as minifyHtml, code_minifyJs as minifyJs, code_minifySql as minifySql, code_normalizeRef as normalizeRef, code_parseDockerImage as parseDockerImage, code_parseEnv as parseEnv, code_parseGitCommand as parseGitCommand, code_parseGithubUrl as parseGithubUrl, code_parseSpec as parseSpec, code_removeConsoleCalls as removeConsoleCalls, code_removeEmptyAttributes as removeEmptyAttributes, code_removeHtmlComments as removeHtmlComments, code_removeJsComments as removeJsComments, code_removeRedundantAttributes as removeRedundantAttributes, code_searchCheatsheet as searchCheatsheet, code_searchEndpoints as searchEndpoints, code_splitStatements as splitStatements, code_testPattern as testPattern, code_testRegex as testRegex, code_validateEnvKey as validateEnvKey };
|
|
393
424
|
}
|
|
394
425
|
|
|
395
|
-
export {
|
|
426
|
+
export { formatCss as $, ALL_FLAGS as A, type SqlDialect as B, CHEATSHEET as C, type DockerImageRef as D, type EnvEntry as E, type SqlFormatOptions as F, type GitCommandConfig as G, type HtmlFormatResult as H, buildDockerPull as I, type JsMinifyOptions as J, buildDockerRun as K, buildGitCommand as L, MIME_TABLE as M, collapseHtmlWhitespace as N, OPERATIONS as O, type ParsedSpec as P, collapseJsWhitespace as Q, type RedosFinding as R, SQL_DEFAULT_OPTIONS as S, type TestPatternResult as T, countProperties as U, countRules as V, detectDialect as W, detectOperationType as X, detectRedos as Y, envToExport as Z, envToJson as _, type ApiEndpoint as a, formatGql as a0, formatHtml as a1, formatSql as a2, getEndpointsByTag as a3, getHtmlStats as a4, getJsStats as a5, getMethodColor as a6, isValidTag as a7, lookupMime as a8, minifyCss as a9, minifyGql as aa, minifyHtml as ab, minifyJs as ac, minifySql as ad, normalizeRef as ae, parseDockerImage as af, parseEnv as ag, parseGitCommand as ah, parseGithubUrl as ai, parseSpec as aj, removeConsoleCalls as ak, removeEmptyAttributes as al, removeHtmlComments as am, removeJsComments as an, removeRedundantAttributes as ao, searchCheatsheet as ap, searchEndpoints as aq, splitStatements as ar, testPattern as as, testRegex as at, validateEnvKey as au, type ApiInfo as b, code as c, type CheatsheetEntry as d, type CssResult as e, type EnvParseResult as f, type GitCommandResult as g, type GitOperation as h, type GithubUrlInfo as i, type GithubUrlType as j, type GqlFormatResult as k, type HtmlMinifyOptions as l, type HtmlMinifyResult as m, type JsMinifyResult as n, type MatchResult as o, type MimeEntry as p, type MimeLookupResult as q, type RedosResult as r, type RedosSeverity as s, type RegexError as t, type RegexFlag as u, type RegexMatch as v, type RegexResult as w, type RegexSuccess as x, type Segment as y, type SpecVersion as z };
|
package/dist/index.cjs
CHANGED
|
@@ -11007,6 +11007,7 @@ __export(code_exports, {
|
|
|
11007
11007
|
countRules: () => countRules,
|
|
11008
11008
|
detectDialect: () => detectDialect,
|
|
11009
11009
|
detectOperationType: () => detectOperationType,
|
|
11010
|
+
detectRedos: () => detectRedos,
|
|
11010
11011
|
envToExport: () => envToExport2,
|
|
11011
11012
|
envToJson: () => envToJson2,
|
|
11012
11013
|
formatCss: () => formatCss,
|
|
@@ -13464,6 +13465,270 @@ function parseGithubUrl(input) {
|
|
|
13464
13465
|
}
|
|
13465
13466
|
return { error: `Unsupported GitHub URL path: /${rest.join("/")}` };
|
|
13466
13467
|
}
|
|
13468
|
+
var _REDOS_UNBOUNDED = /* @__PURE__ */ new Set(["*", "+"]);
|
|
13469
|
+
function _redosParseQuant(src, i) {
|
|
13470
|
+
const ch = src[i];
|
|
13471
|
+
if (ch === "*" || ch === "+" || ch === "?") {
|
|
13472
|
+
let end = i + 1;
|
|
13473
|
+
if (src[end] === "?" || src[end] === "+") end++;
|
|
13474
|
+
return { unbounded: _REDOS_UNBOUNDED.has(ch), raw: src.slice(i, end), end };
|
|
13475
|
+
}
|
|
13476
|
+
if (ch === "{") {
|
|
13477
|
+
const close = src.indexOf("}", i);
|
|
13478
|
+
if (close === -1) return null;
|
|
13479
|
+
const inner = src.slice(i + 1, close);
|
|
13480
|
+
if (!/^\d+(,\d*)?$/.test(inner)) return null;
|
|
13481
|
+
let end = close + 1;
|
|
13482
|
+
if (src[end] === "?" || src[end] === "+") end++;
|
|
13483
|
+
const unbounded = /^\d+,$/.test(inner);
|
|
13484
|
+
return { unbounded, raw: src.slice(i, end), end };
|
|
13485
|
+
}
|
|
13486
|
+
return null;
|
|
13487
|
+
}
|
|
13488
|
+
function _redosParseAlternation(src, start, endExclusive) {
|
|
13489
|
+
const alternatives = [];
|
|
13490
|
+
let current = [];
|
|
13491
|
+
let i = start;
|
|
13492
|
+
while (i < endExclusive) {
|
|
13493
|
+
const ch = src[i];
|
|
13494
|
+
if (ch === "|") {
|
|
13495
|
+
alternatives.push(current);
|
|
13496
|
+
current = [];
|
|
13497
|
+
i++;
|
|
13498
|
+
continue;
|
|
13499
|
+
}
|
|
13500
|
+
let node;
|
|
13501
|
+
if (ch === "\\") {
|
|
13502
|
+
const atomEnd = i + 2 <= endExclusive ? i + 2 : i + 1;
|
|
13503
|
+
node = { kind: "char", start: i, end: atomEnd, quant: null, quantEnd: atomEnd, raw: src.slice(i, atomEnd) };
|
|
13504
|
+
i = atomEnd;
|
|
13505
|
+
} else if (ch === "[") {
|
|
13506
|
+
let j = i + 1;
|
|
13507
|
+
if (src[j] === "^") j++;
|
|
13508
|
+
if (src[j] === "]") j++;
|
|
13509
|
+
while (j < endExclusive && src[j] !== "]") {
|
|
13510
|
+
j += src[j] === "\\" ? 2 : 1;
|
|
13511
|
+
}
|
|
13512
|
+
const classEnd = j < endExclusive ? j + 1 : endExclusive;
|
|
13513
|
+
node = { kind: "class", start: i, end: classEnd, quant: null, quantEnd: classEnd, raw: src.slice(i, classEnd) };
|
|
13514
|
+
i = classEnd;
|
|
13515
|
+
} else if (ch === "(") {
|
|
13516
|
+
let depth = 1;
|
|
13517
|
+
let j = i + 1;
|
|
13518
|
+
while (j < endExclusive && depth > 0) {
|
|
13519
|
+
const cj = src[j];
|
|
13520
|
+
if (cj === "\\") {
|
|
13521
|
+
j += 2;
|
|
13522
|
+
continue;
|
|
13523
|
+
}
|
|
13524
|
+
if (cj === "[") {
|
|
13525
|
+
j++;
|
|
13526
|
+
if (src[j] === "^") j++;
|
|
13527
|
+
if (src[j] === "]") j++;
|
|
13528
|
+
while (j < endExclusive && src[j] !== "]") {
|
|
13529
|
+
j += src[j] === "\\" ? 2 : 1;
|
|
13530
|
+
}
|
|
13531
|
+
j++;
|
|
13532
|
+
continue;
|
|
13533
|
+
}
|
|
13534
|
+
if (cj === "(") depth++;
|
|
13535
|
+
else if (cj === ")") depth--;
|
|
13536
|
+
j++;
|
|
13537
|
+
}
|
|
13538
|
+
const closeIdx = depth === 0 ? j - 1 : endExclusive;
|
|
13539
|
+
let contentStart = i + 1;
|
|
13540
|
+
let lookaround = false;
|
|
13541
|
+
if (src[i + 1] === "?") {
|
|
13542
|
+
const after = src[i + 2];
|
|
13543
|
+
if (after === ":") contentStart = i + 3;
|
|
13544
|
+
else if (after === "=" || after === "!") {
|
|
13545
|
+
contentStart = i + 3;
|
|
13546
|
+
lookaround = true;
|
|
13547
|
+
} else if (after === "<") {
|
|
13548
|
+
const a3 = src[i + 3];
|
|
13549
|
+
if (a3 === "=" || a3 === "!") {
|
|
13550
|
+
contentStart = i + 4;
|
|
13551
|
+
lookaround = true;
|
|
13552
|
+
} else {
|
|
13553
|
+
const gt = src.indexOf(">", i + 2);
|
|
13554
|
+
contentStart = gt !== -1 && gt < closeIdx ? gt + 1 : i + 3;
|
|
13555
|
+
}
|
|
13556
|
+
}
|
|
13557
|
+
}
|
|
13558
|
+
node = {
|
|
13559
|
+
kind: "group",
|
|
13560
|
+
start: i,
|
|
13561
|
+
end: closeIdx + 1,
|
|
13562
|
+
quant: null,
|
|
13563
|
+
quantEnd: closeIdx + 1,
|
|
13564
|
+
raw: src.slice(i, closeIdx + 1),
|
|
13565
|
+
alternatives: _redosParseAlternation(src, contentStart, closeIdx),
|
|
13566
|
+
lookaround
|
|
13567
|
+
};
|
|
13568
|
+
i = closeIdx + 1;
|
|
13569
|
+
} else if (ch === ".") {
|
|
13570
|
+
node = { kind: "dot", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: "." };
|
|
13571
|
+
i++;
|
|
13572
|
+
} else if (ch === "^" || ch === "$") {
|
|
13573
|
+
node = { kind: "anchor", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: ch };
|
|
13574
|
+
i++;
|
|
13575
|
+
} else {
|
|
13576
|
+
node = { kind: "char", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: ch };
|
|
13577
|
+
i++;
|
|
13578
|
+
}
|
|
13579
|
+
if (node.kind !== "anchor") {
|
|
13580
|
+
const q = _redosParseQuant(src, i);
|
|
13581
|
+
if (q) {
|
|
13582
|
+
node.quant = q;
|
|
13583
|
+
node.quantEnd = q.end;
|
|
13584
|
+
i = q.end;
|
|
13585
|
+
}
|
|
13586
|
+
}
|
|
13587
|
+
current.push(node);
|
|
13588
|
+
}
|
|
13589
|
+
alternatives.push(current);
|
|
13590
|
+
return alternatives;
|
|
13591
|
+
}
|
|
13592
|
+
var _REDOS_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_ .,-@/:;#%&=";
|
|
13593
|
+
function _redosFirstMatcher(node) {
|
|
13594
|
+
if (node.kind === "dot") return (c) => c !== "\n";
|
|
13595
|
+
if (node.kind === "char") {
|
|
13596
|
+
if (node.raw.startsWith("\\")) {
|
|
13597
|
+
const body = node.raw.slice(1);
|
|
13598
|
+
if (/^[dDwWsS]$/.test(body)) {
|
|
13599
|
+
try {
|
|
13600
|
+
const re = new RegExp("\\" + body);
|
|
13601
|
+
return (c) => re.test(c);
|
|
13602
|
+
} catch {
|
|
13603
|
+
return null;
|
|
13604
|
+
}
|
|
13605
|
+
}
|
|
13606
|
+
const literal = { n: "\n", t: " ", r: "\r", f: "\f", v: "\v" }[body] ?? body;
|
|
13607
|
+
return (c) => c === literal;
|
|
13608
|
+
}
|
|
13609
|
+
const lit = node.raw;
|
|
13610
|
+
return (c) => c === lit;
|
|
13611
|
+
}
|
|
13612
|
+
if (node.kind === "class") {
|
|
13613
|
+
try {
|
|
13614
|
+
const re = new RegExp(node.raw);
|
|
13615
|
+
return (c) => re.test(c);
|
|
13616
|
+
} catch {
|
|
13617
|
+
return null;
|
|
13618
|
+
}
|
|
13619
|
+
}
|
|
13620
|
+
if (node.kind === "group") {
|
|
13621
|
+
const first = node.alternatives?.[0]?.[0];
|
|
13622
|
+
return first ? _redosFirstMatcher(first) : null;
|
|
13623
|
+
}
|
|
13624
|
+
return null;
|
|
13625
|
+
}
|
|
13626
|
+
function _redosOverlaps(a, b) {
|
|
13627
|
+
const fa = _redosFirstMatcher(a);
|
|
13628
|
+
const fb = _redosFirstMatcher(b);
|
|
13629
|
+
if (!fa || !fb) return false;
|
|
13630
|
+
for (const c of _REDOS_ALPHABET) {
|
|
13631
|
+
if (fa(c) && fb(c)) return true;
|
|
13632
|
+
}
|
|
13633
|
+
return false;
|
|
13634
|
+
}
|
|
13635
|
+
function _redosAnalyzeGroup(node, findings) {
|
|
13636
|
+
const alts = node.alternatives ?? [];
|
|
13637
|
+
const outerUnbounded = node.quant?.unbounded === true && !node.lookaround;
|
|
13638
|
+
if (outerUnbounded) {
|
|
13639
|
+
for (const alt of alts) {
|
|
13640
|
+
const meaningful = alt.filter((n) => n.kind !== "anchor");
|
|
13641
|
+
if (meaningful.length === 1 && meaningful[0].quant?.unbounded) {
|
|
13642
|
+
findings.push({
|
|
13643
|
+
type: "nested-quantifier",
|
|
13644
|
+
severity: "high",
|
|
13645
|
+
message: "Nested unbounded quantifiers: a repeated group whose body is itself unbounded-repeated can match the same input in exponentially many ways, causing catastrophic backtracking.",
|
|
13646
|
+
snippet: node.raw + (node.quant?.raw ?? ""),
|
|
13647
|
+
index: node.start
|
|
13648
|
+
});
|
|
13649
|
+
break;
|
|
13650
|
+
}
|
|
13651
|
+
}
|
|
13652
|
+
if (alts.length > 1) {
|
|
13653
|
+
const firsts = alts.map((alt) => alt.find((n) => n.kind !== "anchor")).filter((n) => !!n);
|
|
13654
|
+
let flagged = false;
|
|
13655
|
+
for (let x = 0; x < firsts.length && !flagged; x++) {
|
|
13656
|
+
for (let y = x + 1; y < firsts.length; y++) {
|
|
13657
|
+
if (_redosOverlaps(firsts[x], firsts[y])) {
|
|
13658
|
+
findings.push({
|
|
13659
|
+
type: "overlapping-alternation",
|
|
13660
|
+
severity: "medium",
|
|
13661
|
+
message: "Overlapping alternation under a repetition: two branches can match the same starting input, so the engine has multiple ways to split each repeat and may backtrack heavily.",
|
|
13662
|
+
snippet: node.raw + (node.quant?.raw ?? ""),
|
|
13663
|
+
index: node.start
|
|
13664
|
+
});
|
|
13665
|
+
flagged = true;
|
|
13666
|
+
break;
|
|
13667
|
+
}
|
|
13668
|
+
}
|
|
13669
|
+
}
|
|
13670
|
+
}
|
|
13671
|
+
}
|
|
13672
|
+
for (const alt of alts) {
|
|
13673
|
+
_redosAnalyzeSequence(alt, findings);
|
|
13674
|
+
}
|
|
13675
|
+
}
|
|
13676
|
+
function _redosAnalyzeSequence(seq, findings) {
|
|
13677
|
+
for (let k = 0; k + 1 < seq.length; k++) {
|
|
13678
|
+
const a = seq[k];
|
|
13679
|
+
const b = seq[k + 1];
|
|
13680
|
+
if (a.quant?.unbounded && b.quant?.unbounded && _redosOverlaps(a, b)) {
|
|
13681
|
+
findings.push({
|
|
13682
|
+
type: "adjacent-quantifier",
|
|
13683
|
+
severity: "medium",
|
|
13684
|
+
message: "Adjacent unbounded quantifiers over overlapping characters: the engine can distribute the same input between them in many ways, causing quadratic backtracking.",
|
|
13685
|
+
snippet: seq[k].raw + (a.quant?.raw ?? "") + seq[k + 1].raw + (b.quant?.raw ?? ""),
|
|
13686
|
+
index: a.start
|
|
13687
|
+
});
|
|
13688
|
+
}
|
|
13689
|
+
}
|
|
13690
|
+
for (const node of seq) {
|
|
13691
|
+
if (node.kind === "group") _redosAnalyzeGroup(node, findings);
|
|
13692
|
+
}
|
|
13693
|
+
}
|
|
13694
|
+
function detectRedos(pattern) {
|
|
13695
|
+
if (typeof pattern !== "string" || pattern === "") {
|
|
13696
|
+
return { error: "Empty pattern" };
|
|
13697
|
+
}
|
|
13698
|
+
if (pattern.length > 5e3) {
|
|
13699
|
+
return { error: "Pattern too long (max 5,000 chars)" };
|
|
13700
|
+
}
|
|
13701
|
+
let valid = true;
|
|
13702
|
+
let compileError;
|
|
13703
|
+
try {
|
|
13704
|
+
new RegExp(pattern);
|
|
13705
|
+
} catch (e) {
|
|
13706
|
+
valid = false;
|
|
13707
|
+
compileError = e instanceof Error ? e.message : String(e);
|
|
13708
|
+
}
|
|
13709
|
+
const findings = [];
|
|
13710
|
+
const alternatives = _redosParseAlternation(pattern, 0, pattern.length);
|
|
13711
|
+
for (const alt of alternatives) {
|
|
13712
|
+
_redosAnalyzeSequence(alt, findings);
|
|
13713
|
+
}
|
|
13714
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13715
|
+
const unique = findings.filter((f) => {
|
|
13716
|
+
const key = `${f.type}:${f.index}`;
|
|
13717
|
+
if (seen.has(key)) return false;
|
|
13718
|
+
seen.add(key);
|
|
13719
|
+
return true;
|
|
13720
|
+
});
|
|
13721
|
+
unique.sort((a, b) => a.index - b.index);
|
|
13722
|
+
const risk = unique.some((f) => f.severity === "high") ? "high" : unique.length > 0 ? "medium" : "none";
|
|
13723
|
+
return {
|
|
13724
|
+
pattern,
|
|
13725
|
+
valid,
|
|
13726
|
+
...compileError ? { compileError } : {},
|
|
13727
|
+
risk,
|
|
13728
|
+
safe: unique.length === 0,
|
|
13729
|
+
findings: unique
|
|
13730
|
+
};
|
|
13731
|
+
}
|
|
13467
13732
|
|
|
13468
13733
|
// src/tools/color.ts
|
|
13469
13734
|
var color_exports = {};
|
package/dist/index.d.cts
CHANGED
|
@@ -8,7 +8,7 @@ export { t as time } from './time-DbT8fjaF.cjs';
|
|
|
8
8
|
export { u as units } from './units-BFTQwpBu.cjs';
|
|
9
9
|
export { n as network } from './network-DZugNyAQ.cjs';
|
|
10
10
|
export { a as api } from './api-Xl7OzIgq.cjs';
|
|
11
|
-
export { c as code } from './code-
|
|
11
|
+
export { c as code } from './code-DaRvff2v.cjs';
|
|
12
12
|
export { c as color } from './color-tPwZCr9H.cjs';
|
|
13
13
|
export { c as css } from './css-Cf7AMGM-.cjs';
|
|
14
14
|
export { m as misc } from './misc-CA3N198T.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { t as time } from './time-DbT8fjaF.js';
|
|
|
8
8
|
export { u as units } from './units-BFTQwpBu.js';
|
|
9
9
|
export { n as network } from './network-DZugNyAQ.js';
|
|
10
10
|
export { a as api } from './api-Xl7OzIgq.js';
|
|
11
|
-
export { c as code } from './code-
|
|
11
|
+
export { c as code } from './code-DaRvff2v.js';
|
|
12
12
|
export { c as color } from './color-tPwZCr9H.js';
|
|
13
13
|
export { c as css } from './css-Cf7AMGM-.js';
|
|
14
14
|
export { m as misc } from './misc-CA3N198T.js';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { media_exports as media } from './chunk-XMKJYVKB.js';
|
|
|
2
2
|
export { units_exports as units } from './chunk-U34B5XPK.js';
|
|
3
3
|
export { network_exports as network } from './chunk-3QMUUTBH.js';
|
|
4
4
|
export { api_exports as api } from './chunk-Q3B3YWOA.js';
|
|
5
|
-
export { code_exports as code } from './chunk-
|
|
5
|
+
export { code_exports as code } from './chunk-LXKIPPDG.js';
|
|
6
6
|
export { color_exports as color } from './chunk-BPVAB4P2.js';
|
|
7
7
|
export { css_exports as css } from './chunk-6YPV2AB5.js';
|
|
8
8
|
export { misc_exports as misc } from './chunk-GX7H6TAX.js';
|
package/dist/tools/code.cjs
CHANGED
|
@@ -2425,6 +2425,270 @@ function parseGithubUrl(input) {
|
|
|
2425
2425
|
}
|
|
2426
2426
|
return { error: `Unsupported GitHub URL path: /${rest.join("/")}` };
|
|
2427
2427
|
}
|
|
2428
|
+
var _REDOS_UNBOUNDED = /* @__PURE__ */ new Set(["*", "+"]);
|
|
2429
|
+
function _redosParseQuant(src, i) {
|
|
2430
|
+
const ch = src[i];
|
|
2431
|
+
if (ch === "*" || ch === "+" || ch === "?") {
|
|
2432
|
+
let end = i + 1;
|
|
2433
|
+
if (src[end] === "?" || src[end] === "+") end++;
|
|
2434
|
+
return { unbounded: _REDOS_UNBOUNDED.has(ch), raw: src.slice(i, end), end };
|
|
2435
|
+
}
|
|
2436
|
+
if (ch === "{") {
|
|
2437
|
+
const close = src.indexOf("}", i);
|
|
2438
|
+
if (close === -1) return null;
|
|
2439
|
+
const inner = src.slice(i + 1, close);
|
|
2440
|
+
if (!/^\d+(,\d*)?$/.test(inner)) return null;
|
|
2441
|
+
let end = close + 1;
|
|
2442
|
+
if (src[end] === "?" || src[end] === "+") end++;
|
|
2443
|
+
const unbounded = /^\d+,$/.test(inner);
|
|
2444
|
+
return { unbounded, raw: src.slice(i, end), end };
|
|
2445
|
+
}
|
|
2446
|
+
return null;
|
|
2447
|
+
}
|
|
2448
|
+
function _redosParseAlternation(src, start, endExclusive) {
|
|
2449
|
+
const alternatives = [];
|
|
2450
|
+
let current = [];
|
|
2451
|
+
let i = start;
|
|
2452
|
+
while (i < endExclusive) {
|
|
2453
|
+
const ch = src[i];
|
|
2454
|
+
if (ch === "|") {
|
|
2455
|
+
alternatives.push(current);
|
|
2456
|
+
current = [];
|
|
2457
|
+
i++;
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
let node;
|
|
2461
|
+
if (ch === "\\") {
|
|
2462
|
+
const atomEnd = i + 2 <= endExclusive ? i + 2 : i + 1;
|
|
2463
|
+
node = { kind: "char", start: i, end: atomEnd, quant: null, quantEnd: atomEnd, raw: src.slice(i, atomEnd) };
|
|
2464
|
+
i = atomEnd;
|
|
2465
|
+
} else if (ch === "[") {
|
|
2466
|
+
let j = i + 1;
|
|
2467
|
+
if (src[j] === "^") j++;
|
|
2468
|
+
if (src[j] === "]") j++;
|
|
2469
|
+
while (j < endExclusive && src[j] !== "]") {
|
|
2470
|
+
j += src[j] === "\\" ? 2 : 1;
|
|
2471
|
+
}
|
|
2472
|
+
const classEnd = j < endExclusive ? j + 1 : endExclusive;
|
|
2473
|
+
node = { kind: "class", start: i, end: classEnd, quant: null, quantEnd: classEnd, raw: src.slice(i, classEnd) };
|
|
2474
|
+
i = classEnd;
|
|
2475
|
+
} else if (ch === "(") {
|
|
2476
|
+
let depth = 1;
|
|
2477
|
+
let j = i + 1;
|
|
2478
|
+
while (j < endExclusive && depth > 0) {
|
|
2479
|
+
const cj = src[j];
|
|
2480
|
+
if (cj === "\\") {
|
|
2481
|
+
j += 2;
|
|
2482
|
+
continue;
|
|
2483
|
+
}
|
|
2484
|
+
if (cj === "[") {
|
|
2485
|
+
j++;
|
|
2486
|
+
if (src[j] === "^") j++;
|
|
2487
|
+
if (src[j] === "]") j++;
|
|
2488
|
+
while (j < endExclusive && src[j] !== "]") {
|
|
2489
|
+
j += src[j] === "\\" ? 2 : 1;
|
|
2490
|
+
}
|
|
2491
|
+
j++;
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
if (cj === "(") depth++;
|
|
2495
|
+
else if (cj === ")") depth--;
|
|
2496
|
+
j++;
|
|
2497
|
+
}
|
|
2498
|
+
const closeIdx = depth === 0 ? j - 1 : endExclusive;
|
|
2499
|
+
let contentStart = i + 1;
|
|
2500
|
+
let lookaround = false;
|
|
2501
|
+
if (src[i + 1] === "?") {
|
|
2502
|
+
const after = src[i + 2];
|
|
2503
|
+
if (after === ":") contentStart = i + 3;
|
|
2504
|
+
else if (after === "=" || after === "!") {
|
|
2505
|
+
contentStart = i + 3;
|
|
2506
|
+
lookaround = true;
|
|
2507
|
+
} else if (after === "<") {
|
|
2508
|
+
const a3 = src[i + 3];
|
|
2509
|
+
if (a3 === "=" || a3 === "!") {
|
|
2510
|
+
contentStart = i + 4;
|
|
2511
|
+
lookaround = true;
|
|
2512
|
+
} else {
|
|
2513
|
+
const gt = src.indexOf(">", i + 2);
|
|
2514
|
+
contentStart = gt !== -1 && gt < closeIdx ? gt + 1 : i + 3;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
node = {
|
|
2519
|
+
kind: "group",
|
|
2520
|
+
start: i,
|
|
2521
|
+
end: closeIdx + 1,
|
|
2522
|
+
quant: null,
|
|
2523
|
+
quantEnd: closeIdx + 1,
|
|
2524
|
+
raw: src.slice(i, closeIdx + 1),
|
|
2525
|
+
alternatives: _redosParseAlternation(src, contentStart, closeIdx),
|
|
2526
|
+
lookaround
|
|
2527
|
+
};
|
|
2528
|
+
i = closeIdx + 1;
|
|
2529
|
+
} else if (ch === ".") {
|
|
2530
|
+
node = { kind: "dot", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: "." };
|
|
2531
|
+
i++;
|
|
2532
|
+
} else if (ch === "^" || ch === "$") {
|
|
2533
|
+
node = { kind: "anchor", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: ch };
|
|
2534
|
+
i++;
|
|
2535
|
+
} else {
|
|
2536
|
+
node = { kind: "char", start: i, end: i + 1, quant: null, quantEnd: i + 1, raw: ch };
|
|
2537
|
+
i++;
|
|
2538
|
+
}
|
|
2539
|
+
if (node.kind !== "anchor") {
|
|
2540
|
+
const q = _redosParseQuant(src, i);
|
|
2541
|
+
if (q) {
|
|
2542
|
+
node.quant = q;
|
|
2543
|
+
node.quantEnd = q.end;
|
|
2544
|
+
i = q.end;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
current.push(node);
|
|
2548
|
+
}
|
|
2549
|
+
alternatives.push(current);
|
|
2550
|
+
return alternatives;
|
|
2551
|
+
}
|
|
2552
|
+
var _REDOS_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_ .,-@/:;#%&=";
|
|
2553
|
+
function _redosFirstMatcher(node) {
|
|
2554
|
+
if (node.kind === "dot") return (c) => c !== "\n";
|
|
2555
|
+
if (node.kind === "char") {
|
|
2556
|
+
if (node.raw.startsWith("\\")) {
|
|
2557
|
+
const body = node.raw.slice(1);
|
|
2558
|
+
if (/^[dDwWsS]$/.test(body)) {
|
|
2559
|
+
try {
|
|
2560
|
+
const re = new RegExp("\\" + body);
|
|
2561
|
+
return (c) => re.test(c);
|
|
2562
|
+
} catch {
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
const literal = { n: "\n", t: " ", r: "\r", f: "\f", v: "\v" }[body] ?? body;
|
|
2567
|
+
return (c) => c === literal;
|
|
2568
|
+
}
|
|
2569
|
+
const lit = node.raw;
|
|
2570
|
+
return (c) => c === lit;
|
|
2571
|
+
}
|
|
2572
|
+
if (node.kind === "class") {
|
|
2573
|
+
try {
|
|
2574
|
+
const re = new RegExp(node.raw);
|
|
2575
|
+
return (c) => re.test(c);
|
|
2576
|
+
} catch {
|
|
2577
|
+
return null;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
if (node.kind === "group") {
|
|
2581
|
+
const first = node.alternatives?.[0]?.[0];
|
|
2582
|
+
return first ? _redosFirstMatcher(first) : null;
|
|
2583
|
+
}
|
|
2584
|
+
return null;
|
|
2585
|
+
}
|
|
2586
|
+
function _redosOverlaps(a, b) {
|
|
2587
|
+
const fa = _redosFirstMatcher(a);
|
|
2588
|
+
const fb = _redosFirstMatcher(b);
|
|
2589
|
+
if (!fa || !fb) return false;
|
|
2590
|
+
for (const c of _REDOS_ALPHABET) {
|
|
2591
|
+
if (fa(c) && fb(c)) return true;
|
|
2592
|
+
}
|
|
2593
|
+
return false;
|
|
2594
|
+
}
|
|
2595
|
+
function _redosAnalyzeGroup(node, findings) {
|
|
2596
|
+
const alts = node.alternatives ?? [];
|
|
2597
|
+
const outerUnbounded = node.quant?.unbounded === true && !node.lookaround;
|
|
2598
|
+
if (outerUnbounded) {
|
|
2599
|
+
for (const alt of alts) {
|
|
2600
|
+
const meaningful = alt.filter((n) => n.kind !== "anchor");
|
|
2601
|
+
if (meaningful.length === 1 && meaningful[0].quant?.unbounded) {
|
|
2602
|
+
findings.push({
|
|
2603
|
+
type: "nested-quantifier",
|
|
2604
|
+
severity: "high",
|
|
2605
|
+
message: "Nested unbounded quantifiers: a repeated group whose body is itself unbounded-repeated can match the same input in exponentially many ways, causing catastrophic backtracking.",
|
|
2606
|
+
snippet: node.raw + (node.quant?.raw ?? ""),
|
|
2607
|
+
index: node.start
|
|
2608
|
+
});
|
|
2609
|
+
break;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
if (alts.length > 1) {
|
|
2613
|
+
const firsts = alts.map((alt) => alt.find((n) => n.kind !== "anchor")).filter((n) => !!n);
|
|
2614
|
+
let flagged = false;
|
|
2615
|
+
for (let x = 0; x < firsts.length && !flagged; x++) {
|
|
2616
|
+
for (let y = x + 1; y < firsts.length; y++) {
|
|
2617
|
+
if (_redosOverlaps(firsts[x], firsts[y])) {
|
|
2618
|
+
findings.push({
|
|
2619
|
+
type: "overlapping-alternation",
|
|
2620
|
+
severity: "medium",
|
|
2621
|
+
message: "Overlapping alternation under a repetition: two branches can match the same starting input, so the engine has multiple ways to split each repeat and may backtrack heavily.",
|
|
2622
|
+
snippet: node.raw + (node.quant?.raw ?? ""),
|
|
2623
|
+
index: node.start
|
|
2624
|
+
});
|
|
2625
|
+
flagged = true;
|
|
2626
|
+
break;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
for (const alt of alts) {
|
|
2633
|
+
_redosAnalyzeSequence(alt, findings);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
function _redosAnalyzeSequence(seq, findings) {
|
|
2637
|
+
for (let k = 0; k + 1 < seq.length; k++) {
|
|
2638
|
+
const a = seq[k];
|
|
2639
|
+
const b = seq[k + 1];
|
|
2640
|
+
if (a.quant?.unbounded && b.quant?.unbounded && _redosOverlaps(a, b)) {
|
|
2641
|
+
findings.push({
|
|
2642
|
+
type: "adjacent-quantifier",
|
|
2643
|
+
severity: "medium",
|
|
2644
|
+
message: "Adjacent unbounded quantifiers over overlapping characters: the engine can distribute the same input between them in many ways, causing quadratic backtracking.",
|
|
2645
|
+
snippet: seq[k].raw + (a.quant?.raw ?? "") + seq[k + 1].raw + (b.quant?.raw ?? ""),
|
|
2646
|
+
index: a.start
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
for (const node of seq) {
|
|
2651
|
+
if (node.kind === "group") _redosAnalyzeGroup(node, findings);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
function detectRedos(pattern) {
|
|
2655
|
+
if (typeof pattern !== "string" || pattern === "") {
|
|
2656
|
+
return { error: "Empty pattern" };
|
|
2657
|
+
}
|
|
2658
|
+
if (pattern.length > 5e3) {
|
|
2659
|
+
return { error: "Pattern too long (max 5,000 chars)" };
|
|
2660
|
+
}
|
|
2661
|
+
let valid = true;
|
|
2662
|
+
let compileError;
|
|
2663
|
+
try {
|
|
2664
|
+
new RegExp(pattern);
|
|
2665
|
+
} catch (e) {
|
|
2666
|
+
valid = false;
|
|
2667
|
+
compileError = e instanceof Error ? e.message : String(e);
|
|
2668
|
+
}
|
|
2669
|
+
const findings = [];
|
|
2670
|
+
const alternatives = _redosParseAlternation(pattern, 0, pattern.length);
|
|
2671
|
+
for (const alt of alternatives) {
|
|
2672
|
+
_redosAnalyzeSequence(alt, findings);
|
|
2673
|
+
}
|
|
2674
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2675
|
+
const unique = findings.filter((f) => {
|
|
2676
|
+
const key = `${f.type}:${f.index}`;
|
|
2677
|
+
if (seen.has(key)) return false;
|
|
2678
|
+
seen.add(key);
|
|
2679
|
+
return true;
|
|
2680
|
+
});
|
|
2681
|
+
unique.sort((a, b) => a.index - b.index);
|
|
2682
|
+
const risk = unique.some((f) => f.severity === "high") ? "high" : unique.length > 0 ? "medium" : "none";
|
|
2683
|
+
return {
|
|
2684
|
+
pattern,
|
|
2685
|
+
valid,
|
|
2686
|
+
...compileError ? { compileError } : {},
|
|
2687
|
+
risk,
|
|
2688
|
+
safe: unique.length === 0,
|
|
2689
|
+
findings: unique
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2428
2692
|
|
|
2429
2693
|
exports.ALL_FLAGS = ALL_FLAGS;
|
|
2430
2694
|
exports.CHEATSHEET = CHEATSHEET;
|
|
@@ -2440,6 +2704,7 @@ exports.countProperties = countProperties;
|
|
|
2440
2704
|
exports.countRules = countRules;
|
|
2441
2705
|
exports.detectDialect = detectDialect;
|
|
2442
2706
|
exports.detectOperationType = detectOperationType;
|
|
2707
|
+
exports.detectRedos = detectRedos;
|
|
2443
2708
|
exports.envToExport = envToExport;
|
|
2444
2709
|
exports.envToJson = envToJson;
|
|
2445
2710
|
exports.formatCss = formatCss;
|
package/dist/tools/code.d.cts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { A as ALL_FLAGS, a as ApiEndpoint, b as ApiInfo, C as CHEATSHEET, d as CheatsheetEntry, e as CssResult, D as DockerImageRef, E as EnvEntry, f as EnvParseResult, G as GitCommandConfig, g as GitCommandResult, h as GitOperation, i as GithubUrlInfo, j as GithubUrlType, k as GqlFormatResult, H as HtmlFormatResult, l as HtmlMinifyOptions, m as HtmlMinifyResult, J as JsMinifyOptions, n as JsMinifyResult, M as MIME_TABLE, o as MatchResult, p as MimeEntry, q as MimeLookupResult, O as OPERATIONS, P as ParsedSpec, R as
|
|
1
|
+
export { A as ALL_FLAGS, a as ApiEndpoint, b as ApiInfo, C as CHEATSHEET, d as CheatsheetEntry, e as CssResult, D as DockerImageRef, E as EnvEntry, f as EnvParseResult, G as GitCommandConfig, g as GitCommandResult, h as GitOperation, i as GithubUrlInfo, j as GithubUrlType, k as GqlFormatResult, H as HtmlFormatResult, l as HtmlMinifyOptions, m as HtmlMinifyResult, J as JsMinifyOptions, n as JsMinifyResult, M as MIME_TABLE, o as MatchResult, p as MimeEntry, q as MimeLookupResult, O as OPERATIONS, P as ParsedSpec, R as RedosFinding, r as RedosResult, s as RedosSeverity, t as RegexError, u as RegexFlag, v as RegexMatch, w as RegexResult, x as RegexSuccess, S as SQL_DEFAULT_OPTIONS, y as Segment, z as SpecVersion, B as SqlDialect, F as SqlFormatOptions, T as TestPatternResult, I as buildDockerPull, K as buildDockerRun, L as buildGitCommand, N as collapseHtmlWhitespace, Q as collapseJsWhitespace, U as countProperties, V as countRules, W as detectDialect, X as detectOperationType, Y as detectRedos, Z as envToExport, _ as envToJson, $ as formatCss, a0 as formatGql, a1 as formatHtml, a2 as formatSql, a3 as getEndpointsByTag, a4 as getHtmlStats, a5 as getJsStats, a6 as getMethodColor, a7 as isValidTag, a8 as lookupMime, a9 as minifyCss, aa as minifyGql, ab as minifyHtml, ac as minifyJs, ad as minifySql, ae as normalizeRef, af as parseDockerImage, ag as parseEnv, ah as parseGitCommand, ai as parseGithubUrl, aj as parseSpec, ak as removeConsoleCalls, al as removeEmptyAttributes, am as removeHtmlComments, an as removeJsComments, ao as removeRedundantAttributes, ap as searchCheatsheet, aq as searchEndpoints, ar as splitStatements, as as testPattern, at as testRegex, au as validateEnvKey } from '../code-DaRvff2v.cjs';
|
package/dist/tools/code.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { A as ALL_FLAGS, a as ApiEndpoint, b as ApiInfo, C as CHEATSHEET, d as CheatsheetEntry, e as CssResult, D as DockerImageRef, E as EnvEntry, f as EnvParseResult, G as GitCommandConfig, g as GitCommandResult, h as GitOperation, i as GithubUrlInfo, j as GithubUrlType, k as GqlFormatResult, H as HtmlFormatResult, l as HtmlMinifyOptions, m as HtmlMinifyResult, J as JsMinifyOptions, n as JsMinifyResult, M as MIME_TABLE, o as MatchResult, p as MimeEntry, q as MimeLookupResult, O as OPERATIONS, P as ParsedSpec, R as
|
|
1
|
+
export { A as ALL_FLAGS, a as ApiEndpoint, b as ApiInfo, C as CHEATSHEET, d as CheatsheetEntry, e as CssResult, D as DockerImageRef, E as EnvEntry, f as EnvParseResult, G as GitCommandConfig, g as GitCommandResult, h as GitOperation, i as GithubUrlInfo, j as GithubUrlType, k as GqlFormatResult, H as HtmlFormatResult, l as HtmlMinifyOptions, m as HtmlMinifyResult, J as JsMinifyOptions, n as JsMinifyResult, M as MIME_TABLE, o as MatchResult, p as MimeEntry, q as MimeLookupResult, O as OPERATIONS, P as ParsedSpec, R as RedosFinding, r as RedosResult, s as RedosSeverity, t as RegexError, u as RegexFlag, v as RegexMatch, w as RegexResult, x as RegexSuccess, S as SQL_DEFAULT_OPTIONS, y as Segment, z as SpecVersion, B as SqlDialect, F as SqlFormatOptions, T as TestPatternResult, I as buildDockerPull, K as buildDockerRun, L as buildGitCommand, N as collapseHtmlWhitespace, Q as collapseJsWhitespace, U as countProperties, V as countRules, W as detectDialect, X as detectOperationType, Y as detectRedos, Z as envToExport, _ as envToJson, $ as formatCss, a0 as formatGql, a1 as formatHtml, a2 as formatSql, a3 as getEndpointsByTag, a4 as getHtmlStats, a5 as getJsStats, a6 as getMethodColor, a7 as isValidTag, a8 as lookupMime, a9 as minifyCss, aa as minifyGql, ab as minifyHtml, ac as minifyJs, ad as minifySql, ae as normalizeRef, af as parseDockerImage, ag as parseEnv, ah as parseGitCommand, ai as parseGithubUrl, aj as parseSpec, ak as removeConsoleCalls, al as removeEmptyAttributes, am as removeHtmlComments, an as removeJsComments, ao as removeRedundantAttributes, ap as searchCheatsheet, aq as searchEndpoints, ar as splitStatements, as as testPattern, at as testRegex, au as validateEnvKey } from '../code-DaRvff2v.js';
|
package/dist/tools/code.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { ALL_FLAGS, CHEATSHEET, MIME_TABLE, OPERATIONS, SQL_DEFAULT_OPTIONS, buildDockerPull, buildDockerRun, buildGitCommand, collapseHtmlWhitespace, collapseJsWhitespace, countProperties, countRules, detectDialect, detectOperationType, envToExport, envToJson, formatCss, formatGql, formatHtml, formatSql, getEndpointsByTag, getHtmlStats, getJsStats, getMethodColor, isValidTag, lookupMime, minifyCss, minifyGql, minifyHtml, minifyJs, minifySql, normalizeRef, parseDockerImage, parseEnv, parseGitCommand, parseGithubUrl, parseSpec, removeConsoleCalls, removeEmptyAttributes, removeHtmlComments, removeJsComments, removeRedundantAttributes, searchCheatsheet, searchEndpoints, splitStatements, testPattern, testRegex, validateEnvKey } from '../chunk-
|
|
1
|
+
export { ALL_FLAGS, CHEATSHEET, MIME_TABLE, OPERATIONS, SQL_DEFAULT_OPTIONS, buildDockerPull, buildDockerRun, buildGitCommand, collapseHtmlWhitespace, collapseJsWhitespace, countProperties, countRules, detectDialect, detectOperationType, detectRedos, envToExport, envToJson, formatCss, formatGql, formatHtml, formatSql, getEndpointsByTag, getHtmlStats, getJsStats, getMethodColor, isValidTag, lookupMime, minifyCss, minifyGql, minifyHtml, minifyJs, minifySql, normalizeRef, parseDockerImage, parseEnv, parseGitCommand, parseGithubUrl, parseSpec, removeConsoleCalls, removeEmptyAttributes, removeHtmlComments, removeJsComments, removeRedundantAttributes, searchCheatsheet, searchEndpoints, splitStatements, testPattern, testRegex, validateEnvKey } from '../chunk-LXKIPPDG.js';
|
|
2
2
|
import '../chunk-MLKGABMK.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utilix-tech/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "154+ developer utility tools for Node.js: JSON, encoding, hashing, color, CSS, network and more. Runs locally, no API key required.",
|
|
5
5
|
"author": "Utilix <hello@utilix.tech>",
|
|
6
6
|
"license": "MIT",
|