diff --git a/docs/enclave/core-libraries/ast-guard/pre-scanner.mdx b/docs/enclave/core-libraries/ast-guard/pre-scanner.mdx index 7f79dc6..38c6510 100644 --- a/docs/enclave/core-libraries/ast-guard/pre-scanner.mdx +++ b/docs/enclave/core-libraries/ast-guard/pre-scanner.mdx @@ -129,8 +129,10 @@ The pre-scanner detects dangerous regex patterns that cause exponential backtrac ### Manual ReDoS Analysis +`@enclave-vm/ast/redos` exports the analyzer on its own: it loads none of the parser (`acorn`, `acorn-walk`, `astring`) that the package root loads. The package root exports the same functions. + ```ts -import { analyzeForReDoS } from '@enclave-vm/ast'; +import { analyzeForReDoS } from '@enclave-vm/ast/redos'; // Analyze a pattern for ReDoS vulnerabilities const result = analyzeForReDoS('(a+)+', 'catastrophic'); diff --git a/docs/enclave/libraries/ast-guard.mdx b/docs/enclave/libraries/ast-guard.mdx index 1e597e2..051a333 100644 --- a/docs/enclave/libraries/ast-guard.mdx +++ b/docs/enclave/libraries/ast-guard.mdx @@ -114,7 +114,8 @@ The pre-scanner supports three regex handling modes: - **`allow`** - Allow all regex without analysis (Permissive only) ```ts -import { createPreScannerConfig, analyzeForReDoS } from '@enclave-vm/ast'; +// The analyzer alone, without the parser the package root loads +import { analyzeForReDoS } from '@enclave-vm/ast/redos'; // Analyze a pattern for ReDoS vulnerabilities const result = analyzeForReDoS('(a+)+', 'catastrophic'); diff --git a/libs/ast/README.md b/libs/ast/README.md index 769bbe6..5cf23a9 100644 --- a/libs/ast/README.md +++ b/libs/ast/README.md @@ -833,6 +833,19 @@ npm run test:coverage - **Validation time**: ~1-10ms per rule (depends on complexity) - **Memory**: Minimal overhead, AST is shared across rules +## ReDoS Analysis Without the Parser + +To check regular expressions for catastrophic backtracking without validating JavaScript, import the analyzer from `@enclave-vm/ast/redos`. It loads none of the parser (`acorn`, `acorn-walk`, `astring`) that the package root loads: about 8 KB instead of 240 KB plus the parser. + +```ts +import { analyzeForReDoS, REDOS_THRESHOLDS } from '@enclave-vm/ast/redos'; + +const { score } = analyzeForReDoS('(a+)+$', 'catastrophic'); +if (score >= REDOS_THRESHOLDS.BLOCK) throw new Error('Pattern can backtrack catastrophically'); +``` + +It also exports `REDOS_PATTERNS`, `calculateStarHeight` and the `ReDoSAnalysisResult` type, the same as the package root. + ## Comparison | Feature | AST Guard | ESLint | TypeScript | diff --git a/libs/ast/package.json b/libs/ast/package.json index 14a82a0..b4044a1 100644 --- a/libs/ast/package.json +++ b/libs/ast/package.json @@ -37,6 +37,13 @@ "import": "./dist/esm/index.mjs", "require": "./dist/index.js", "default": "./dist/index.js" + }, + "./redos": { + "development": "./src/redos.ts", + "types": "./dist/redos.d.ts", + "import": "./dist/esm/redos.mjs", + "require": "./dist/redos.js", + "default": "./dist/redos.js" } }, "dependencies": { diff --git a/libs/ast/project.json b/libs/ast/project.json index 4dff5db..91c51ea 100644 --- a/libs/ast/project.json +++ b/libs/ast/project.json @@ -19,6 +19,7 @@ "bundle": true, "thirdParty": false, "platform": "node", + "additionalEntryPoints": ["libs/ast/src/redos.ts"], "assets": ["libs/ast/README.md", "libs/ast/CHANGELOG.md", "LICENSE", "libs/ast/package.json"], "esbuildOptions": { "outExtension": { ".js": ".js" }, @@ -39,6 +40,7 @@ "bundle": true, "thirdParty": false, "platform": "node", + "additionalEntryPoints": ["libs/ast/src/redos.ts"], "esbuildOptions": { "outExtension": { ".js": ".mjs" }, "external": ["acorn", "acorn-walk", "astring", "@types/estree"] diff --git a/libs/ast/src/__tests__/redos-entry.spec.ts b/libs/ast/src/__tests__/redos-entry.spec.ts new file mode 100644 index 0000000..3213b4d --- /dev/null +++ b/libs/ast/src/__tests__/redos-entry.spec.ts @@ -0,0 +1,21 @@ +jest.mock('acorn', () => { + throw new Error('@enclave-vm/ast/redos loaded acorn'); +}); +jest.mock('acorn-walk', () => { + throw new Error('@enclave-vm/ast/redos loaded acorn-walk'); +}); +jest.mock('astring', () => { + throw new Error('@enclave-vm/ast/redos loaded astring'); +}); + +import { REDOS_THRESHOLDS, analyzeForReDoS, calculateStarHeight } from '../redos'; + +describe('@enclave-vm/ast/redos', () => { + it('analyzes patterns without loading the parser', () => { + const nested = analyzeForReDoS('(a+)+', 'catastrophic'); + expect(nested.vulnerable).toBe(true); + expect(nested.score).toBeGreaterThanOrEqual(REDOS_THRESHOLDS.BLOCK); + expect(analyzeForReDoS('[a-z]+', 'polynomial').vulnerable).toBe(false); + expect(calculateStarHeight('(a*)*')).toBe(2); + }); +}); diff --git a/libs/ast/src/redos.ts b/libs/ast/src/redos.ts new file mode 100644 index 0000000..023821f --- /dev/null +++ b/libs/ast/src/redos.ts @@ -0,0 +1,11 @@ +/** + * `@enclave-vm/ast/redos`: the ReDoS analyzer on its own, for code that checks regular expressions without validating + * JavaScript. It loads none of the parser (acorn, acorn-walk, astring) that the package root loads. + */ +export { + REDOS_PATTERNS, + REDOS_THRESHOLDS, + analyzeForReDoS, + calculateStarHeight, +} from './pre-scanner/checks/regex-check'; +export type { ReDoSAnalysisResult } from './pre-scanner/checks/regex-check';