@lipemat/eslint-config 5.0.1 → 5.1.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/helpers/config.js +1 -0
- package/helpers/config.js.map +1 -0
- package/{types/helpers/config.d.ts → helpers/config.ts} +16 -5
- package/index.js +1 -0
- package/package.json +9 -12
- package/plugins/security/helpers/dom-purify.js +1 -0
- package/plugins/security/helpers/dom-purify.js.map +1 -0
- package/plugins/security/helpers/dom-purify.ts +21 -0
- package/plugins/security/helpers/element.js +1 -0
- package/plugins/security/helpers/element.js.map +1 -0
- package/plugins/security/helpers/element.ts +22 -0
- package/plugins/security/helpers/string.js +1 -0
- package/plugins/security/helpers/string.js.map +1 -0
- package/plugins/security/helpers/string.ts +44 -0
- package/plugins/security/helpers/ts-types.js +1 -0
- package/plugins/security/helpers/ts-types.js.map +1 -0
- package/plugins/security/helpers/ts-types.ts +11 -0
- package/plugins/security/index.js +10 -0
- package/plugins/security/index.js.map +1 -0
- package/plugins/security/index.ts +74 -0
- package/plugins/security/package.json +25 -0
- package/plugins/security/rules/dangerously-set-inner-html.js +1 -0
- package/plugins/security/rules/dangerously-set-inner-html.js.map +1 -0
- package/plugins/security/rules/dangerously-set-inner-html.ts +93 -0
- package/plugins/security/rules/html-executing-assignment.js +1 -0
- package/plugins/security/rules/html-executing-assignment.js.map +1 -0
- package/plugins/security/rules/html-executing-assignment.ts +78 -0
- package/plugins/security/rules/html-executing-function.js +1 -0
- package/plugins/security/rules/html-executing-function.js.map +1 -0
- package/plugins/security/rules/html-executing-function.ts +164 -0
- package/plugins/security/rules/html-sinks.js +1 -0
- package/plugins/security/rules/html-sinks.js.map +1 -0
- package/plugins/security/rules/html-sinks.ts +146 -0
- package/plugins/security/rules/html-string-concat.js +1 -0
- package/plugins/security/rules/html-string-concat.js.map +1 -0
- package/plugins/security/rules/html-string-concat.ts +71 -0
- package/plugins/security/rules/jquery-executing.js +1 -0
- package/plugins/security/rules/jquery-executing.js.map +1 -0
- package/plugins/security/rules/jquery-executing.ts +134 -0
- package/plugins/security/rules/no-at-html-tags.js +53 -0
- package/plugins/security/rules/no-at-html-tags.js.map +1 -0
- package/plugins/security/rules/no-at-html-tags.ts +74 -0
- package/plugins/security/rules/vulnerable-tag-stripping.js +1 -0
- package/plugins/security/rules/vulnerable-tag-stripping.js.map +1 -0
- package/plugins/security/rules/vulnerable-tag-stripping.ts +89 -0
- package/plugins/security/rules/window-escaping.js +2 -1
- package/plugins/security/rules/window-escaping.js.map +1 -0
- package/plugins/security/rules/window-escaping.ts +220 -0
- package/plugins/tsconfig.json +8 -0
- package/types/index.d.ts +0 -3
- package/types/plugins/security/helpers/dom-purify.d.ts +0 -6
- package/types/plugins/security/helpers/element.d.ts +0 -10
- package/types/plugins/security/helpers/string.d.ts +0 -20
- package/types/plugins/security/helpers/ts-types.d.ts +0 -6
- package/types/plugins/security/index.d.ts +0 -8
- package/types/plugins/security/rules/dangerously-set-inner-html.d.ts +0 -4
- package/types/plugins/security/rules/html-executing-assignment.d.ts +0 -4
- package/types/plugins/security/rules/html-executing-function.d.ts +0 -6
- package/types/plugins/security/rules/html-sinks.d.ts +0 -4
- package/types/plugins/security/rules/html-string-concat.d.ts +0 -9
- package/types/plugins/security/rules/jquery-executing.d.ts +0 -17
- package/types/plugins/security/rules/vulnerable-tag-stripping.d.ts +0 -4
- package/types/plugins/security/rules/window-escaping.d.ts +0 -5
package/helpers/config.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,mBAAmB,EAAC,MAAM,2CAA2C,CAAC;AAM9E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,SAAS,CAAE,OAA4B;IACtD,MAAM,IAAI,GAAG;QACZ,OAAO;KACP,CAAC;IACF,MAAM,YAAY,GAAqB;QACtC,GAAG,IAAI;QACP,GAAG,mBAAmB,CAAoB,eAAe,EAAE,IAAI,CAAE;KACjE,CAAC;IACF,OAAO,YAAY,CAAC,OAAO,CAAC;AAC7B,CAAC"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {getExtensionsConfig} from '@lipemat/js-boilerplate/helpers/config.js';
|
|
2
|
+
import type {FlatConfig} from '@typescript-eslint/utils/ts-eslint';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export type ExtensionConfigs = { configs: FlatConfig.Config[] };
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Get a config from our /index.js merged with any
|
|
7
9
|
* matching configuration from the project directory.
|
|
@@ -22,4 +24,13 @@ export type ExtensionConfigs = {
|
|
|
22
24
|
* return config
|
|
23
25
|
* }
|
|
24
26
|
*/
|
|
25
|
-
export
|
|
27
|
+
export function getConfig( configs: FlatConfig.Config[] ): FlatConfig.Config[] {
|
|
28
|
+
const BASE = {
|
|
29
|
+
configs,
|
|
30
|
+
};
|
|
31
|
+
const mergedConfig: ExtensionConfigs = {
|
|
32
|
+
...BASE,
|
|
33
|
+
...getExtensionsConfig<ExtensionConfigs>( 'eslint.config', BASE ),
|
|
34
|
+
};
|
|
35
|
+
return mergedConfig.configs;
|
|
36
|
+
}
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lipemat/eslint-config",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Eslint configuration for all @lipemat packages",
|
|
6
6
|
"engines": {
|
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "index.js",
|
|
11
|
-
"types": "types",
|
|
12
11
|
"files": [
|
|
13
12
|
"index.js",
|
|
14
|
-
"helpers
|
|
15
|
-
"plugins
|
|
16
|
-
"types"
|
|
13
|
+
"helpers",
|
|
14
|
+
"plugins"
|
|
17
15
|
],
|
|
18
16
|
"repository": {
|
|
19
17
|
"type": "git",
|
|
@@ -24,16 +22,16 @@
|
|
|
24
22
|
},
|
|
25
23
|
"homepage": "https://github.com/lipemat/eslint-config#readme",
|
|
26
24
|
"scripts": {
|
|
27
|
-
"build": "tsc --project ./plugins
|
|
25
|
+
"build": "tsc --project ./plugins",
|
|
28
26
|
"prepublishOnly": "yarn run build",
|
|
29
27
|
"test": "lipemat-js-boilerplate test",
|
|
30
28
|
"validate-ts": "tsc --project ./plugins --noEmit",
|
|
31
|
-
"watch": "tsc --project ./plugins --watch
|
|
29
|
+
"watch": "tsc --project ./plugins --watch"
|
|
32
30
|
},
|
|
33
31
|
"dependencies": {
|
|
34
32
|
"@eslint/compat": "^1.2.4",
|
|
35
33
|
"@eslint/eslintrc": "^3.2.0",
|
|
36
|
-
"@lipemat/js-boilerplate": "^10.14.
|
|
34
|
+
"@lipemat/js-boilerplate": "^10.14.3",
|
|
37
35
|
"@stylistic/eslint-plugin-ts": "^2.12.1",
|
|
38
36
|
"@types/eslint": "^9",
|
|
39
37
|
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
|
@@ -52,18 +50,17 @@
|
|
|
52
50
|
"@types/jquery": "^3.5.16",
|
|
53
51
|
"@types/node": "^20",
|
|
54
52
|
"@typescript-eslint/rule-tester": "^8.40.0",
|
|
55
|
-
"@typescript-eslint/types": "^8.40.0",
|
|
56
53
|
"dompurify": "^3.2.6",
|
|
57
54
|
"execa": "^5.1.1",
|
|
58
55
|
"jest": "^29",
|
|
59
56
|
"jest-runner-eslint": "^2.2.1",
|
|
60
|
-
"typescript": "5.8.3"
|
|
57
|
+
"typescript": "~5.8.3"
|
|
61
58
|
},
|
|
62
59
|
"peerDependencies": {
|
|
63
|
-
"typescript": "
|
|
60
|
+
"typescript": "~5.8.3"
|
|
64
61
|
},
|
|
65
62
|
"resolutions": {
|
|
66
63
|
"@babel/runtime": "^7.27.0"
|
|
67
64
|
},
|
|
68
|
-
"packageManager": "yarn@4.9.
|
|
65
|
+
"packageManager": "yarn@4.9.4"
|
|
69
66
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dom-purify.js","sourceRoot":"","sources":["dom-purify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAgB,MAAM,0BAA0B,CAAC;AAGvE;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAE,IAAsF;IAClH,IAAK,cAAc,CAAC,cAAc,KAAK,IAAI,CAAC,IAAI,EAAG,CAAC;QACnD,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAK,cAAc,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,UAAU,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,EAAG,CAAC;QACzF,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAK,cAAc,CAAC,gBAAgB,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,cAAc,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAG,CAAC;QACrH,OAAO,WAAW,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE;YAC3D,cAAc,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,IAAI,UAAU,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtG,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {AST_NODE_TYPES, type TSESTree} from '@typescript-eslint/utils';
|
|
2
|
+
import type ESTree from 'estree';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Check if a node is a call to a known sanitization function.
|
|
6
|
+
* - Currently recognizes `sanitize(...)` and `DOMPurify.sanitize(...)`.
|
|
7
|
+
*/
|
|
8
|
+
export function isSanitized( node: ESTree.Expression | TSESTree.Property['value'] | TSESTree.CallExpressionArgument ): boolean {
|
|
9
|
+
if ( AST_NODE_TYPES.CallExpression !== node.type ) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if ( AST_NODE_TYPES.Identifier === node.callee.type && 'sanitize' === node.callee.name ) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if ( AST_NODE_TYPES.MemberExpression === node.callee.type && AST_NODE_TYPES.Identifier === node.callee.object.type ) {
|
|
17
|
+
return 'dompurify' === node.callee.object.name.toLowerCase() &&
|
|
18
|
+
AST_NODE_TYPES.Identifier === node.callee.property.type && 'sanitize' === node.callee.property.name;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element.js","sourceRoot":"","sources":["element.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAEtC;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAuE,UAA2C,EAAE,OAAgB;IACnK,MAAM,IAAI,GAAS,OAAO,CAAW,UAAU,EAAE,OAAO,CAAE,CAAC;IAE3D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,WAAW,IAAI,EAAE,CAAC;IACjD,4FAA4F;IAC5F,IAAK,SAAS,KAAK,IAAI,EAAG,CAAC;QAC1B,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,IAAI,CAAC,UAAU,CAAE,MAAM,CAAE,IAAI,IAAI,CAAC,QAAQ,CAAE,SAAS,CAAE,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {TSESLint, TSESTree} from '@typescript-eslint/utils';
|
|
2
|
+
import type {Type} from 'typescript';
|
|
3
|
+
import {getType} from './ts-types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Is the type of variable being passed a DOM element?
|
|
7
|
+
*
|
|
8
|
+
* - DOM elements are of the type `HTML{*}Element`.
|
|
9
|
+
* - DOM elements do not require sanitization.
|
|
10
|
+
*
|
|
11
|
+
* @link https://typescript-eslint.io/developers/custom-rules/#typed-rules
|
|
12
|
+
*/
|
|
13
|
+
export function isDomElementType<Context extends Readonly<TSESLint.RuleContext<string, readonly []>>>( expression: TSESTree.CallExpressionArgument, context: Context ): boolean {
|
|
14
|
+
const type: Type = getType<Context>( expression, context );
|
|
15
|
+
|
|
16
|
+
const name = type.getSymbol()?.escapedName ?? '';
|
|
17
|
+
// Match any type that ends with "Element", e.g., HTMLElement, HTMLDivElement, Element, etc.
|
|
18
|
+
if ( 'Element' === name ) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return name.startsWith( 'HTML' ) && name.endsWith( 'Element' );
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string.js","sourceRoot":"","sources":["string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAA+B,MAAM,0BAA0B,CAAC;AAEtF,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAE,IAAqC,EAAE,OAA4D;IAChI,MAAM,IAAI,GAAG,OAAO,CAAE,IAAI,EAAE,OAAO,CAAE,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;IACvC,MAAM,SAAS,GAAG,eAAe,IAAI,IAAI,IAAI,QAAQ,KAAK,IAAI,CAAC,aAAa,CAAC;IAC7E,OAAO,CAAE,cAAc,CAAC,OAAO,KAAK,IAAI,CAAC,IAAI,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,KAAK,CAAE,IAAI,CAAE,cAAc,CAAC,eAAe,KAAK,IAAI,CAAC,IAAI,CAAE,IAAI,OAAO,IAAI,SAAS,CAAC;AAC/J,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAE,IAAkE;IAClG,OAAO,cAAc,CAAC,OAAO,KAAK,IAAI,CAAC,IAAI,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC;AAC/E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAE,IAAkE;IACtG,IAAK,cAAc,CAAC,qBAAqB,KAAK,IAAI,CAAC,IAAI,EAAG,CAAC;QAC1D,OAAO,mBAAmB,CAAE,IAAI,CAAC,UAAU,CAAE,IAAI,mBAAmB,CAAE,IAAI,CAAC,SAAS,CAAE,CAAC;IACxF,CAAC;IAED,IAAK,CAAE,eAAe,CAAE,IAAI,CAAE,EAAG,CAAC;QACjC,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAE,SAAS,CAAE,EAAG,CAAC;QACxC,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,CAAE,wDAAwD,CAAC,IAAI,CAAE,kBAAkB,CAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAE,yBAAyB,EAAE,EAAE,CAAE,CAAE,CAAE,CAAC;AACrJ,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {AST_NODE_TYPES, type TSESLint, type TSESTree} from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
import {getType} from './ts-types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Is the node of type string.
|
|
7
|
+
* - String literals.
|
|
8
|
+
* - constants of type string.
|
|
9
|
+
* - template literals.
|
|
10
|
+
* - intrinsic type string.
|
|
11
|
+
*/
|
|
12
|
+
export function isStringLike( node: TSESTree.CallExpressionArgument, context: Readonly<TSESLint.RuleContext<string, readonly []>> ): boolean {
|
|
13
|
+
const type = getType( node, context );
|
|
14
|
+
const literal = type.isStringLiteral();
|
|
15
|
+
const intrinsic = 'intrinsicName' in type && 'string' === type.intrinsicName;
|
|
16
|
+
return ( AST_NODE_TYPES.Literal === node.type && 'string' === typeof node.value ) || ( AST_NODE_TYPES.TemplateLiteral === node.type ) || literal || intrinsic;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if a node is a literal string
|
|
21
|
+
*/
|
|
22
|
+
export function isLiteralString( node: TSESTree.Property['value'] | TSESTree.CallExpressionArgument ): node is TSESTree.StringLiteral {
|
|
23
|
+
return AST_NODE_TYPES.Literal === node.type && 'string' === typeof node.value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a node is a literal string that is safe to use in an HTML context.
|
|
28
|
+
* - Must be a literal string. Or a conditional expression where both branches are safe literal strings.
|
|
29
|
+
* - Must not contain `<script`.
|
|
30
|
+
* - Must not start with a dangerous protocol (javascript:, data:, vbscript:, about:, livescript:).
|
|
31
|
+
*/
|
|
32
|
+
export function isSafeLiteralString( node: TSESTree.Property['value'] | TSESTree.CallExpressionArgument ): boolean {
|
|
33
|
+
if ( AST_NODE_TYPES.ConditionalExpression === node.type ) {
|
|
34
|
+
return isSafeLiteralString( node.consequent ) && isSafeLiteralString( node.alternate );
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if ( ! isLiteralString( node ) ) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if ( node.value.includes( '<script' ) ) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return ! /^\s*(?:javascript|data|vbscript|about|livescript)\s*:/i.test( decodeURIComponent( node.value.replace( /[\u0000-\u001F\u007F]+/g, '' ) ) );
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts-types.js","sourceRoot":"","sources":["ts-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAA+B,MAAM,0BAA0B,CAAC;AAGnF;;GAEG;AACH,MAAM,UAAU,OAAO,CAAuE,UAA2C,EAAE,OAAgB;IAC1J,MAAM,EAAC,iBAAiB,EAAC,GAAG,WAAW,CAAC,iBAAiB,CAAE,OAAO,CAAE,CAAC;IACrE,MAAM,IAAI,GAAG,iBAAiB,CAAE,UAAU,CAAE,CAAC;IAC7C,OAAO,IAAI,CAAC,kBAAkB,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {ESLintUtils, type TSESLint, type TSESTree} from '@typescript-eslint/utils';
|
|
2
|
+
import type {Type} from 'typescript';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the TypeScript type of node.
|
|
6
|
+
*/
|
|
7
|
+
export function getType<Context extends Readonly<TSESLint.RuleContext<string, readonly []>>>( expression: TSESTree.CallExpressionArgument, context: Context ): Type {
|
|
8
|
+
const {getTypeAtLocation} = ESLintUtils.getParserServices( context );
|
|
9
|
+
const type = getTypeAtLocation( expression );
|
|
10
|
+
return type.getNonNullableType();
|
|
11
|
+
}
|
|
@@ -6,6 +6,7 @@ import htmlStringConcat from './rules/html-string-concat.js';
|
|
|
6
6
|
import jqueryExecuting from './rules/jquery-executing.js';
|
|
7
7
|
import vulnerableTagStripping from './rules/vulnerable-tag-stripping.js';
|
|
8
8
|
import windowEscaping from './rules/window-escaping.js';
|
|
9
|
+
import noAtHtmlTags from './rules/no-at-html-tags.js';
|
|
9
10
|
import { readFileSync } from 'fs';
|
|
10
11
|
import { resolve } from 'path';
|
|
11
12
|
const pkg = JSON.parse(readFileSync(resolve('./package.json'), 'utf8'));
|
|
@@ -21,11 +22,13 @@ const plugin = {
|
|
|
21
22
|
'html-sinks': htmlSinks,
|
|
22
23
|
'html-string-concat': htmlStringConcat,
|
|
23
24
|
'jquery-executing': jqueryExecuting,
|
|
25
|
+
'no-at-html-tags': noAtHtmlTags,
|
|
24
26
|
'vulnerable-tag-stripping': vulnerableTagStripping,
|
|
25
27
|
'window-escaping': windowEscaping,
|
|
26
28
|
},
|
|
27
29
|
configs: {
|
|
28
30
|
recommended: {},
|
|
31
|
+
svelte: {},
|
|
29
32
|
},
|
|
30
33
|
};
|
|
31
34
|
// Freeze the plugin to prevent modifications and use the plugin within.
|
|
@@ -45,5 +48,12 @@ plugin.configs = Object.freeze({
|
|
|
45
48
|
'@lipemat/security/window-escaping': 'error',
|
|
46
49
|
},
|
|
47
50
|
},
|
|
51
|
+
svelte: {
|
|
52
|
+
files: ['**/*.svelte', '*.svelte'],
|
|
53
|
+
rules: {
|
|
54
|
+
'@lipemat/security/no-at-html-tags': 'error',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
48
57
|
});
|
|
49
58
|
export default plugin;
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,uBAAuB,MAAM,uCAAuC,CAAC;AAC5E,OAAO,uBAAuB,MAAM,sCAAsC,CAAC;AAC3E,OAAO,qBAAqB,MAAM,oCAAoC,CAAC;AACvE,OAAO,SAAS,MAAM,uBAAuB,CAAC;AAC9C,OAAO,gBAAgB,MAAM,+BAA+B,CAAC;AAC7D,OAAO,eAAe,MAAM,6BAA6B,CAAC;AAC1D,OAAO,sBAAsB,MAAM,qCAAqC,CAAC;AACzE,OAAO,cAAc,MAAM,4BAA4B,CAAC;AACxD,OAAO,YAAY,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAC,YAAY,EAAC,MAAM,IAAI,CAAC;AAChC,OAAO,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAI7B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACrB,YAAY,CAAE,OAAO,CAAE,gBAAgB,CAAE,EAAE,MAAM,CAAE,CACnD,CAAC;AAUF,MAAM,MAAM,GAAW;IACtB,IAAI,EAAE;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,OAAO,EAAE,GAAG,CAAC,OAAO;KACpB;IACD,KAAK,EAAE;QACN,4BAA4B,EAAE,uBAAuB;QACrD,2BAA2B,EAAE,uBAAuB;QACpD,yBAAyB,EAAE,qBAAqB;QAChD,YAAY,EAAE,SAAS;QACvB,oBAAoB,EAAE,gBAAgB;QACtC,kBAAkB,EAAE,eAAe;QACnC,iBAAiB,EAAE,YAAY;QAC/B,0BAA0B,EAAE,sBAAsB;QAClD,iBAAiB,EAAE,cAAc;KACjC;IACD,OAAO,EAAE;QACR,WAAW,EAAE,EAAE;QACf,MAAM,EAAE,EAAE;KACV;CACD,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAE;IAC/B,WAAW,EAAE;QACZ,OAAO,EAAE;YACR,mBAAmB,EAAE,MAAM;SAC3B;QACD,KAAK,EAAE;YACN,8CAA8C,EAAE,OAAO;YACvD,6CAA6C,EAAE,OAAO;YACtD,2CAA2C,EAAE,OAAO;YACpD,8BAA8B,EAAE,OAAO;YACvC,sCAAsC,EAAE,OAAO;YAC/C,oCAAoC,EAAE,OAAO;YAC7C,4CAA4C,EAAE,OAAO;YACrD,mCAAmC,EAAE,OAAO;SAC5C;KACD;IACD,MAAM,EAAE;QACP,KAAK,EAAE,CAAE,aAAa,EAAE,UAAU,CAAE;QACpC,KAAK,EAAE;YACN,mCAAmC,EAAE,OAAO;SAC5C;KACD;CACD,CAAE,CAAC;AAEJ,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import dangerouslySetInnerHtml from './rules/dangerously-set-inner-html.js';
|
|
2
|
+
import htmlExecutingAssignment from './rules/html-executing-assignment.js';
|
|
3
|
+
import htmlExecutingFunction from './rules/html-executing-function.js';
|
|
4
|
+
import htmlSinks from './rules/html-sinks.js';
|
|
5
|
+
import htmlStringConcat from './rules/html-string-concat.js';
|
|
6
|
+
import jqueryExecuting from './rules/jquery-executing.js';
|
|
7
|
+
import vulnerableTagStripping from './rules/vulnerable-tag-stripping.js';
|
|
8
|
+
import windowEscaping from './rules/window-escaping.js';
|
|
9
|
+
import noAtHtmlTags from './rules/no-at-html-tags.js';
|
|
10
|
+
import {readFileSync} from 'fs';
|
|
11
|
+
import {resolve} from 'path';
|
|
12
|
+
import type {FlatConfig} from '@typescript-eslint/utils/ts-eslint';
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const pkg = JSON.parse(
|
|
16
|
+
readFileSync( resolve( './package.json' ), 'utf8' ),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
type Plugin = FlatConfig.Plugin & {
|
|
21
|
+
configs: {
|
|
22
|
+
recommended: FlatConfig.Config;
|
|
23
|
+
svelte: FlatConfig.Config;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const plugin: Plugin = {
|
|
28
|
+
meta: {
|
|
29
|
+
name: pkg.name,
|
|
30
|
+
version: pkg.version,
|
|
31
|
+
},
|
|
32
|
+
rules: {
|
|
33
|
+
'dangerously-set-inner-html': dangerouslySetInnerHtml,
|
|
34
|
+
'html-executing-assignment': htmlExecutingAssignment,
|
|
35
|
+
'html-executing-function': htmlExecutingFunction,
|
|
36
|
+
'html-sinks': htmlSinks,
|
|
37
|
+
'html-string-concat': htmlStringConcat,
|
|
38
|
+
'jquery-executing': jqueryExecuting,
|
|
39
|
+
'no-at-html-tags': noAtHtmlTags,
|
|
40
|
+
'vulnerable-tag-stripping': vulnerableTagStripping,
|
|
41
|
+
'window-escaping': windowEscaping,
|
|
42
|
+
},
|
|
43
|
+
configs: {
|
|
44
|
+
recommended: {},
|
|
45
|
+
svelte: {},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Freeze the plugin to prevent modifications and use the plugin within.
|
|
50
|
+
plugin.configs = Object.freeze( {
|
|
51
|
+
recommended: {
|
|
52
|
+
plugins: {
|
|
53
|
+
'@lipemat/security': plugin,
|
|
54
|
+
},
|
|
55
|
+
rules: {
|
|
56
|
+
'@lipemat/security/dangerously-set-inner-html': 'error',
|
|
57
|
+
'@lipemat/security/html-executing-assignment': 'error',
|
|
58
|
+
'@lipemat/security/html-executing-function': 'error',
|
|
59
|
+
'@lipemat/security/html-sinks': 'error',
|
|
60
|
+
'@lipemat/security/html-string-concat': 'error',
|
|
61
|
+
'@lipemat/security/jquery-executing': 'error',
|
|
62
|
+
'@lipemat/security/vulnerable-tag-stripping': 'error',
|
|
63
|
+
'@lipemat/security/window-escaping': 'error',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
svelte: {
|
|
67
|
+
files: [ '**/*.svelte', '*.svelte' ],
|
|
68
|
+
rules: {
|
|
69
|
+
'@lipemat/security/no-at-html-tags': 'error',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
export default plugin;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lipemat/eslint-plugin-security",
|
|
3
|
+
"description": "ESLint rules to replace PHPCS security scanning",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"source": "index.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"index.js",
|
|
13
|
+
"rules/",
|
|
14
|
+
"helpers/"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"eslint": "^9",
|
|
18
|
+
"@typescript-eslint/utils": "^8.40.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"eslint": "^9",
|
|
22
|
+
"@typescript-eslint/parser": "^8.40.0",
|
|
23
|
+
"typescript": "~5.8.3"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dangerously-set-inner-html.js","sourceRoot":"","sources":["dangerously-set-inner-html.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {AST_NODE_TYPES, type TSESLint, type TSESTree} from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
import {isSanitized} from '../helpers/dom-purify.js';
|
|
4
|
+
|
|
5
|
+
type Messages = 'dangerousInnerHtml' | 'sanitize' | 'domPurify';
|
|
6
|
+
type Context = TSESLint.RuleContext<Messages, []>;
|
|
7
|
+
|
|
8
|
+
function isDangerouslySetInnerHTML( node: TSESTree.JSXAttribute ): boolean {
|
|
9
|
+
return (
|
|
10
|
+
'JSXAttribute' === node.type &&
|
|
11
|
+
'dangerouslySetInnerHTML' === node.name.name
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getDangerouslySetInnerHTMLValue( node: TSESTree.JSXAttribute ): null | TSESTree.Property['value'] {
|
|
16
|
+
// Expecting value like: {{ __html: expr }}
|
|
17
|
+
const val = node.value;
|
|
18
|
+
if ( null === val ) {
|
|
19
|
+
return null; // No value provided
|
|
20
|
+
}
|
|
21
|
+
if ( AST_NODE_TYPES.JSXExpressionContainer !== val.type ) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const expr = val.expression;
|
|
25
|
+
if ( AST_NODE_TYPES.ObjectExpression !== expr.type ) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const htmlProp = expr.properties.find( p => (
|
|
29
|
+
AST_NODE_TYPES.Property === p.type &&
|
|
30
|
+
(
|
|
31
|
+
( AST_NODE_TYPES.Identifier === p.key.type && '__html' === p.key.name ) ||
|
|
32
|
+
( AST_NODE_TYPES.Literal === p.key.type && '__html' === p.key.value )
|
|
33
|
+
)
|
|
34
|
+
) );
|
|
35
|
+
if ( undefined !== htmlProp && 'value' in htmlProp ) {
|
|
36
|
+
return htmlProp.value;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
const plugin: TSESLint.RuleModule<Messages> = {
|
|
43
|
+
defaultOptions: [],
|
|
44
|
+
meta: {
|
|
45
|
+
type: 'problem',
|
|
46
|
+
hasSuggestions: true,
|
|
47
|
+
docs: {
|
|
48
|
+
description: 'Disallow using unsanitized values in dangerouslySetInnerHTML',
|
|
49
|
+
},
|
|
50
|
+
messages: {
|
|
51
|
+
dangerousInnerHtml: 'Any HTML passed to `dangerouslySetInnerHTML` gets executed. Please make sure it\'s properly escaped.',
|
|
52
|
+
|
|
53
|
+
// Suggestions
|
|
54
|
+
domPurify: 'Wrap the content with a `DOMPurify.sanitize()` call.',
|
|
55
|
+
sanitize: 'Wrap the content with a `sanitize()` call.',
|
|
56
|
+
},
|
|
57
|
+
schema: [],
|
|
58
|
+
},
|
|
59
|
+
create( context: Context ): TSESLint.RuleListener {
|
|
60
|
+
return {
|
|
61
|
+
JSXAttribute( node: TSESTree.JSXAttribute ) {
|
|
62
|
+
if ( ! isDangerouslySetInnerHTML( node ) ) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const htmlValue = getDangerouslySetInnerHTMLValue( node );
|
|
66
|
+
if ( null === htmlValue || isSanitized( htmlValue ) ) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
context.report( {
|
|
71
|
+
node,
|
|
72
|
+
messageId: 'dangerousInnerHtml',
|
|
73
|
+
suggest: [
|
|
74
|
+
{
|
|
75
|
+
messageId: 'domPurify',
|
|
76
|
+
fix: ( fixer: TSESLint.RuleFixer ) => {
|
|
77
|
+
return fixer.replaceText( node, `dangerouslySetInnerHTML={{__html: DOMPurify.sanitize( ${context.sourceCode.getText( htmlValue )} )}}` );
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
messageId: 'sanitize',
|
|
82
|
+
fix: ( fixer: TSESLint.RuleFixer ) => {
|
|
83
|
+
return fixer.replaceText( node, `dangerouslySetInnerHTML={{__html: sanitize( ${context.sourceCode.getText( htmlValue )} )}}` );
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
} );
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default plugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-executing-assignment.js","sourceRoot":"","sources":["html-executing-assignment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAA+B,MAAM,0BAA0B,CAAC;AACtF,OAAO,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAOvD,MAAM,iBAAiB,GAAuB;IAC7C,WAAW;IACX,WAAW;CACX,CAAC;AAEF,SAAS,gBAAgB,CAAE,YAAoB;IAC9C,OAAO,iBAAiB,CAAC,QAAQ,CAAE,YAAgC,CAAE,CAAC;AACvE,CAAC;AAED,MAAM,MAAM,GAAkC;IAC7C,cAAc,EAAE,EAAE;IAClB,IAAI,EAAE;QACL,IAAI,EAAE;YACL,WAAW,EAAE,0EAA0E;SACvF;QACD,cAAc,EAAE,IAAI;QACpB,QAAQ,EAAE;YACT,QAAQ,EAAE,wFAAwF;YAElG,cAAc;YACd,SAAS,EAAE,uDAAuD;YAClE,QAAQ,EAAE,6CAA6C;SACvD;QACD,MAAM,EAAE,EAAE;QACV,IAAI,EAAE,SAAS;KACf;IACD,MAAM,CAAE,OAAgB;QACvB,OAAO;YACN,oBAAoB,CAAE,IAAmC;gBACxD,IAAK,cAAc,CAAC,gBAAgB,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,cAAc,CAAC,UAAU,KAAK,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAG,CAAC;oBACnH,OAAO;gBACR,CAAC;gBACD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC7C,IAAK,CAAE,gBAAgB,CAAE,YAAY,CAAE,EAAG,CAAC;oBAC1C,OAAO;gBACR,CAAC;gBACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;gBACzB,IAAK,CAAE,mBAAmB,CAAE,KAAK,CAAE,IAAI,CAAE,WAAW,CAAE,KAAK,CAAE,IAAI,gBAAgB,CAAW,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAE,EAAG,CAAC;oBAC1H,OAAO,CAAC,MAAM,CAAE;wBACf,IAAI;wBACJ,SAAS,EAAE,UAAU;wBACrB,IAAI,EAAE;4BACL,YAAY;yBACZ;wBACD,OAAO,EAAE;4BACR;gCACC,SAAS,EAAE,WAAW;gCACtB,GAAG,EAAE,CAAE,KAAyB,EAAG,EAAE;oCACpC,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAE,KAAK,CAAE,CAAC;oCACtD,OAAO,KAAK,CAAC,WAAW,CAAE,KAAK,EAAE,uBAAuB,SAAS,IAAI,CAAE,CAAC;gCACzE,CAAC;6BACD;4BACD;gCACC,SAAS,EAAE,UAAU;gCACrB,GAAG,EAAE,CAAE,KAAyB,EAAG,EAAE;oCACpC,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAE,KAAK,CAAE,CAAC;oCACtD,OAAO,KAAK,CAAC,WAAW,CAAE,KAAK,EAAE,aAAa,SAAS,IAAI,CAAE,CAAC;gCAC/D,CAAC;6BACD;yBACD;qBACD,CAAE,CAAC;gBACL,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {AST_NODE_TYPES, type TSESLint, type TSESTree} from '@typescript-eslint/utils';
|
|
2
|
+
import {isSafeLiteralString} from '../helpers/string.js';
|
|
3
|
+
import {isSanitized} from '../helpers/dom-purify.js';
|
|
4
|
+
import {isDomElementType} from '../helpers/element.js';
|
|
5
|
+
|
|
6
|
+
type UnsafeProperties = 'innerHTML' | 'outerHTML';
|
|
7
|
+
|
|
8
|
+
type Messages = 'executed' | 'sanitize' | 'domPurify';
|
|
9
|
+
type Context = TSESLint.RuleContext<Messages, []>;
|
|
10
|
+
|
|
11
|
+
const UNSAFE_PROPERTIES: UnsafeProperties[] = [
|
|
12
|
+
'innerHTML',
|
|
13
|
+
'outerHTML',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function isUnsafeProperty( propertyName: string ): propertyName is UnsafeProperties {
|
|
17
|
+
return UNSAFE_PROPERTIES.includes( propertyName as UnsafeProperties );
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const plugin: TSESLint.RuleModule<Messages> = {
|
|
21
|
+
defaultOptions: [],
|
|
22
|
+
meta: {
|
|
23
|
+
docs: {
|
|
24
|
+
description: 'Disallow using unsanitized values in HTML executing property assignments',
|
|
25
|
+
},
|
|
26
|
+
hasSuggestions: true,
|
|
27
|
+
messages: {
|
|
28
|
+
executed: 'Any HTML used with `{{propertyName}}` gets executed. Make sure it\'s properly escaped.',
|
|
29
|
+
|
|
30
|
+
// Suggestions
|
|
31
|
+
domPurify: 'Wrap the argument with a `DOMPurify.sanitize()` call.',
|
|
32
|
+
sanitize: 'Wrap the argument with a `sanitize()` call.',
|
|
33
|
+
},
|
|
34
|
+
schema: [],
|
|
35
|
+
type: 'problem',
|
|
36
|
+
},
|
|
37
|
+
create( context: Context ): TSESLint.RuleListener {
|
|
38
|
+
return {
|
|
39
|
+
AssignmentExpression( node: TSESTree.AssignmentExpression ) {
|
|
40
|
+
if ( AST_NODE_TYPES.MemberExpression !== node.left.type || AST_NODE_TYPES.Identifier !== node.left.property.type ) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const propertyName = node.left.property.name;
|
|
44
|
+
if ( ! isUnsafeProperty( propertyName ) ) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const value = node.right;
|
|
48
|
+
if ( ! isSafeLiteralString( value ) && ! isSanitized( value ) && isDomElementType<Context>( node.left.object, context ) ) {
|
|
49
|
+
context.report( {
|
|
50
|
+
node,
|
|
51
|
+
messageId: 'executed',
|
|
52
|
+
data: {
|
|
53
|
+
propertyName,
|
|
54
|
+
},
|
|
55
|
+
suggest: [
|
|
56
|
+
{
|
|
57
|
+
messageId: 'domPurify',
|
|
58
|
+
fix: ( fixer: TSESLint.RuleFixer ) => {
|
|
59
|
+
const valueText = context.sourceCode.getText( value );
|
|
60
|
+
return fixer.replaceText( value, `DOMPurify.sanitize( ${valueText} )` );
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
messageId: 'sanitize',
|
|
65
|
+
fix: ( fixer: TSESLint.RuleFixer ) => {
|
|
66
|
+
const valueText = context.sourceCode.getText( value );
|
|
67
|
+
return fixer.replaceText( value, `sanitize( ${valueText} )` );
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
} );
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default plugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-executing-function.js","sourceRoot":"","sources":["html-executing-function.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAA+B,MAAM,0BAA0B,CAAC;AACtF,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAkBvD,MAAM,gBAAgB,GAA6B;IAClD,gBAAgB;IAChB,kBAAkB;CAClB,CAAC;AAEF,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAe;IAChD,oBAAoB;IACpB,cAAc;CACd,CAAE,CAAC;AAEJ,MAAM,cAAc,GAAG;IACtB,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,oBAAoB,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc;CACjD,CAAC;AAE5C,SAAS,gBAAgB,CAAE,UAAkB;IAC5C,OAAO,gBAAgB,CAAC,QAAQ,CAAE,UAAoC,CAAE,CAAC;AAC1E,CAAC;AAED,SAAS,cAAc,CAAE,UAAkB;IAC1C,OAAO,cAAc,CAAC,QAAQ,CAAE,UAAyB,CAAE,CAAC;AAC7D,CAAC;AAED,SAAS,iBAAiB,CAAE,UAAkB;IAC7C,OAAO,kBAAkB,CAAC,GAAG,CAAE,UAAyB,CAAE,CAAC;AAC5D,CAAC;AAGD,SAAS,eAAe,CAAE,IAA6B;IACtD,IAAI,UAAU,GAAG,EAAE,CAAC;IACpB,IAAK,cAAc,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,EAAG,CAAC;QACtD,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC/B,CAAC;SAAM,IAAK,cAAc,CAAC,gBAAgB,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,EAAG,CAAC;QACnE,IAAK,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAG,CAAC;YACpC,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;YACrC,IAAK,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAG,CAAC;gBACtC,UAAU,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC/C,CAAC;QACF,CAAC;aAAM,IAAK,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAG,CAAC;YAC7C,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QACxC,CAAC;IACF,CAAC;IACD,IAAK,gBAAgB,CAAE,UAAU,CAAE,EAAG,CAAC;QACtC,OAAO,UAAU,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,CAAC;AACb,CAAC;AAGD,SAAS,oBAAoB,CAAE,IAA6B,EAAE,OAAgB;IAC7E,IAAK,cAAc,CAAC,gBAAgB,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,cAAc,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAG,CAAC;QACvH,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC7C,IAAK,CAAE,gBAAgB,CAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAE,EAAG,CAAC;QACzD,OAAO,IAAI,CAAC,CAAC,+CAA+C;IAC7D,CAAC;IAED,IAAK,CAAE,cAAc,CAAE,UAAU,CAAE,EAAG,CAAC;QACtC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,IAAK,YAAY,CAAE,IAAI,CAAE,EAAG,CAAC;QAC5B,OAAO,IAAI,CAAC,CAAC,mCAAmC;IACjD,CAAC;IACD,OAAO,UAAU,CAAC;AACnB,CAAC;AAGD,MAAM,MAAM,GAAkC;IAC7C,cAAc,EAAE,EAAE;IAClB,IAAI,EAAE;QACL,IAAI,EAAE;YACL,WAAW,EAAE,kEAAkE;SAC/E;QACD,cAAc,EAAE,IAAI;QACpB,QAAQ,EAAE;YACT,gBAAgB,EAAE,sFAAsF;YACxG,kBAAkB,EAAE,wFAAwF;YAC5G,KAAK,EAAE,6EAA6E;YACpF,MAAM,EAAE,8EAA8E;YACtF,MAAM,EAAE,8EAA8E;YACtF,kBAAkB,EAAE,0FAA0F;YAC9G,OAAO,EAAE,+EAA+E;YACxF,WAAW,EAAE,mFAAmF;YAChG,YAAY,EAAE,sFAAsF;YAEpG,cAAc;YACd,SAAS,EAAE,uDAAuD;YAClE,QAAQ,EAAE,6CAA6C;SACvD;QACD,MAAM,EAAE,EAAE;QACV,IAAI,EAAE,SAAS;KAEf;IACD,MAAM,CAAE,OAAgB;QACvB,OAAO;YACN,cAAc,CAAE,IAA6B;gBAC5C,IAAI,MAAmD,CAAC;gBACxD,MAAM,cAAc,GAAG,eAAe,CAAE,IAAI,CAAE,CAAC;gBAE/C,IAAK,IAAI,KAAK,cAAc,EAAG,CAAC;oBAC/B,MAAM,GAAG,cAAc,CAAC;gBACzB,CAAC;qBAAM,CAAC;oBACP,MAAM,GAAG,oBAAoB,CAAE,IAAI,EAAE,OAAO,CAAE,CAAC;oBAC/C,IAAK,IAAI,KAAK,MAAM,EAAG,CAAC;wBACvB,OAAO;oBACR,CAAC;gBACF,CAAC;gBAED,IAAI,GAAG,GAAoC,IAAI,CAAC,SAAS,CAAE,CAAC,CAAE,CAAC;gBAC/D,IAAK,iBAAiB,CAAE,MAAM,CAAE,EAAG,CAAC;oBACnC,GAAG,GAAG,IAAI,CAAC,SAAS,CAAE,CAAC,CAAE,CAAC;gBAC3B,CAAC;gBAED,IAAK,CAAE,mBAAmB,CAAE,GAAG,CAAE,IAAI,CAAE,WAAW,CAAE,GAAG,CAAE,IAAI,CAAE,gBAAgB,CAAW,GAAG,EAAE,OAAO,CAAE,EAAG,CAAC;oBAC3G,OAAO,CAAC,MAAM,CAAE;wBACf,IAAI;wBACJ,SAAS,EAAE,MAAM;wBACjB,OAAO,EAAE;4BACR;gCACC,SAAS,EAAE,WAAW;gCACtB,GAAG,EAAE,CAAE,KAAyB,EAAG,EAAE;oCACpC,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAE,GAAG,CAAE,CAAC;oCAClD,OAAO,KAAK,CAAC,WAAW,CAAE,GAAG,EAAE,uBAAuB,OAAO,IAAI,CAAE,CAAC;gCACrE,CAAC;6BACD;4BACD;gCACC,SAAS,EAAE,UAAU;gCACrB,GAAG,EAAE,CAAE,KAAyB,EAAG,EAAE;oCACpC,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAE,GAAG,CAAE,CAAC;oCAClD,OAAO,KAAK,CAAC,WAAW,CAAE,GAAG,EAAE,aAAa,OAAO,IAAI,CAAE,CAAC;gCAC3D,CAAC;6BACD;yBACD;qBACD,CAAE,CAAC;gBACL,CAAC;YACF,CAAC;SACD,CAAC;IACH,CAAC;CACD,CAAC;AAEF,eAAe,MAAM,CAAC"}
|