eslint-plugin-secure-coding 2.3.2 → 2.3.3
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/README.md +1 -0
- package/package.json +3 -10
- package/src/index.ts +605 -0
- package/src/rules/__tests__/integration-demo.test.ts +290 -0
- package/src/rules/__tests__/integration-llm.test.ts +89 -0
- package/src/rules/database-injection/database-injection.test.ts +456 -0
- package/src/rules/database-injection/index.ts +488 -0
- package/src/rules/detect-child-process/detect-child-process.test.ts +207 -0
- package/src/rules/detect-child-process/index.ts +634 -0
- package/src/rules/detect-eval-with-expression/detect-eval-with-expression.test.ts +416 -0
- package/src/rules/detect-eval-with-expression/index.ts +463 -0
- package/src/rules/detect-mixed-content/detect-mixed-content.test.ts +28 -0
- package/src/rules/detect-mixed-content/index.ts +52 -0
- package/src/rules/detect-non-literal-fs-filename/detect-non-literal-fs-filename.test.ts +269 -0
- package/src/rules/detect-non-literal-fs-filename/index.ts +551 -0
- package/src/rules/detect-non-literal-regexp/detect-non-literal-regexp.test.ts +189 -0
- package/src/rules/detect-non-literal-regexp/index.ts +490 -0
- package/src/rules/detect-object-injection/detect-object-injection.test.ts +440 -0
- package/src/rules/detect-object-injection/index.ts +674 -0
- package/src/rules/detect-suspicious-dependencies/detect-suspicious-dependencies.test.ts +32 -0
- package/src/rules/detect-suspicious-dependencies/index.ts +84 -0
- package/src/rules/detect-weak-password-validation/detect-weak-password-validation.test.ts +31 -0
- package/src/rules/detect-weak-password-validation/index.ts +68 -0
- package/src/rules/no-allow-arbitrary-loads/index.ts +54 -0
- package/src/rules/no-allow-arbitrary-loads/no-allow-arbitrary-loads.test.ts +28 -0
- package/src/rules/no-arbitrary-file-access/index.ts +238 -0
- package/src/rules/no-arbitrary-file-access/no-arbitrary-file-access.test.ts +119 -0
- package/src/rules/no-buffer-overread/index.ts +724 -0
- package/src/rules/no-buffer-overread/no-buffer-overread.test.ts +313 -0
- package/src/rules/no-clickjacking/index.ts +481 -0
- package/src/rules/no-clickjacking/no-clickjacking.test.ts +253 -0
- package/src/rules/no-client-side-auth-logic/index.ts +81 -0
- package/src/rules/no-client-side-auth-logic/no-client-side-auth-logic.test.ts +33 -0
- package/src/rules/no-credentials-in-query-params/index.ts +69 -0
- package/src/rules/no-credentials-in-query-params/no-credentials-in-query-params.test.ts +33 -0
- package/src/rules/no-credentials-in-storage-api/index.ts +64 -0
- package/src/rules/no-credentials-in-storage-api/no-credentials-in-storage-api.test.ts +31 -0
- package/src/rules/no-data-in-temp-storage/index.ts +75 -0
- package/src/rules/no-data-in-temp-storage/no-data-in-temp-storage.test.ts +33 -0
- package/src/rules/no-debug-code-in-production/index.ts +59 -0
- package/src/rules/no-debug-code-in-production/no-debug-code-in-production.test.ts +26 -0
- package/src/rules/no-directive-injection/index.ts +551 -0
- package/src/rules/no-directive-injection/no-directive-injection.test.ts +305 -0
- package/src/rules/no-disabled-certificate-validation/index.ts +72 -0
- package/src/rules/no-disabled-certificate-validation/no-disabled-certificate-validation.test.ts +33 -0
- package/src/rules/no-document-cookie/index.ts +113 -0
- package/src/rules/no-document-cookie/no-document-cookie.test.ts +382 -0
- package/src/rules/no-dynamic-dependency-loading/index.ts +60 -0
- package/src/rules/no-dynamic-dependency-loading/no-dynamic-dependency-loading.test.ts +27 -0
- package/src/rules/no-electron-security-issues/index.ts +504 -0
- package/src/rules/no-electron-security-issues/no-electron-security-issues.test.ts +324 -0
- package/src/rules/no-exposed-debug-endpoints/index.ts +73 -0
- package/src/rules/no-exposed-debug-endpoints/no-exposed-debug-endpoints.test.ts +40 -0
- package/src/rules/no-exposed-sensitive-data/index.ts +428 -0
- package/src/rules/no-exposed-sensitive-data/no-exposed-sensitive-data.test.ts +75 -0
- package/src/rules/no-format-string-injection/index.ts +801 -0
- package/src/rules/no-format-string-injection/no-format-string-injection.test.ts +437 -0
- package/src/rules/no-graphql-injection/index.ts +508 -0
- package/src/rules/no-graphql-injection/no-graphql-injection.test.ts +371 -0
- package/src/rules/no-hardcoded-credentials/index.ts +478 -0
- package/src/rules/no-hardcoded-credentials/no-hardcoded-credentials.test.ts +639 -0
- package/src/rules/no-hardcoded-session-tokens/index.ts +69 -0
- package/src/rules/no-hardcoded-session-tokens/no-hardcoded-session-tokens.test.ts +42 -0
- package/src/rules/no-http-urls/index.ts +131 -0
- package/src/rules/no-http-urls/no-http-urls.test.ts +60 -0
- package/src/rules/no-improper-sanitization/index.ts +502 -0
- package/src/rules/no-improper-sanitization/no-improper-sanitization.test.ts +156 -0
- package/src/rules/no-improper-type-validation/index.ts +572 -0
- package/src/rules/no-improper-type-validation/no-improper-type-validation.test.ts +372 -0
- package/src/rules/no-insecure-comparison/index.ts +232 -0
- package/src/rules/no-insecure-comparison/no-insecure-comparison.test.ts +218 -0
- package/src/rules/no-insecure-cookie-settings/index.ts +391 -0
- package/src/rules/no-insecure-cookie-settings/no-insecure-cookie-settings.test.ts +409 -0
- package/src/rules/no-insecure-jwt/index.ts +467 -0
- package/src/rules/no-insecure-jwt/no-insecure-jwt.test.ts +259 -0
- package/src/rules/no-insecure-redirects/index.ts +267 -0
- package/src/rules/no-insecure-redirects/no-insecure-redirects.test.ts +108 -0
- package/src/rules/no-insecure-websocket/index.ts +72 -0
- package/src/rules/no-insecure-websocket/no-insecure-websocket.test.ts +42 -0
- package/src/rules/no-insufficient-postmessage-validation/index.ts +497 -0
- package/src/rules/no-insufficient-postmessage-validation/no-insufficient-postmessage-validation.test.ts +360 -0
- package/src/rules/no-insufficient-random/index.ts +288 -0
- package/src/rules/no-insufficient-random/no-insufficient-random.test.ts +246 -0
- package/src/rules/no-ldap-injection/index.ts +547 -0
- package/src/rules/no-ldap-injection/no-ldap-injection.test.ts +317 -0
- package/src/rules/no-missing-authentication/index.ts +408 -0
- package/src/rules/no-missing-authentication/no-missing-authentication.test.ts +350 -0
- package/src/rules/no-missing-cors-check/index.ts +453 -0
- package/src/rules/no-missing-cors-check/no-missing-cors-check.test.ts +392 -0
- package/src/rules/no-missing-csrf-protection/index.ts +229 -0
- package/src/rules/no-missing-csrf-protection/no-missing-csrf-protection.test.ts +222 -0
- package/src/rules/no-missing-security-headers/index.ts +266 -0
- package/src/rules/no-missing-security-headers/no-missing-security-headers.test.ts +98 -0
- package/src/rules/no-password-in-url/index.ts +64 -0
- package/src/rules/no-password-in-url/no-password-in-url.test.ts +27 -0
- package/src/rules/no-permissive-cors/index.ts +78 -0
- package/src/rules/no-permissive-cors/no-permissive-cors.test.ts +28 -0
- package/src/rules/no-pii-in-logs/index.ts +83 -0
- package/src/rules/no-pii-in-logs/no-pii-in-logs.test.ts +26 -0
- package/src/rules/no-postmessage-origin-wildcard/index.ts +67 -0
- package/src/rules/no-postmessage-origin-wildcard/no-postmessage-origin-wildcard.test.ts +27 -0
- package/src/rules/no-privilege-escalation/index.ts +403 -0
- package/src/rules/no-privilege-escalation/no-privilege-escalation.test.ts +306 -0
- package/src/rules/no-redos-vulnerable-regex/index.ts +379 -0
- package/src/rules/no-redos-vulnerable-regex/no-redos-vulnerable-regex.test.ts +83 -0
- package/src/rules/no-sensitive-data-exposure/index.ts +294 -0
- package/src/rules/no-sensitive-data-exposure/no-sensitive-data-exposure.test.ts +262 -0
- package/src/rules/no-sensitive-data-in-analytics/index.ts +73 -0
- package/src/rules/no-sensitive-data-in-analytics/no-sensitive-data-in-analytics.test.ts +42 -0
- package/src/rules/no-sensitive-data-in-cache/index.ts +59 -0
- package/src/rules/no-sensitive-data-in-cache/no-sensitive-data-in-cache.test.ts +32 -0
- package/src/rules/no-sql-injection/index.ts +424 -0
- package/src/rules/no-sql-injection/no-sql-injection.test.ts +303 -0
- package/src/rules/no-timing-attack/index.ts +552 -0
- package/src/rules/no-timing-attack/no-timing-attack.test.ts +348 -0
- package/src/rules/no-toctou-vulnerability/index.ts +250 -0
- package/src/rules/no-toctou-vulnerability/no-toctou-vulnerability.test.ts +60 -0
- package/src/rules/no-tracking-without-consent/index.ts +78 -0
- package/src/rules/no-tracking-without-consent/no-tracking-without-consent.test.ts +34 -0
- package/src/rules/no-unchecked-loop-condition/index.ts +781 -0
- package/src/rules/no-unchecked-loop-condition/no-unchecked-loop-condition.test.ts +459 -0
- package/src/rules/no-unencrypted-local-storage/index.ts +73 -0
- package/src/rules/no-unencrypted-local-storage/no-unencrypted-local-storage.test.ts +41 -0
- package/src/rules/no-unencrypted-transmission/index.ts +296 -0
- package/src/rules/no-unencrypted-transmission/no-unencrypted-transmission.test.ts +287 -0
- package/src/rules/no-unescaped-url-parameter/index.ts +424 -0
- package/src/rules/no-unescaped-url-parameter/no-unescaped-url-parameter.test.ts +263 -0
- package/src/rules/no-unlimited-resource-allocation/index.ts +767 -0
- package/src/rules/no-unlimited-resource-allocation/no-unlimited-resource-allocation.test.ts +544 -0
- package/src/rules/no-unsafe-deserialization/index.ts +593 -0
- package/src/rules/no-unsafe-deserialization/no-unsafe-deserialization.test.ts +310 -0
- package/src/rules/no-unsafe-dynamic-require/index.ts +125 -0
- package/src/rules/no-unsafe-dynamic-require/no-unsafe-dynamic-require.test.ts +151 -0
- package/src/rules/no-unsafe-regex-construction/index.ts +370 -0
- package/src/rules/no-unsafe-regex-construction/no-unsafe-regex-construction.test.ts +181 -0
- package/src/rules/no-unsanitized-html/index.ts +400 -0
- package/src/rules/no-unsanitized-html/no-unsanitized-html.test.ts +488 -0
- package/src/rules/no-unvalidated-deeplinks/index.ts +73 -0
- package/src/rules/no-unvalidated-deeplinks/no-unvalidated-deeplinks.test.ts +29 -0
- package/src/rules/no-unvalidated-user-input/index.ts +498 -0
- package/src/rules/no-unvalidated-user-input/no-unvalidated-user-input.test.ts +463 -0
- package/src/rules/no-verbose-error-messages/index.ts +83 -0
- package/src/rules/no-verbose-error-messages/no-verbose-error-messages.test.ts +34 -0
- package/src/rules/no-weak-crypto/index.ts +447 -0
- package/src/rules/no-weak-crypto/no-weak-crypto.test.ts +297 -0
- package/src/rules/no-weak-password-recovery/index.ts +509 -0
- package/src/rules/no-weak-password-recovery/no-weak-password-recovery.test.ts +184 -0
- package/src/rules/no-xpath-injection/index.ts +596 -0
- package/src/rules/no-xpath-injection/no-xpath-injection.test.ts +405 -0
- package/src/rules/no-xxe-injection/index.ts +342 -0
- package/src/rules/no-xxe-injection/no-xxe-injection.test.ts +122 -0
- package/src/rules/no-zip-slip/index.ts +526 -0
- package/src/rules/no-zip-slip/no-zip-slip.test.ts +305 -0
- package/src/rules/require-backend-authorization/index.ts +71 -0
- package/src/rules/require-backend-authorization/require-backend-authorization.test.ts +31 -0
- package/src/rules/require-code-minification/index.ts +54 -0
- package/src/rules/require-code-minification/require-code-minification.test.ts +30 -0
- package/src/rules/require-csp-headers/index.ts +74 -0
- package/src/rules/require-csp-headers/require-csp-headers.test.ts +34 -0
- package/src/rules/require-data-minimization/index.ts +65 -0
- package/src/rules/require-data-minimization/require-data-minimization.test.ts +31 -0
- package/src/rules/require-dependency-integrity/index.ts +78 -0
- package/src/rules/require-dependency-integrity/require-dependency-integrity.test.ts +44 -0
- package/src/rules/require-https-only/index.ts +75 -0
- package/src/rules/require-https-only/require-https-only.test.ts +26 -0
- package/src/rules/require-mime-type-validation/index.ts +77 -0
- package/src/rules/require-mime-type-validation/require-mime-type-validation.test.ts +32 -0
- package/src/rules/require-network-timeout/index.ts +58 -0
- package/src/rules/require-network-timeout/require-network-timeout.test.ts +26 -0
- package/src/rules/require-package-lock/index.ts +75 -0
- package/src/rules/require-package-lock/require-package-lock.test.ts +27 -0
- package/src/rules/require-secure-credential-storage/index.ts +60 -0
- package/src/rules/require-secure-credential-storage/require-secure-credential-storage.test.ts +26 -0
- package/src/rules/require-secure-defaults/index.ts +54 -0
- package/src/rules/require-secure-defaults/require-secure-defaults.test.ts +26 -0
- package/src/rules/require-secure-deletion/index.ts +52 -0
- package/src/rules/require-secure-deletion/require-secure-deletion.test.ts +29 -0
- package/src/rules/require-storage-encryption/index.ts +60 -0
- package/src/rules/require-storage-encryption/require-storage-encryption.test.ts +26 -0
- package/src/rules/require-url-validation/index.ts +85 -0
- package/src/rules/require-url-validation/require-url-validation.test.ts +32 -0
- package/src/types/{index.d.ts → index.ts} +157 -53
- package/src/index.d.ts +0 -32
- package/src/index.js +0 -465
- package/src/rules/database-injection/index.d.ts +0 -13
- package/src/rules/database-injection/index.js +0 -406
- package/src/rules/detect-child-process/index.d.ts +0 -11
- package/src/rules/detect-child-process/index.js +0 -529
- package/src/rules/detect-eval-with-expression/index.d.ts +0 -9
- package/src/rules/detect-eval-with-expression/index.js +0 -392
- package/src/rules/detect-mixed-content/index.d.ts +0 -8
- package/src/rules/detect-mixed-content/index.js +0 -44
- package/src/rules/detect-non-literal-fs-filename/index.d.ts +0 -7
- package/src/rules/detect-non-literal-fs-filename/index.js +0 -454
- package/src/rules/detect-non-literal-regexp/index.d.ts +0 -9
- package/src/rules/detect-non-literal-regexp/index.js +0 -403
- package/src/rules/detect-object-injection/index.d.ts +0 -11
- package/src/rules/detect-object-injection/index.js +0 -560
- package/src/rules/detect-suspicious-dependencies/index.d.ts +0 -8
- package/src/rules/detect-suspicious-dependencies/index.js +0 -71
- package/src/rules/detect-weak-password-validation/index.d.ts +0 -6
- package/src/rules/detect-weak-password-validation/index.js +0 -58
- package/src/rules/no-allow-arbitrary-loads/index.d.ts +0 -8
- package/src/rules/no-allow-arbitrary-loads/index.js +0 -47
- package/src/rules/no-arbitrary-file-access/index.d.ts +0 -13
- package/src/rules/no-arbitrary-file-access/index.js +0 -195
- package/src/rules/no-buffer-overread/index.d.ts +0 -29
- package/src/rules/no-buffer-overread/index.js +0 -606
- package/src/rules/no-clickjacking/index.d.ts +0 -10
- package/src/rules/no-clickjacking/index.js +0 -396
- package/src/rules/no-client-side-auth-logic/index.d.ts +0 -6
- package/src/rules/no-client-side-auth-logic/index.js +0 -69
- package/src/rules/no-credentials-in-query-params/index.d.ts +0 -8
- package/src/rules/no-credentials-in-query-params/index.js +0 -57
- package/src/rules/no-credentials-in-storage-api/index.d.ts +0 -6
- package/src/rules/no-credentials-in-storage-api/index.js +0 -54
- package/src/rules/no-data-in-temp-storage/index.d.ts +0 -6
- package/src/rules/no-data-in-temp-storage/index.js +0 -64
- package/src/rules/no-debug-code-in-production/index.d.ts +0 -8
- package/src/rules/no-debug-code-in-production/index.js +0 -51
- package/src/rules/no-directive-injection/index.d.ts +0 -12
- package/src/rules/no-directive-injection/index.js +0 -457
- package/src/rules/no-disabled-certificate-validation/index.d.ts +0 -6
- package/src/rules/no-disabled-certificate-validation/index.js +0 -61
- package/src/rules/no-document-cookie/index.d.ts +0 -5
- package/src/rules/no-document-cookie/index.js +0 -89
- package/src/rules/no-dynamic-dependency-loading/index.d.ts +0 -8
- package/src/rules/no-dynamic-dependency-loading/index.js +0 -51
- package/src/rules/no-electron-security-issues/index.d.ts +0 -10
- package/src/rules/no-electron-security-issues/index.js +0 -423
- package/src/rules/no-exposed-debug-endpoints/index.d.ts +0 -6
- package/src/rules/no-exposed-debug-endpoints/index.js +0 -62
- package/src/rules/no-exposed-sensitive-data/index.d.ts +0 -11
- package/src/rules/no-exposed-sensitive-data/index.js +0 -340
- package/src/rules/no-format-string-injection/index.d.ts +0 -17
- package/src/rules/no-format-string-injection/index.js +0 -660
- package/src/rules/no-graphql-injection/index.d.ts +0 -12
- package/src/rules/no-graphql-injection/index.js +0 -411
- package/src/rules/no-hardcoded-credentials/index.d.ts +0 -26
- package/src/rules/no-hardcoded-credentials/index.js +0 -376
- package/src/rules/no-hardcoded-session-tokens/index.d.ts +0 -6
- package/src/rules/no-hardcoded-session-tokens/index.js +0 -59
- package/src/rules/no-http-urls/index.d.ts +0 -12
- package/src/rules/no-http-urls/index.js +0 -114
- package/src/rules/no-improper-sanitization/index.d.ts +0 -12
- package/src/rules/no-improper-sanitization/index.js +0 -411
- package/src/rules/no-improper-type-validation/index.d.ts +0 -10
- package/src/rules/no-improper-type-validation/index.js +0 -475
- package/src/rules/no-insecure-comparison/index.d.ts +0 -7
- package/src/rules/no-insecure-comparison/index.js +0 -193
- package/src/rules/no-insecure-cookie-settings/index.d.ts +0 -9
- package/src/rules/no-insecure-cookie-settings/index.js +0 -306
- package/src/rules/no-insecure-jwt/index.d.ts +0 -10
- package/src/rules/no-insecure-jwt/index.js +0 -380
- package/src/rules/no-insecure-redirects/index.d.ts +0 -7
- package/src/rules/no-insecure-redirects/index.js +0 -216
- package/src/rules/no-insecure-websocket/index.d.ts +0 -6
- package/src/rules/no-insecure-websocket/index.js +0 -61
- package/src/rules/no-insufficient-postmessage-validation/index.d.ts +0 -14
- package/src/rules/no-insufficient-postmessage-validation/index.js +0 -392
- package/src/rules/no-insufficient-random/index.d.ts +0 -9
- package/src/rules/no-insufficient-random/index.js +0 -208
- package/src/rules/no-ldap-injection/index.d.ts +0 -10
- package/src/rules/no-ldap-injection/index.js +0 -455
- package/src/rules/no-missing-authentication/index.d.ts +0 -13
- package/src/rules/no-missing-authentication/index.js +0 -333
- package/src/rules/no-missing-cors-check/index.d.ts +0 -9
- package/src/rules/no-missing-cors-check/index.js +0 -399
- package/src/rules/no-missing-csrf-protection/index.d.ts +0 -11
- package/src/rules/no-missing-csrf-protection/index.js +0 -180
- package/src/rules/no-missing-security-headers/index.d.ts +0 -7
- package/src/rules/no-missing-security-headers/index.js +0 -218
- package/src/rules/no-password-in-url/index.d.ts +0 -8
- package/src/rules/no-password-in-url/index.js +0 -54
- package/src/rules/no-permissive-cors/index.d.ts +0 -8
- package/src/rules/no-permissive-cors/index.js +0 -65
- package/src/rules/no-pii-in-logs/index.d.ts +0 -8
- package/src/rules/no-pii-in-logs/index.js +0 -70
- package/src/rules/no-postmessage-origin-wildcard/index.d.ts +0 -8
- package/src/rules/no-postmessage-origin-wildcard/index.js +0 -56
- package/src/rules/no-privilege-escalation/index.d.ts +0 -13
- package/src/rules/no-privilege-escalation/index.js +0 -321
- package/src/rules/no-redos-vulnerable-regex/index.d.ts +0 -7
- package/src/rules/no-redos-vulnerable-regex/index.js +0 -306
- package/src/rules/no-sensitive-data-exposure/index.d.ts +0 -11
- package/src/rules/no-sensitive-data-exposure/index.js +0 -250
- package/src/rules/no-sensitive-data-in-analytics/index.d.ts +0 -8
- package/src/rules/no-sensitive-data-in-analytics/index.js +0 -62
- package/src/rules/no-sensitive-data-in-cache/index.d.ts +0 -8
- package/src/rules/no-sensitive-data-in-cache/index.js +0 -52
- package/src/rules/no-sql-injection/index.d.ts +0 -10
- package/src/rules/no-sql-injection/index.js +0 -335
- package/src/rules/no-timing-attack/index.d.ts +0 -10
- package/src/rules/no-timing-attack/index.js +0 -447
- package/src/rules/no-toctou-vulnerability/index.d.ts +0 -7
- package/src/rules/no-toctou-vulnerability/index.js +0 -208
- package/src/rules/no-tracking-without-consent/index.d.ts +0 -6
- package/src/rules/no-tracking-without-consent/index.js +0 -67
- package/src/rules/no-unchecked-loop-condition/index.d.ts +0 -12
- package/src/rules/no-unchecked-loop-condition/index.js +0 -646
- package/src/rules/no-unencrypted-local-storage/index.d.ts +0 -8
- package/src/rules/no-unencrypted-local-storage/index.js +0 -61
- package/src/rules/no-unencrypted-transmission/index.d.ts +0 -11
- package/src/rules/no-unencrypted-transmission/index.js +0 -236
- package/src/rules/no-unescaped-url-parameter/index.d.ts +0 -9
- package/src/rules/no-unescaped-url-parameter/index.js +0 -355
- package/src/rules/no-unlimited-resource-allocation/index.d.ts +0 -12
- package/src/rules/no-unlimited-resource-allocation/index.js +0 -643
- package/src/rules/no-unsafe-deserialization/index.d.ts +0 -10
- package/src/rules/no-unsafe-deserialization/index.js +0 -491
- package/src/rules/no-unsafe-dynamic-require/index.d.ts +0 -5
- package/src/rules/no-unsafe-dynamic-require/index.js +0 -106
- package/src/rules/no-unsafe-regex-construction/index.d.ts +0 -9
- package/src/rules/no-unsafe-regex-construction/index.js +0 -291
- package/src/rules/no-unsanitized-html/index.d.ts +0 -9
- package/src/rules/no-unsanitized-html/index.js +0 -335
- package/src/rules/no-unvalidated-deeplinks/index.d.ts +0 -6
- package/src/rules/no-unvalidated-deeplinks/index.js +0 -62
- package/src/rules/no-unvalidated-user-input/index.d.ts +0 -9
- package/src/rules/no-unvalidated-user-input/index.js +0 -420
- package/src/rules/no-verbose-error-messages/index.d.ts +0 -8
- package/src/rules/no-verbose-error-messages/index.js +0 -68
- package/src/rules/no-weak-crypto/index.d.ts +0 -11
- package/src/rules/no-weak-crypto/index.js +0 -351
- package/src/rules/no-weak-password-recovery/index.d.ts +0 -12
- package/src/rules/no-weak-password-recovery/index.js +0 -424
- package/src/rules/no-xpath-injection/index.d.ts +0 -10
- package/src/rules/no-xpath-injection/index.js +0 -487
- package/src/rules/no-xxe-injection/index.d.ts +0 -7
- package/src/rules/no-xxe-injection/index.js +0 -266
- package/src/rules/no-zip-slip/index.d.ts +0 -9
- package/src/rules/no-zip-slip/index.js +0 -445
- package/src/rules/require-backend-authorization/index.d.ts +0 -6
- package/src/rules/require-backend-authorization/index.js +0 -60
- package/src/rules/require-code-minification/index.d.ts +0 -8
- package/src/rules/require-code-minification/index.js +0 -47
- package/src/rules/require-csp-headers/index.d.ts +0 -6
- package/src/rules/require-csp-headers/index.js +0 -64
- package/src/rules/require-data-minimization/index.d.ts +0 -8
- package/src/rules/require-data-minimization/index.js +0 -53
- package/src/rules/require-dependency-integrity/index.d.ts +0 -6
- package/src/rules/require-dependency-integrity/index.js +0 -64
- package/src/rules/require-https-only/index.d.ts +0 -8
- package/src/rules/require-https-only/index.js +0 -62
- package/src/rules/require-mime-type-validation/index.d.ts +0 -6
- package/src/rules/require-mime-type-validation/index.js +0 -66
- package/src/rules/require-network-timeout/index.d.ts +0 -8
- package/src/rules/require-network-timeout/index.js +0 -50
- package/src/rules/require-package-lock/index.d.ts +0 -8
- package/src/rules/require-package-lock/index.js +0 -63
- package/src/rules/require-secure-credential-storage/index.d.ts +0 -8
- package/src/rules/require-secure-credential-storage/index.js +0 -50
- package/src/rules/require-secure-defaults/index.d.ts +0 -8
- package/src/rules/require-secure-defaults/index.js +0 -47
- package/src/rules/require-secure-deletion/index.d.ts +0 -8
- package/src/rules/require-secure-deletion/index.js +0 -44
- package/src/rules/require-storage-encryption/index.d.ts +0 -8
- package/src/rules/require-storage-encryption/index.js +0 -50
- package/src/rules/require-url-validation/index.d.ts +0 -6
- package/src/rules/require-url-validation/index.js +0 -72
- package/src/types/index.js +0 -17
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint Rule: no-improper-sanitization
|
|
3
|
+
* Detects improper sanitization of user input (CWE-94, CWE-79, CWE-116)
|
|
4
|
+
*
|
|
5
|
+
* Improper sanitization occurs when user input is not properly cleaned
|
|
6
|
+
* before use in sensitive contexts. This can lead to injection attacks,
|
|
7
|
+
* XSS, or other security vulnerabilities.
|
|
8
|
+
*
|
|
9
|
+
* False Positive Reduction:
|
|
10
|
+
* This rule uses security utilities to reduce false positives by detecting:
|
|
11
|
+
* - Known safe sanitization patterns
|
|
12
|
+
* - Trusted sanitization libraries
|
|
13
|
+
* - JSDoc annotations (@sanitized, @safe)
|
|
14
|
+
* - Context-aware validation
|
|
15
|
+
*/
|
|
16
|
+
import type { TSESLint, TSESTree } from '@interlace/eslint-devkit';
|
|
17
|
+
import { createRule } from '@interlace/eslint-devkit';
|
|
18
|
+
import { formatLLMMessage, MessageIcons } from '@interlace/eslint-devkit';
|
|
19
|
+
import {
|
|
20
|
+
createSafetyChecker,
|
|
21
|
+
type SecurityRuleOptions,
|
|
22
|
+
} from '@interlace/eslint-devkit';
|
|
23
|
+
|
|
24
|
+
type MessageIds =
|
|
25
|
+
| 'improperSanitization'
|
|
26
|
+
| 'insufficientXssProtection'
|
|
27
|
+
| 'incompleteHtmlEscaping'
|
|
28
|
+
| 'unsafeReplaceSanitization'
|
|
29
|
+
| 'missingContextEncoding'
|
|
30
|
+
| 'dangerousSanitizerUsage'
|
|
31
|
+
| 'sqlInjectionSanitization'
|
|
32
|
+
| 'commandInjectionSanitization'
|
|
33
|
+
| 'useProperSanitization'
|
|
34
|
+
| 'validateSanitization'
|
|
35
|
+
| 'implementContextAware'
|
|
36
|
+
| 'strategyDefenseInDepth'
|
|
37
|
+
| 'strategyInputValidation'
|
|
38
|
+
| 'strategyOutputEncoding';
|
|
39
|
+
|
|
40
|
+
export interface Options extends SecurityRuleOptions {
|
|
41
|
+
/** Safe sanitization functions */
|
|
42
|
+
safeSanitizers?: string[];
|
|
43
|
+
|
|
44
|
+
/** Characters that should be escaped */
|
|
45
|
+
dangerousChars?: string[];
|
|
46
|
+
|
|
47
|
+
/** Contexts that require different encoding */
|
|
48
|
+
contexts?: string[];
|
|
49
|
+
|
|
50
|
+
/** Trusted sanitization libraries */
|
|
51
|
+
trustedLibraries?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type RuleOptions = [Options?];
|
|
55
|
+
|
|
56
|
+
export const noImproperSanitization = createRule<RuleOptions, MessageIds>({
|
|
57
|
+
name: 'no-improper-sanitization',
|
|
58
|
+
meta: {
|
|
59
|
+
type: 'problem',
|
|
60
|
+
docs: {
|
|
61
|
+
description: 'Detects improper sanitization of user input',
|
|
62
|
+
},
|
|
63
|
+
fixable: 'code',
|
|
64
|
+
hasSuggestions: true,
|
|
65
|
+
messages: {
|
|
66
|
+
improperSanitization: formatLLMMessage({
|
|
67
|
+
icon: MessageIcons.SECURITY,
|
|
68
|
+
issueName: 'Improper Sanitization',
|
|
69
|
+
cwe: 'CWE-116',
|
|
70
|
+
description: 'User input sanitization is insufficient or incorrect',
|
|
71
|
+
severity: '{{severity}}',
|
|
72
|
+
fix: '{{safeAlternative}}',
|
|
73
|
+
documentationLink: 'https://cwe.mitre.org/data/definitions/116.html',
|
|
74
|
+
}),
|
|
75
|
+
insufficientXssProtection: formatLLMMessage({
|
|
76
|
+
icon: MessageIcons.SECURITY,
|
|
77
|
+
issueName: 'Insufficient XSS Protection',
|
|
78
|
+
cwe: 'CWE-79',
|
|
79
|
+
description: 'XSS protection is incomplete or missing',
|
|
80
|
+
severity: 'HIGH',
|
|
81
|
+
fix: 'Use comprehensive XSS prevention or trusted sanitization library',
|
|
82
|
+
documentationLink: 'https://owasp.org/www-community/attacks/xss/',
|
|
83
|
+
}),
|
|
84
|
+
incompleteHtmlEscaping: formatLLMMessage({
|
|
85
|
+
icon: MessageIcons.SECURITY,
|
|
86
|
+
issueName: 'Incomplete HTML Escaping',
|
|
87
|
+
cwe: 'CWE-116',
|
|
88
|
+
description: 'HTML escaping misses dangerous characters',
|
|
89
|
+
severity: 'MEDIUM',
|
|
90
|
+
fix: 'Escape all HTML special characters: & < > " \'',
|
|
91
|
+
documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html',
|
|
92
|
+
}),
|
|
93
|
+
unsafeReplaceSanitization: formatLLMMessage({
|
|
94
|
+
icon: MessageIcons.SECURITY,
|
|
95
|
+
issueName: 'Unsafe Replace Sanitization',
|
|
96
|
+
cwe: 'CWE-116',
|
|
97
|
+
description: 'Simple replace() calls are insufficient for sanitization',
|
|
98
|
+
severity: 'MEDIUM',
|
|
99
|
+
fix: 'Use comprehensive sanitization libraries',
|
|
100
|
+
documentationLink: 'https://cwe.mitre.org/data/definitions/116.html',
|
|
101
|
+
}),
|
|
102
|
+
missingContextEncoding: formatLLMMessage({
|
|
103
|
+
icon: MessageIcons.SECURITY,
|
|
104
|
+
issueName: 'Missing Context Encoding',
|
|
105
|
+
cwe: 'CWE-116',
|
|
106
|
+
description: 'Output encoding missing for specific context',
|
|
107
|
+
severity: 'MEDIUM',
|
|
108
|
+
fix: 'Encode output according to usage context (HTML, URL, SQL, etc.)',
|
|
109
|
+
documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html',
|
|
110
|
+
}),
|
|
111
|
+
dangerousSanitizerUsage: formatLLMMessage({
|
|
112
|
+
icon: MessageIcons.SECURITY,
|
|
113
|
+
issueName: 'Dangerous Sanitizer Usage',
|
|
114
|
+
cwe: 'CWE-94',
|
|
115
|
+
description: 'Custom sanitizer may be incomplete or bypassable',
|
|
116
|
+
severity: 'LOW',
|
|
117
|
+
fix: 'Use well-tested sanitization libraries',
|
|
118
|
+
documentationLink: 'https://cwe.mitre.org/data/definitions/94.html',
|
|
119
|
+
}),
|
|
120
|
+
sqlInjectionSanitization: formatLLMMessage({
|
|
121
|
+
icon: MessageIcons.SECURITY,
|
|
122
|
+
issueName: 'SQL Injection Sanitization',
|
|
123
|
+
cwe: 'CWE-89',
|
|
124
|
+
description: 'SQL input sanitization is insufficient',
|
|
125
|
+
severity: 'HIGH',
|
|
126
|
+
fix: 'Use parameterized queries instead of sanitization',
|
|
127
|
+
documentationLink: 'https://owasp.org/www-community/attacks/SQL_Injection',
|
|
128
|
+
}),
|
|
129
|
+
commandInjectionSanitization: formatLLMMessage({
|
|
130
|
+
icon: MessageIcons.SECURITY,
|
|
131
|
+
issueName: 'Command Injection Sanitization',
|
|
132
|
+
cwe: 'CWE-78',
|
|
133
|
+
description: 'Command input sanitization is insufficient',
|
|
134
|
+
severity: 'CRITICAL',
|
|
135
|
+
fix: 'Avoid shell commands with user input, use safe APIs',
|
|
136
|
+
documentationLink: 'https://owasp.org/www-community/attacks/Command_Injection',
|
|
137
|
+
}),
|
|
138
|
+
useProperSanitization: formatLLMMessage({
|
|
139
|
+
icon: MessageIcons.INFO,
|
|
140
|
+
issueName: 'Use Proper Sanitization',
|
|
141
|
+
description: 'Use proper sanitization methods for each context',
|
|
142
|
+
severity: 'LOW',
|
|
143
|
+
fix: 'HTML: DOMPurify, URL: encodeURIComponent, SQL: parameterized queries',
|
|
144
|
+
documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html',
|
|
145
|
+
}),
|
|
146
|
+
validateSanitization: formatLLMMessage({
|
|
147
|
+
icon: MessageIcons.INFO,
|
|
148
|
+
issueName: 'Validate Sanitization',
|
|
149
|
+
description: 'Validate that sanitization is effective',
|
|
150
|
+
severity: 'LOW',
|
|
151
|
+
fix: 'Test sanitization with malicious inputs',
|
|
152
|
+
documentationLink: 'https://cwe.mitre.org/data/definitions/116.html',
|
|
153
|
+
}),
|
|
154
|
+
implementContextAware: formatLLMMessage({
|
|
155
|
+
icon: MessageIcons.INFO,
|
|
156
|
+
issueName: 'Implement Context Aware Sanitization',
|
|
157
|
+
description: 'Use different sanitization for different contexts',
|
|
158
|
+
severity: 'LOW',
|
|
159
|
+
fix: 'HTML context: escape <>&"\' , URL context: encodeURIComponent',
|
|
160
|
+
documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html',
|
|
161
|
+
}),
|
|
162
|
+
strategyDefenseInDepth: formatLLMMessage({
|
|
163
|
+
icon: MessageIcons.STRATEGY,
|
|
164
|
+
issueName: 'Defense in Depth Strategy',
|
|
165
|
+
description: 'Implement multiple layers of input validation',
|
|
166
|
+
severity: 'LOW',
|
|
167
|
+
fix: 'Validate input, sanitize output, use CSP, implement rate limiting',
|
|
168
|
+
documentationLink: 'https://owasp.org/www-community/controls/Defense_in_depth',
|
|
169
|
+
}),
|
|
170
|
+
strategyInputValidation: formatLLMMessage({
|
|
171
|
+
icon: MessageIcons.STRATEGY,
|
|
172
|
+
issueName: 'Input Validation Strategy',
|
|
173
|
+
description: 'Validate input at multiple layers',
|
|
174
|
+
severity: 'LOW',
|
|
175
|
+
fix: 'Client-side, server-side, and database-level validation',
|
|
176
|
+
documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html',
|
|
177
|
+
}),
|
|
178
|
+
strategyOutputEncoding: formatLLMMessage({
|
|
179
|
+
icon: MessageIcons.STRATEGY,
|
|
180
|
+
issueName: 'Output Encoding Strategy',
|
|
181
|
+
description: 'Encode output according to context',
|
|
182
|
+
severity: 'LOW',
|
|
183
|
+
fix: 'Use appropriate encoding for HTML, JavaScript, CSS, URL contexts',
|
|
184
|
+
documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html',
|
|
185
|
+
})
|
|
186
|
+
},
|
|
187
|
+
schema: [
|
|
188
|
+
{
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {
|
|
191
|
+
safeSanitizers: {
|
|
192
|
+
type: 'array',
|
|
193
|
+
items: { type: 'string' },
|
|
194
|
+
default: ['DOMPurify.sanitize', 'he.encode', 'encodeURIComponent', 'encodeURI', 'escape'],
|
|
195
|
+
},
|
|
196
|
+
dangerousChars: {
|
|
197
|
+
type: 'array',
|
|
198
|
+
items: { type: 'string' },
|
|
199
|
+
default: ['<', '>', '"', "'", '&', '`', '$', '{', '}', '|', ';', '(', ')'],
|
|
200
|
+
},
|
|
201
|
+
contexts: {
|
|
202
|
+
type: 'array',
|
|
203
|
+
items: { type: 'string' },
|
|
204
|
+
default: ['html', 'url', 'sql', 'command', 'javascript', 'css'],
|
|
205
|
+
},
|
|
206
|
+
trustedLibraries: {
|
|
207
|
+
type: 'array',
|
|
208
|
+
items: { type: 'string' },
|
|
209
|
+
default: ['DOMPurify', 'he', 'validator', 'express-validator'],
|
|
210
|
+
},
|
|
211
|
+
trustedSanitizers: {
|
|
212
|
+
type: 'array',
|
|
213
|
+
items: { type: 'string' },
|
|
214
|
+
default: [],
|
|
215
|
+
description: 'Additional function names to consider as sanitizers',
|
|
216
|
+
},
|
|
217
|
+
trustedAnnotations: {
|
|
218
|
+
type: 'array',
|
|
219
|
+
items: { type: 'string' },
|
|
220
|
+
default: [],
|
|
221
|
+
description: 'Additional JSDoc annotations to consider as safe markers',
|
|
222
|
+
},
|
|
223
|
+
strictMode: {
|
|
224
|
+
type: 'boolean',
|
|
225
|
+
default: false,
|
|
226
|
+
description: 'Disable all false positive detection (strict mode)',
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
additionalProperties: false,
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
},
|
|
233
|
+
defaultOptions: [
|
|
234
|
+
{
|
|
235
|
+
safeSanitizers: ['DOMPurify.sanitize', 'he.encode', 'encodeURIComponent', 'encodeURI', 'escape'],
|
|
236
|
+
dangerousChars: ['<', '>', '"', "'", '&', '`', '$', '{', '}', '|', ';', '(', ')'],
|
|
237
|
+
contexts: ['html', 'url', 'sql', 'command', 'javascript', 'css'],
|
|
238
|
+
trustedLibraries: ['DOMPurify', 'he', 'validator', 'express-validator'],
|
|
239
|
+
trustedSanitizers: [],
|
|
240
|
+
trustedAnnotations: [],
|
|
241
|
+
strictMode: false,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
create(context: TSESLint.RuleContext<MessageIds, RuleOptions>) {
|
|
245
|
+
const options = context.options[0] || {};
|
|
246
|
+
const {
|
|
247
|
+
safeSanitizers = ['DOMPurify.sanitize', 'he.encode', 'encodeURIComponent', 'encodeURI', 'escape'],
|
|
248
|
+
dangerousChars = ['<', '>', '"', "'", '&', '`', '$', '{', '}', '|', ';', '(', ')'],
|
|
249
|
+
trustedSanitizers = [],
|
|
250
|
+
trustedAnnotations = [],
|
|
251
|
+
strictMode = false,
|
|
252
|
+
}: Options = options;
|
|
253
|
+
|
|
254
|
+
const sourceCode = context.sourceCode || context.sourceCode;
|
|
255
|
+
const filename = context.filename || context.getFilename();
|
|
256
|
+
|
|
257
|
+
// Create safety checker for false positive detection
|
|
258
|
+
const safetyChecker = createSafetyChecker({
|
|
259
|
+
trustedSanitizers,
|
|
260
|
+
trustedAnnotations,
|
|
261
|
+
trustedOrmPatterns: [],
|
|
262
|
+
strictMode,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Check if sanitization is safe
|
|
267
|
+
*/
|
|
268
|
+
const isSafeSanitizer = (callText: string): boolean => {
|
|
269
|
+
return safeSanitizers.some(sanitizer => callText.includes(sanitizer));
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Check if replace sanitization is incomplete
|
|
274
|
+
*/
|
|
275
|
+
const isIncompleteReplaceSanitization = (callExpression: TSESTree.CallExpression): boolean => {
|
|
276
|
+
const callText = sourceCode.getText(callExpression);
|
|
277
|
+
|
|
278
|
+
// Check for incomplete HTML escaping (any quote style)
|
|
279
|
+
const escapesOnlyTags = /replace\(\s*\/[<>]/.test(callText);
|
|
280
|
+
if (escapesOnlyTags) {
|
|
281
|
+
// If only escaping < or > but not other dangerous chars, it's incomplete
|
|
282
|
+
const hasQuoteEscaping = /"|'|'/.test(callText);
|
|
283
|
+
const hasAmpersandEscaping = /&/.test(callText);
|
|
284
|
+
|
|
285
|
+
return !(hasQuoteEscaping && hasAmpersandEscaping);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return false;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Check if output context suggests needed encoding
|
|
293
|
+
*/
|
|
294
|
+
const needsContextEncoding = (outputNode: TSESTree.Node): string | null => {
|
|
295
|
+
let current: TSESTree.Node | undefined = outputNode;
|
|
296
|
+
|
|
297
|
+
// Look for context clues in surrounding code
|
|
298
|
+
while (current) {
|
|
299
|
+
const text = sourceCode.getText(current).toLowerCase();
|
|
300
|
+
|
|
301
|
+
if (text.includes('innerhtml') || text.includes('outerhtml')) {
|
|
302
|
+
return 'html';
|
|
303
|
+
}
|
|
304
|
+
if (text.includes('href') || text.includes('src') || text.includes('url')) {
|
|
305
|
+
return 'url';
|
|
306
|
+
}
|
|
307
|
+
if (text.includes('sql') || text.includes('query') || text.includes('execute')) {
|
|
308
|
+
return 'sql';
|
|
309
|
+
}
|
|
310
|
+
if (text.includes('exec') || text.includes('spawn') || text.includes('command')) {
|
|
311
|
+
return 'command';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
current = current.parent as TSESTree.Node;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return null;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
// Check call expressions for sanitization issues
|
|
322
|
+
CallExpression(node: TSESTree.CallExpression) {
|
|
323
|
+
const callee = node.callee;
|
|
324
|
+
|
|
325
|
+
// Check for replace() sanitization
|
|
326
|
+
if (callee.type === 'MemberExpression' &&
|
|
327
|
+
callee.property.type === 'Identifier' &&
|
|
328
|
+
callee.property.name === 'replace') {
|
|
329
|
+
|
|
330
|
+
if (isIncompleteReplaceSanitization(node)) {
|
|
331
|
+
/* c8 ignore start -- safetyChecker requires JSDoc annotations not testable via RuleTester */
|
|
332
|
+
if (safetyChecker.isSafe(node, context)) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
/* c8 ignore stop */
|
|
336
|
+
|
|
337
|
+
context.report({
|
|
338
|
+
node,
|
|
339
|
+
messageId: 'incompleteHtmlEscaping',
|
|
340
|
+
data: {
|
|
341
|
+
filePath: filename,
|
|
342
|
+
line: String(node.loc?.start.line ?? 0),
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Check for custom sanitizer functions
|
|
349
|
+
if (callee.type === 'Identifier') {
|
|
350
|
+
const functionName = callee.name;
|
|
351
|
+
|
|
352
|
+
// Check if it's a known dangerous sanitizer pattern
|
|
353
|
+
if (functionName.toLowerCase().includes('sanitize') ||
|
|
354
|
+
functionName.toLowerCase().includes('escape') ||
|
|
355
|
+
functionName.toLowerCase().includes('clean')) {
|
|
356
|
+
|
|
357
|
+
// If it's not in our safe list, flag it
|
|
358
|
+
if (!safeSanitizers.some(safe => functionName.includes(safe))) {
|
|
359
|
+
// Check if arguments contain user input
|
|
360
|
+
const args = node.arguments;
|
|
361
|
+
let hasUserInput = false;
|
|
362
|
+
|
|
363
|
+
for (const arg of args) {
|
|
364
|
+
const argText = sourceCode.getText(arg).toLowerCase();
|
|
365
|
+
if (argText.includes('req.') || argText.includes('body') ||
|
|
366
|
+
argText.includes('query') || argText.includes('params') ||
|
|
367
|
+
argText.includes('input') || argText.includes('data')) {
|
|
368
|
+
hasUserInput = true;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (hasUserInput) {
|
|
374
|
+
/* c8 ignore start -- safetyChecker requires JSDoc annotations not testable via RuleTester */
|
|
375
|
+
if (safetyChecker.isSafe(node, context)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
/* c8 ignore stop */
|
|
379
|
+
|
|
380
|
+
context.report({
|
|
381
|
+
node,
|
|
382
|
+
messageId: 'dangerousSanitizerUsage',
|
|
383
|
+
data: {
|
|
384
|
+
filePath: filename,
|
|
385
|
+
line: String(node.loc?.start.line ?? 0),
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
// Check assignments that might need sanitization
|
|
395
|
+
AssignmentExpression(node: TSESTree.AssignmentExpression) {
|
|
396
|
+
const left = node.left;
|
|
397
|
+
const right = node.right;
|
|
398
|
+
|
|
399
|
+
// Check for assignments to potentially dangerous properties
|
|
400
|
+
if (left.type === 'MemberExpression' &&
|
|
401
|
+
left.property.type === 'Identifier') {
|
|
402
|
+
|
|
403
|
+
const propertyName = left.property.name.toLowerCase();
|
|
404
|
+
|
|
405
|
+
if (['innerhtml', 'outerhtml', 'innertext', 'textcontent'].includes(propertyName)) {
|
|
406
|
+
const encodingContext = needsContextEncoding(node);
|
|
407
|
+
|
|
408
|
+
if (encodingContext === 'html' && propertyName === 'innerhtml') {
|
|
409
|
+
// Check if right side is properly sanitized
|
|
410
|
+
const rightText = sourceCode.getText(right);
|
|
411
|
+
|
|
412
|
+
if (!isSafeSanitizer(rightText)) {
|
|
413
|
+
// Check if right side contains user input
|
|
414
|
+
const hasUserInput = rightText.includes('req.') ||
|
|
415
|
+
rightText.includes('body') ||
|
|
416
|
+
rightText.includes('query') ||
|
|
417
|
+
rightText.includes('input');
|
|
418
|
+
|
|
419
|
+
if (hasUserInput) {
|
|
420
|
+
/* c8 ignore start -- safetyChecker requires JSDoc annotations not testable via RuleTester */
|
|
421
|
+
if (safetyChecker.isSafe(node, context)) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
/* c8 ignore stop */
|
|
425
|
+
|
|
426
|
+
context.report({
|
|
427
|
+
node: right,
|
|
428
|
+
messageId: 'insufficientXssProtection',
|
|
429
|
+
data: {
|
|
430
|
+
filePath: filename,
|
|
431
|
+
line: String(node.loc?.start.line ?? 0),
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
// Check string literals that might contain dangerous characters
|
|
442
|
+
Literal(node: TSESTree.Literal) {
|
|
443
|
+
if (typeof node.value !== 'string') {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const text = node.value;
|
|
448
|
+
|
|
449
|
+
// Check if this string is used in a dangerous context
|
|
450
|
+
let current: TSESTree.Node | undefined = node;
|
|
451
|
+
let isInDangerousContext = false;
|
|
452
|
+
|
|
453
|
+
while (current && !isInDangerousContext) {
|
|
454
|
+
if (current.type === 'AssignmentExpression') {
|
|
455
|
+
const left = current.left;
|
|
456
|
+
if (left.type === 'MemberExpression' &&
|
|
457
|
+
left.property.type === 'Identifier' &&
|
|
458
|
+
['innerHTML', 'outerHTML'].includes(left.property.name)) {
|
|
459
|
+
isInDangerousContext = true;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
} else if (current.type === 'CallExpression') {
|
|
463
|
+
const callee = current.callee;
|
|
464
|
+
if (callee.type === 'MemberExpression' &&
|
|
465
|
+
callee.property.type === 'Identifier' &&
|
|
466
|
+
['write', 'send', 'json'].includes(callee.property.name)) {
|
|
467
|
+
// Could be response output
|
|
468
|
+
isInDangerousContext = true;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
current = current.parent as TSESTree.Node;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (isInDangerousContext) {
|
|
476
|
+
// Check if string contains dangerous characters without proper escaping
|
|
477
|
+
const hasDangerousChars = dangerousChars.some(char => text.includes(char));
|
|
478
|
+
const hasEscaping = text.includes('<') || text.includes('>') ||
|
|
479
|
+
text.includes('"') || text.includes(''') ||
|
|
480
|
+
text.includes('&');
|
|
481
|
+
|
|
482
|
+
if (hasDangerousChars && !hasEscaping) {
|
|
483
|
+
/* c8 ignore start -- safetyChecker requires JSDoc annotations not testable via RuleTester */
|
|
484
|
+
if (safetyChecker.isSafe(node, context)) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
/* c8 ignore stop */
|
|
488
|
+
|
|
489
|
+
context.report({
|
|
490
|
+
node,
|
|
491
|
+
messageId: 'unsafeReplaceSanitization',
|
|
492
|
+
data: {
|
|
493
|
+
filePath: filename,
|
|
494
|
+
line: String(node.loc?.start.line ?? 0),
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
},
|
|
502
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for no-improper-sanitization rule
|
|
3
|
+
* Security: CWE-116 (Improper Encoding or Escaping of Output)
|
|
4
|
+
*/
|
|
5
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
6
|
+
import { describe, it, afterAll } from 'vitest';
|
|
7
|
+
import parser from '@typescript-eslint/parser';
|
|
8
|
+
import { noImproperSanitization } from './index';
|
|
9
|
+
|
|
10
|
+
// Configure RuleTester for Vitest
|
|
11
|
+
RuleTester.afterAll = afterAll;
|
|
12
|
+
RuleTester.it = it;
|
|
13
|
+
RuleTester.itOnly = it.only;
|
|
14
|
+
RuleTester.describe = describe;
|
|
15
|
+
|
|
16
|
+
const ruleTester = new RuleTester({
|
|
17
|
+
languageOptions: {
|
|
18
|
+
parser,
|
|
19
|
+
ecmaVersion: 2022,
|
|
20
|
+
sourceType: 'module',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('no-improper-sanitization', () => {
|
|
25
|
+
describe('Valid Code', () => {
|
|
26
|
+
ruleTester.run('valid - proper sanitization', noImproperSanitization, {
|
|
27
|
+
valid: [
|
|
28
|
+
// Safe sanitization with trusted libraries
|
|
29
|
+
'element.innerHTML = DOMPurify.sanitize(userInput);',
|
|
30
|
+
'const safe = he.encode(userInput);',
|
|
31
|
+
'const encoded = encodeURIComponent(userInput);',
|
|
32
|
+
// textContent is safe (doesn't interpret HTML)
|
|
33
|
+
'element.textContent = userInput;',
|
|
34
|
+
// Direct assignment without user input indicators isn't flagged
|
|
35
|
+
'element.innerHTML = userInput;',
|
|
36
|
+
// String concatenation without dangerous context
|
|
37
|
+
'const html = "<div>" + req.body.content + "</div>";',
|
|
38
|
+
// Safe annotation
|
|
39
|
+
`
|
|
40
|
+
/** @safe */
|
|
41
|
+
myCustomSanitize(req.body.data);
|
|
42
|
+
`,
|
|
43
|
+
// Proper HTML entity escaping
|
|
44
|
+
|
|
45
|
+
],
|
|
46
|
+
invalid: [],
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('Invalid Code - Incomplete HTML Escaping', () => {
|
|
51
|
+
ruleTester.run('invalid - incomplete HTML escaping', noImproperSanitization, {
|
|
52
|
+
valid: [],
|
|
53
|
+
invalid: [
|
|
54
|
+
// Incomplete escaping - only escapes < but not other dangerous chars
|
|
55
|
+
{
|
|
56
|
+
code: 'element.innerHTML = userInput.replace(/</g, "<");',
|
|
57
|
+
errors: [
|
|
58
|
+
{
|
|
59
|
+
messageId: 'incompleteHtmlEscaping',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
// Incomplete escaping - only escapes > but not other dangerous chars
|
|
64
|
+
{
|
|
65
|
+
code: 'const safe = data.replace(/>/g, ">");',
|
|
66
|
+
errors: [
|
|
67
|
+
{
|
|
68
|
+
messageId: 'incompleteHtmlEscaping',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Chained replacement flagged individually (Known Limitation)
|
|
74
|
+
{
|
|
75
|
+
code: 'element.innerHTML = userInput.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/&/g, "&");',
|
|
76
|
+
errors: [
|
|
77
|
+
{ messageId: 'incompleteHtmlEscaping' },
|
|
78
|
+
{ messageId: 'incompleteHtmlEscaping' },
|
|
79
|
+
{ messageId: 'incompleteHtmlEscaping' },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('Invalid Code - Custom Sanitizer Functions', () => {
|
|
87
|
+
ruleTester.run('invalid - custom sanitizer with user input', noImproperSanitization, {
|
|
88
|
+
valid: [],
|
|
89
|
+
invalid: [
|
|
90
|
+
// Custom sanitize function with user input
|
|
91
|
+
{
|
|
92
|
+
code: 'const clean = mySanitize(req.body.content);',
|
|
93
|
+
errors: [{ messageId: 'dangerousSanitizerUsage' }],
|
|
94
|
+
},
|
|
95
|
+
// Custom escape function with user data
|
|
96
|
+
{
|
|
97
|
+
code: 'const safe = myEscape(req.query.data);',
|
|
98
|
+
errors: [{ messageId: 'dangerousSanitizerUsage' }],
|
|
99
|
+
},
|
|
100
|
+
// Custom clean function with user input
|
|
101
|
+
{
|
|
102
|
+
code: 'const result = myClean(req.params.id);',
|
|
103
|
+
errors: [{ messageId: 'dangerousSanitizerUsage' }],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('Invalid Code - innerHTML Without Sanitization', () => {
|
|
110
|
+
ruleTester.run('invalid - innerHTML with user input', noImproperSanitization, {
|
|
111
|
+
valid: [],
|
|
112
|
+
invalid: [
|
|
113
|
+
// innerHTML with unsanitized user input
|
|
114
|
+
{
|
|
115
|
+
code: 'element.innerHTML = req.body.content;',
|
|
116
|
+
errors: [{ messageId: 'insufficientXssProtection' }],
|
|
117
|
+
},
|
|
118
|
+
// innerHTML with query parameter
|
|
119
|
+
{
|
|
120
|
+
code: 'div.innerHTML = req.query.html;',
|
|
121
|
+
errors: [{ messageId: 'insufficientXssProtection' }],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('Invalid Code - String Literals in Dangerous Contexts', () => {
|
|
128
|
+
ruleTester.run('invalid - unescaped strings in dangerous contexts', noImproperSanitization, {
|
|
129
|
+
valid: [],
|
|
130
|
+
invalid: [
|
|
131
|
+
// String with dangerous chars in innerHTML context
|
|
132
|
+
{
|
|
133
|
+
code: 'element.innerHTML = "<script>alert(1)</script>";',
|
|
134
|
+
errors: [{ messageId: 'unsafeReplaceSanitization' }],
|
|
135
|
+
},
|
|
136
|
+
// String with dangerous chars in response send
|
|
137
|
+
{
|
|
138
|
+
code: 'res.send("<img src=x onerror=alert(1)>");',
|
|
139
|
+
errors: [{ messageId: 'unsafeReplaceSanitization' }],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('Context Encoding Detection', () => {
|
|
146
|
+
ruleTester.run('context - URL and SQL context detection', noImproperSanitization, {
|
|
147
|
+
valid: [
|
|
148
|
+
// Proper URL encoding
|
|
149
|
+
'const url = "https://example.com?q=" + encodeURIComponent(userInput);',
|
|
150
|
+
// Parameterized query (not direct)
|
|
151
|
+
'db.query("SELECT * FROM users WHERE id = ?", [userId]);',
|
|
152
|
+
],
|
|
153
|
+
invalid: [],
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|