@ts-graphviz/ast 3.0.3 → 3.0.4-next-6966a6699e87e2c74e7348aa6fdc2c50ae11b308
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 +23 -0
- package/README.md +16 -0
- package/lib/ast.d.ts +6 -0
- package/lib/ast.js +60 -38
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @ts-graphviz/ast
|
|
2
2
|
|
|
3
|
+
## 3.0.4-next-6966a6699e87e2c74e7348aa6fdc2c50ae11b308
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1526](https://github.com/ts-graphviz/ts-graphviz/pull/1526) [`00aaf2f`](https://github.com/ts-graphviz/ts-graphviz/commit/00aaf2ff6ef6fa8b6611ec2a477bc46b76fdebaf) Thanks [@kamiazya](https://github.com/kamiazya)! - Fix critical stack overflow vulnerability in HTML string parser
|
|
8
|
+
|
|
9
|
+
This patch addresses a critical security vulnerability where deeply nested HTML-like structures in DOT files could cause stack overflow, leading to application crashes and potential DoS attacks.
|
|
10
|
+
|
|
11
|
+
**Changes:**
|
|
12
|
+
|
|
13
|
+
- Added depth limit (default: 100) to HTML string parsing in PEG grammar
|
|
14
|
+
- Introduced `maxHtmlNestingDepth` option to `parse()` function for custom depth limits
|
|
15
|
+
- Improved parser to track and limit HTML nesting depth during parsing
|
|
16
|
+
|
|
17
|
+
**Security Impact:**
|
|
18
|
+
|
|
19
|
+
- Prevents stack overflow attacks from maliciously crafted DOT files with deep HTML nesting
|
|
20
|
+
- Normal use cases (typically <10 levels) are unaffected
|
|
21
|
+
- Configurable limit allows complex parsing when needed
|
|
22
|
+
|
|
23
|
+
**Breaking Changes:**
|
|
24
|
+
None. This is a backward-compatible security fix with sensible defaults.
|
|
25
|
+
|
|
3
26
|
## 3.0.3
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -66,6 +66,22 @@ console.log(ast);
|
|
|
66
66
|
// Output: A DotASTNode representing the DOT structure
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
#### Parser Options
|
|
70
|
+
|
|
71
|
+
The `parse` function accepts an optional second argument for configuration:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { parse } from "@ts-graphviz/ast";
|
|
75
|
+
|
|
76
|
+
// Parse with custom HTML nesting depth limit
|
|
77
|
+
const ast = parse(dotString, {
|
|
78
|
+
startRule: 'Dot', // Specify the starting rule (default: 'Dot')
|
|
79
|
+
maxHtmlNestingDepth: 200 // Set maximum HTML nesting depth (default: 100)
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Security Note**: The `maxHtmlNestingDepth` option limits the depth of nested HTML-like structures in DOT files to prevent stack overflow attacks. The default limit of 100 is sufficient for normal use cases (typically <10 levels). Increase this value only if you have legitimate deeply nested HTML structures.
|
|
84
|
+
|
|
69
85
|
### Generating DOT Language
|
|
70
86
|
|
|
71
87
|
```ts
|
package/lib/ast.d.ts
CHANGED
|
@@ -189,6 +189,12 @@ export declare interface CommonParseOptions {
|
|
|
189
189
|
* filename (optional): A string value that is used to identify the file to be parsed.
|
|
190
190
|
*/
|
|
191
191
|
filename?: string;
|
|
192
|
+
/**
|
|
193
|
+
* maxHtmlNestingDepth (optional): Maximum allowed nesting depth for HTML-like strings.
|
|
194
|
+
* Default is 100. This limit prevents stack overflow attacks from deeply nested HTML structures.
|
|
195
|
+
* @default 100
|
|
196
|
+
*/
|
|
197
|
+
maxHtmlNestingDepth?: number;
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
/**
|
package/lib/ast.js
CHANGED
|
@@ -485,16 +485,24 @@ function peg$parse(input, options) {
|
|
|
485
485
|
[]
|
|
486
486
|
);
|
|
487
487
|
}
|
|
488
|
-
function peg$f44(
|
|
489
|
-
|
|
488
|
+
function peg$f44() {
|
|
489
|
+
if (htmlNestingDepth >= MAX_HTML_NESTING_DEPTH) {
|
|
490
|
+
error(`HTML nesting depth exceeds maximum allowed depth of ${MAX_HTML_NESTING_DEPTH}`);
|
|
491
|
+
}
|
|
492
|
+
htmlNestingDepth++;
|
|
493
|
+
return true;
|
|
490
494
|
}
|
|
491
495
|
function peg$f45(v) {
|
|
492
|
-
|
|
496
|
+
htmlNestingDepth--;
|
|
497
|
+
return "<" + v.join("") + ">";
|
|
493
498
|
}
|
|
494
499
|
function peg$f46(v) {
|
|
500
|
+
return v;
|
|
501
|
+
}
|
|
502
|
+
function peg$f47(v) {
|
|
495
503
|
return v.join("");
|
|
496
504
|
}
|
|
497
|
-
function peg$
|
|
505
|
+
function peg$f48(chars) {
|
|
498
506
|
return b.createElement(
|
|
499
507
|
"Literal",
|
|
500
508
|
{
|
|
@@ -504,13 +512,13 @@ function peg$parse(input, options) {
|
|
|
504
512
|
[]
|
|
505
513
|
);
|
|
506
514
|
}
|
|
507
|
-
function peg$
|
|
515
|
+
function peg$f49() {
|
|
508
516
|
return text();
|
|
509
517
|
}
|
|
510
|
-
function peg$
|
|
518
|
+
function peg$f50(v) {
|
|
511
519
|
return v[1] === '"' ? '"' : v[0] + v[1];
|
|
512
520
|
}
|
|
513
|
-
function peg$
|
|
521
|
+
function peg$f51() {
|
|
514
522
|
return "";
|
|
515
523
|
}
|
|
516
524
|
let peg$currPos = options.peg$currPos | 0;
|
|
@@ -2257,7 +2265,7 @@ function peg$parse(input, options) {
|
|
|
2257
2265
|
return s0;
|
|
2258
2266
|
}
|
|
2259
2267
|
function peg$parsehtml_raw_string() {
|
|
2260
|
-
let s0, s1, s2, s3;
|
|
2268
|
+
let s0, s1, s2, s3, s4;
|
|
2261
2269
|
s0 = peg$currPos;
|
|
2262
2270
|
if (input.charCodeAt(peg$currPos) === 60) {
|
|
2263
2271
|
s1 = peg$c30;
|
|
@@ -2269,30 +2277,42 @@ function peg$parse(input, options) {
|
|
|
2269
2277
|
}
|
|
2270
2278
|
}
|
|
2271
2279
|
if (s1 !== peg$FAILED) {
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
if (
|
|
2275
|
-
|
|
2280
|
+
peg$savedPos = peg$currPos;
|
|
2281
|
+
s2 = peg$f44();
|
|
2282
|
+
if (s2) {
|
|
2283
|
+
s2 = void 0;
|
|
2284
|
+
} else {
|
|
2285
|
+
s2 = peg$FAILED;
|
|
2276
2286
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
if (
|
|
2281
|
-
|
|
2287
|
+
if (s2 !== peg$FAILED) {
|
|
2288
|
+
s3 = [];
|
|
2289
|
+
s4 = peg$parsehtml_char();
|
|
2290
|
+
if (s4 === peg$FAILED) {
|
|
2291
|
+
s4 = peg$parsehtml_raw_string();
|
|
2282
2292
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2293
|
+
while (s4 !== peg$FAILED) {
|
|
2294
|
+
s3.push(s4);
|
|
2295
|
+
s4 = peg$parsehtml_char();
|
|
2296
|
+
if (s4 === peg$FAILED) {
|
|
2297
|
+
s4 = peg$parsehtml_raw_string();
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
if (input.charCodeAt(peg$currPos) === 62) {
|
|
2301
|
+
s4 = peg$c31;
|
|
2302
|
+
peg$currPos++;
|
|
2303
|
+
} else {
|
|
2304
|
+
s4 = peg$FAILED;
|
|
2305
|
+
if (peg$silentFails === 0) {
|
|
2306
|
+
peg$fail(peg$e39);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
if (s4 !== peg$FAILED) {
|
|
2310
|
+
peg$savedPos = s0;
|
|
2311
|
+
s0 = peg$f45(s3);
|
|
2312
|
+
} else {
|
|
2313
|
+
peg$currPos = s0;
|
|
2314
|
+
s0 = peg$FAILED;
|
|
2291
2315
|
}
|
|
2292
|
-
}
|
|
2293
|
-
if (s3 !== peg$FAILED) {
|
|
2294
|
-
peg$savedPos = s0;
|
|
2295
|
-
s0 = peg$f44(s2);
|
|
2296
2316
|
} else {
|
|
2297
2317
|
peg$currPos = s0;
|
|
2298
2318
|
s0 = peg$FAILED;
|
|
@@ -2338,7 +2358,7 @@ function peg$parse(input, options) {
|
|
|
2338
2358
|
}
|
|
2339
2359
|
if (s4 !== peg$FAILED) {
|
|
2340
2360
|
peg$savedPos = s2;
|
|
2341
|
-
s2 = peg$
|
|
2361
|
+
s2 = peg$f46(s4);
|
|
2342
2362
|
} else {
|
|
2343
2363
|
peg$currPos = s2;
|
|
2344
2364
|
s2 = peg$FAILED;
|
|
@@ -2381,7 +2401,7 @@ function peg$parse(input, options) {
|
|
|
2381
2401
|
}
|
|
2382
2402
|
if (s4 !== peg$FAILED) {
|
|
2383
2403
|
peg$savedPos = s2;
|
|
2384
|
-
s2 = peg$
|
|
2404
|
+
s2 = peg$f46(s4);
|
|
2385
2405
|
} else {
|
|
2386
2406
|
peg$currPos = s2;
|
|
2387
2407
|
s2 = peg$FAILED;
|
|
@@ -2396,7 +2416,7 @@ function peg$parse(input, options) {
|
|
|
2396
2416
|
}
|
|
2397
2417
|
if (s1 !== peg$FAILED) {
|
|
2398
2418
|
peg$savedPos = s0;
|
|
2399
|
-
s1 = peg$
|
|
2419
|
+
s1 = peg$f47(s1);
|
|
2400
2420
|
}
|
|
2401
2421
|
s0 = s1;
|
|
2402
2422
|
return s0;
|
|
@@ -2431,7 +2451,7 @@ function peg$parse(input, options) {
|
|
|
2431
2451
|
}
|
|
2432
2452
|
if (s3 !== peg$FAILED) {
|
|
2433
2453
|
peg$savedPos = s0;
|
|
2434
|
-
s0 = peg$
|
|
2454
|
+
s0 = peg$f48(s2);
|
|
2435
2455
|
} else {
|
|
2436
2456
|
peg$currPos = s0;
|
|
2437
2457
|
s0 = peg$FAILED;
|
|
@@ -2469,7 +2489,7 @@ function peg$parse(input, options) {
|
|
|
2469
2489
|
s2 = peg$parseSourceCharacter();
|
|
2470
2490
|
if (s2 !== peg$FAILED) {
|
|
2471
2491
|
peg$savedPos = s0;
|
|
2472
|
-
s0 = peg$
|
|
2492
|
+
s0 = peg$f49();
|
|
2473
2493
|
} else {
|
|
2474
2494
|
peg$currPos = s0;
|
|
2475
2495
|
s0 = peg$FAILED;
|
|
@@ -2520,7 +2540,7 @@ function peg$parse(input, options) {
|
|
|
2520
2540
|
}
|
|
2521
2541
|
if (s1 !== peg$FAILED) {
|
|
2522
2542
|
peg$savedPos = s0;
|
|
2523
|
-
s1 = peg$
|
|
2543
|
+
s1 = peg$f50(s1);
|
|
2524
2544
|
}
|
|
2525
2545
|
s0 = s1;
|
|
2526
2546
|
return s0;
|
|
@@ -2541,7 +2561,7 @@ function peg$parse(input, options) {
|
|
|
2541
2561
|
s2 = peg$parseLineTerminatorSequence();
|
|
2542
2562
|
if (s2 !== peg$FAILED) {
|
|
2543
2563
|
peg$savedPos = s0;
|
|
2544
|
-
s0 = peg$
|
|
2564
|
+
s0 = peg$f51();
|
|
2545
2565
|
} else {
|
|
2546
2566
|
peg$currPos = s0;
|
|
2547
2567
|
s0 = peg$FAILED;
|
|
@@ -2685,6 +2705,8 @@ function peg$parse(input, options) {
|
|
|
2685
2705
|
return str;
|
|
2686
2706
|
}
|
|
2687
2707
|
const edgeops = [];
|
|
2708
|
+
const MAX_HTML_NESTING_DEPTH = options.maxHtmlNestingDepth ?? 100;
|
|
2709
|
+
let htmlNestingDepth = 0;
|
|
2688
2710
|
const b = new Builder({
|
|
2689
2711
|
locationFunction: location
|
|
2690
2712
|
});
|
|
@@ -2721,9 +2743,9 @@ function peg$parse(input, options) {
|
|
|
2721
2743
|
}
|
|
2722
2744
|
}
|
|
2723
2745
|
function parse(input, options) {
|
|
2724
|
-
const { startRule, filename } = options ?? {};
|
|
2746
|
+
const { startRule, filename, maxHtmlNestingDepth } = options ?? {};
|
|
2725
2747
|
try {
|
|
2726
|
-
return peg$parse(input, { startRule, filename });
|
|
2748
|
+
return peg$parse(input, { startRule, filename, maxHtmlNestingDepth });
|
|
2727
2749
|
} catch (e) {
|
|
2728
2750
|
if (e instanceof peg$SyntaxError) {
|
|
2729
2751
|
throw new DotSyntaxError(e.message, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-graphviz/ast",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4-next-6966a6699e87e2c74e7348aa6fdc2c50ae11b308",
|
|
4
4
|
"description": "Graphviz AST(Abstract Syntax Tree) Utilities",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/ts-graphviz/ts-graphviz#readme",
|