@sun-asterisk/sunlint 1.0.6 → 1.0.7

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.
Files changed (165) hide show
  1. package/CHANGELOG.md +108 -169
  2. package/README.md +7 -1
  3. package/config/presets/beginner.json +1 -1
  4. package/config/presets/ci.json +3 -2
  5. package/config/presets/recommended.json +1 -1
  6. package/config/presets/strict.json +3 -2
  7. package/config/rules-registry.json +60 -0
  8. package/config/sunlint-schema.json +0 -7
  9. package/config/typescript/eslint.config.js +4 -0
  10. package/core/config-manager.js +9 -8
  11. package/core/config-merger.js +12 -0
  12. package/core/file-targeting-service.js +1 -6
  13. package/core/rule-mapping-service.js +8 -0
  14. package/package.json +2 -5
  15. package/cli-legacy.js +0 -355
  16. package/docs/AI.md +0 -163
  17. package/docs/ARCHITECTURE.md +0 -78
  18. package/docs/CI-CD-GUIDE.md +0 -315
  19. package/docs/COMMAND-EXAMPLES.md +0 -256
  20. package/docs/DEBUG.md +0 -86
  21. package/docs/DISTRIBUTION.md +0 -153
  22. package/docs/ENHANCED_FILE_TARGETING.md +0 -0
  23. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  24. package/docs/ESLINT_INTEGRATION.md +0 -238
  25. package/docs/FILE_TARGETING_COMPARISON.md +0 -0
  26. package/docs/FOLDER_STRUCTURE.md +0 -59
  27. package/docs/HEURISTIC_VS_AI.md +0 -113
  28. package/docs/README.md +0 -32
  29. package/docs/RELEASE_GUIDE.md +0 -230
  30. package/docs/RULE-RESPONSIBILITY-MATRIX.md +0 -204
  31. package/eslint-integration/.eslintrc.js +0 -98
  32. package/eslint-integration/cli.js +0 -35
  33. package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +0 -204
  34. package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +0 -246
  35. package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +0 -207
  36. package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +0 -90
  37. package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +0 -43
  38. package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +0 -38
  39. package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +0 -39
  40. package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +0 -335
  41. package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +0 -142
  42. package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +0 -50
  43. package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +0 -80
  44. package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +0 -294
  45. package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +0 -34
  46. package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +0 -32
  47. package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +0 -64
  48. package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +0 -406
  49. package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +0 -300
  50. package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +0 -239
  51. package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +0 -31
  52. package/eslint-integration/eslint-plugin-custom/index.js +0 -155
  53. package/eslint-integration/eslint-plugin-custom/package.json +0 -13
  54. package/eslint-integration/eslint-plugin-custom/package.json.bak +0 -9
  55. package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +0 -86
  56. package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +0 -95
  57. package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +0 -69
  58. package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +0 -62
  59. package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +0 -103
  60. package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +0 -123
  61. package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +0 -66
  62. package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +0 -71
  63. package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +0 -50
  64. package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +0 -43
  65. package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +0 -59
  66. package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +0 -193
  67. package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +0 -56
  68. package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +0 -113
  69. package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +0 -89
  70. package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +0 -78
  71. package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +0 -300
  72. package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +0 -217
  73. package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +0 -68
  74. package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +0 -80
  75. package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +0 -79
  76. package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +0 -78
  77. package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +0 -80
  78. package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +0 -77
  79. package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +0 -74
  80. package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +0 -68
  81. package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +0 -70
  82. package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +0 -74
  83. package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +0 -63
  84. package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +0 -211
  85. package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +0 -294
  86. package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +0 -254
  87. package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +0 -292
  88. package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +0 -46
  89. package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +0 -44
  90. package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +0 -108
  91. package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +0 -54
  92. package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +0 -94
  93. package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +0 -66
  94. package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +0 -109
  95. package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +0 -143
  96. package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +0 -54
  97. package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +0 -73
  98. package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +0 -42
  99. package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +0 -48
  100. package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +0 -160
  101. package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +0 -52
  102. package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +0 -175
  103. package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +0 -95
  104. package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +0 -48
  105. package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +0 -377
  106. package/eslint-integration/eslint.config.js +0 -125
  107. package/eslint-integration/eslint.config.simple.js +0 -24
  108. package/eslint-integration/package.json +0 -23
  109. package/eslint-integration/sample.ts +0 -53
  110. package/eslint-integration/test-s003.js +0 -5
  111. package/eslint-integration/tsconfig.json +0 -27
  112. package/examples/.github/workflows/code-quality.yml +0 -111
  113. package/examples/README.md +0 -69
  114. package/examples/basic-typescript-demo/.eslintrc.json +0 -18
  115. package/examples/basic-typescript-demo/.next/cache/eslint/.cache_1othrmo +0 -1
  116. package/examples/basic-typescript-demo/.sunlint.json +0 -29
  117. package/examples/basic-typescript-demo/eslint.config.mjs +0 -37
  118. package/examples/basic-typescript-demo/next-env.d.ts +0 -5
  119. package/examples/basic-typescript-demo/next.config.mjs +0 -4
  120. package/examples/basic-typescript-demo/package-lock.json +0 -5656
  121. package/examples/basic-typescript-demo/package.json +0 -34
  122. package/examples/basic-typescript-demo/src/app/layout.tsx +0 -18
  123. package/examples/basic-typescript-demo/src/app/page.tsx +0 -48
  124. package/examples/basic-typescript-demo/src/config.ts +0 -14
  125. package/examples/basic-typescript-demo/src/good-practices.ts +0 -58
  126. package/examples/basic-typescript-demo/src/types.generated.ts +0 -13
  127. package/examples/basic-typescript-demo/src/user.test.ts +0 -19
  128. package/examples/basic-typescript-demo/src/violations.ts +0 -61
  129. package/examples/basic-typescript-demo/test-config-priority.sh +0 -0
  130. package/examples/basic-typescript-demo/test-file-targeting.sh +0 -0
  131. package/examples/basic-typescript-demo/tsconfig.json +0 -27
  132. package/examples/enhanced-config.json +0 -0
  133. package/examples/eslint-integration-demo/.eslintrc.js +0 -38
  134. package/examples/eslint-integration-demo/.sunlint.json +0 -42
  135. package/examples/eslint-integration-demo/next-env.d.ts +0 -5
  136. package/examples/eslint-integration-demo/next.config.js +0 -8
  137. package/examples/eslint-integration-demo/package-lock.json +0 -5740
  138. package/examples/eslint-integration-demo/package.json +0 -37
  139. package/examples/eslint-integration-demo/src/api.test.ts +0 -20
  140. package/examples/eslint-integration-demo/src/conflict-test.tsx +0 -44
  141. package/examples/eslint-integration-demo/src/naming-conflicts.ts +0 -50
  142. package/examples/eslint-integration-demo/test-file-targeting.sh +0 -0
  143. package/examples/eslint-integration-demo/tsconfig.json +0 -26
  144. package/examples/file-targeting-demo/global.d.ts +0 -11
  145. package/examples/file-targeting-demo/jest.config.js +0 -8
  146. package/examples/file-targeting-demo/sample.ts +0 -53
  147. package/examples/file-targeting-demo/src/server.js +0 -11
  148. package/examples/file-targeting-demo/src/server.test.js +0 -11
  149. package/examples/file-targeting-demo/src/types.d.ts +0 -4
  150. package/examples/file-targeting-demo/src/types.generated.ts +0 -10
  151. package/examples/file-targeting-demo/user-service.test.ts +0 -15
  152. package/examples/file-targeting-demo/user-service.ts +0 -13
  153. package/examples/file-targeting-demo/utils.js +0 -15
  154. package/examples/multi-language-project/.eslintrc.json +0 -38
  155. package/examples/multi-language-project/package.json +0 -37
  156. package/examples/multi-language-project/src/sample.ts +0 -39
  157. package/examples/rule-test-fixtures/README.md +0 -67
  158. package/examples/rule-test-fixtures/rules/C006_function_naming/clean/typescript-clean.ts +0 -64
  159. package/examples/rule-test-fixtures/rules/C006_function_naming/violations/dart-violations.dart +0 -56
  160. package/examples/rule-test-fixtures/rules/C006_function_naming/violations/typescript-violations.ts +0 -47
  161. package/examples/rule-test-fixtures/rules/C019_log_level_usage/clean/typescript-clean.ts +0 -93
  162. package/examples/rule-test-fixtures/rules/C019_log_level_usage/violations/dart-violations.dart +0 -75
  163. package/examples/rule-test-fixtures/rules/C019_log_level_usage/violations/typescript-violations.ts +0 -84
  164. package/examples/rule-test-fixtures/rules/C029_catch_block_logging/clean/typescript-clean.ts +0 -0
  165. package/examples/rule-test-fixtures/rules/C029_catch_block_logging/violations/typescript-violations.ts +0 -37
@@ -1,193 +0,0 @@
1
- "use strict";
2
-
3
- module.exports = {
4
- meta: {
5
- type: "problem",
6
- docs: {
7
- description:
8
- "Verify that data selection or database queries use parameterized queries, ORMs, entity frameworks, or are otherwise protected from database injection attacks",
9
- recommended: true,
10
- },
11
- schema: [],
12
- messages: {
13
- sqlInjection: "Potential SQL injection vulnerability detected. Use parameterized queries instead of string concatenation in '{{method}}'.",
14
- unsafeQuery: "Unsafe database query detected in '{{method}}'. Consider using ORM or parameterized queries.",
15
- stringConcatenation: "Avoid string concatenation in SQL queries. Use parameterized queries in '{{method}}'.",
16
- templateLiteralQuery: "Template literals in SQL queries can be vulnerable to injection. Use parameterized queries in '{{method}}'.",
17
- },
18
- },
19
-
20
- create(context) {
21
- // Database methods that should use parameterized queries
22
- const databaseMethods = [
23
- // MySQL
24
- "query", "execute", "prepare",
25
- // PostgreSQL
26
- "query", "execute",
27
- // MongoDB
28
- "find", "findOne", "aggregate", "updateOne", "updateMany", "deleteOne", "deleteMany",
29
- // SQLite
30
- "run", "get", "all", "each", "prepare",
31
- // General ORM methods
32
- "where", "whereRaw", "raw", "knex",
33
- // Sequelize
34
- "findAll", "findOne", "create", "update", "destroy",
35
- // TypeORM
36
- "createQueryBuilder", "query", "manager",
37
- // Prisma
38
- "findMany", "findUnique", "create", "update", "delete",
39
- ];
40
-
41
- // Dangerous string methods that suggest concatenation
42
- const dangerousStringMethods = [
43
- "concat", "replace", "replaceAll", "substring", "slice"
44
- ];
45
-
46
- // SQL keywords that when used with string operations are suspicious
47
- const sqlKeywords = [
48
- "SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER",
49
- "WHERE", "FROM", "JOIN", "UNION", "ORDER", "GROUP", "HAVING",
50
- "select", "insert", "update", "delete", "drop", "create", "alter",
51
- "where", "from", "join", "union", "order", "group", "having"
52
- ];
53
-
54
- function containsSqlKeywords(str) {
55
- return sqlKeywords.some(keyword => str.includes(keyword));
56
- }
57
-
58
- function checkStringConcatenation(node) {
59
- if (node.type === "BinaryExpression" && node.operator === "+") {
60
- const leftStr = getStringValue(node.left);
61
- const rightStr = getStringValue(node.right);
62
-
63
- if ((leftStr && containsSqlKeywords(leftStr)) ||
64
- (rightStr && containsSqlKeywords(rightStr))) {
65
- return true;
66
- }
67
- }
68
- return false;
69
- }
70
-
71
- function getStringValue(node) {
72
- if (node.type === "Literal" && typeof node.value === "string") {
73
- return node.value;
74
- }
75
- if (node.type === "TemplateLiteral") {
76
- return node.quasis.map(q => q.value.raw).join("");
77
- }
78
- return null;
79
- }
80
-
81
- function checkTemplateLiteral(node) {
82
- if (node.type === "TemplateLiteral") {
83
- const templateString = node.quasis.map(q => q.value.raw).join("");
84
- return containsSqlKeywords(templateString) && node.expressions.length > 0;
85
- }
86
- return false;
87
- }
88
-
89
- function isDatabaseCall(node) {
90
- if (node.type === "CallExpression") {
91
- // Check for method calls like db.query(), connection.execute()
92
- if (node.callee.type === "MemberExpression") {
93
- const methodName = node.callee.property.name;
94
- return databaseMethods.includes(methodName);
95
- }
96
-
97
- // Check for direct function calls like query()
98
- if (node.callee.type === "Identifier") {
99
- return databaseMethods.includes(node.callee.name);
100
- }
101
- }
102
- return false;
103
- }
104
-
105
- function getMethodName(node) {
106
- if (node.callee.type === "MemberExpression") {
107
- return node.callee.property.name;
108
- }
109
- if (node.callee.type === "Identifier") {
110
- return node.callee.name;
111
- }
112
- return "unknown";
113
- }
114
-
115
- function checkCallExpression(node) {
116
- if (!isDatabaseCall(node)) {
117
- return;
118
- }
119
-
120
- const methodName = getMethodName(node);
121
-
122
- // Check each argument
123
- for (const arg of node.arguments) {
124
- // Check for string concatenation
125
- if (checkStringConcatenation(arg)) {
126
- context.report({
127
- node: arg,
128
- messageId: "stringConcatenation",
129
- data: { method: methodName },
130
- });
131
- continue;
132
- }
133
-
134
- // Check for template literals with expressions
135
- if (checkTemplateLiteral(arg)) {
136
- context.report({
137
- node: arg,
138
- messageId: "templateLiteralQuery",
139
- data: { method: methodName },
140
- });
141
- continue;
142
- }
143
-
144
- // Check for potentially unsafe patterns
145
- if (arg.type === "Identifier" || arg.type === "MemberExpression") {
146
- // This is a variable or property access - could be unsafe
147
- // We'll be more lenient here and only warn if it's clearly a string concatenation
148
- continue;
149
- }
150
- }
151
- }
152
-
153
- return {
154
- CallExpression: checkCallExpression,
155
-
156
- // Also check assignment expressions that might build SQL strings
157
- AssignmentExpression(node) {
158
- if (node.operator === "=" || node.operator === "+=") {
159
- if (checkStringConcatenation(node.right)) {
160
- const leftStr = getStringValue(node.right.left);
161
- const rightStr = getStringValue(node.right.right);
162
-
163
- if ((leftStr && containsSqlKeywords(leftStr)) ||
164
- (rightStr && containsSqlKeywords(rightStr))) {
165
- context.report({
166
- node: node.right,
167
- messageId: "sqlInjection",
168
- data: { method: "string assignment" },
169
- });
170
- }
171
- }
172
- }
173
- },
174
-
175
- // Check variable declarations
176
- VariableDeclarator(node) {
177
- if (node.init && checkStringConcatenation(node.init)) {
178
- const leftStr = getStringValue(node.init.left);
179
- const rightStr = getStringValue(node.init.right);
180
-
181
- if ((leftStr && containsSqlKeywords(leftStr)) ||
182
- (rightStr && containsSqlKeywords(rightStr))) {
183
- context.report({
184
- node: node.init,
185
- messageId: "sqlInjection",
186
- data: { method: "variable declaration" },
187
- });
188
- }
189
- }
190
- },
191
- };
192
- },
193
- };
@@ -1,56 +0,0 @@
1
- "use strict";
2
-
3
- /**
4
- * S018 – Positive Input Validation
5
- * OWASP ASVS 5.1.3
6
- * Ensure that all input is validated using positive validation (allow lists).
7
- */
8
-
9
- module.exports = {
10
- meta: {
11
- type: "problem",
12
- docs: {
13
- description:
14
- "Ensure input validation uses allow lists (whitelisting), not deny lists (blacklisting) wherever possible.",
15
- recommended: true,
16
- },
17
- schema: [],
18
- messages: {
19
- denyList:
20
- "Do not use deny lists (blacklisting) for input validation. Use allow lists (whitelisting) instead.",
21
- },
22
- },
23
-
24
- create(context) {
25
- // Detect use of common denylist patterns
26
- function isDenyListCheck(node) {
27
- // Example: if (input.includes('badword')) { ... }
28
- return (
29
- node &&
30
- node.type === "CallExpression" &&
31
- node.callee.type === "MemberExpression" &&
32
- (node.callee.property.name === "includes" ||
33
- node.callee.property.name === "indexOf") &&
34
- node.arguments.length &&
35
- node.arguments[0].type === "Literal" &&
36
- typeof node.arguments[0].value === "string"
37
- );
38
- }
39
-
40
- return {
41
- IfStatement(node) {
42
- if (
43
- node.test &&
44
- ((node.test.type === "UnaryExpression" &&
45
- isDenyListCheck(node.test.argument)) ||
46
- (node.test.type === "CallExpression" && isDenyListCheck(node.test)))
47
- ) {
48
- context.report({
49
- node: node.test,
50
- messageId: "denyList",
51
- });
52
- }
53
- },
54
- };
55
- },
56
- };
@@ -1,113 +0,0 @@
1
- /**
2
- * Custom ESLint rule for: S019 – Sanitize input before using in email systems
3
- * Rule ID: custom/s019
4
- * Purpose: Prevent SMTP/IMAP injection via unvalidated user input
5
- */
6
-
7
- "use strict";
8
-
9
- module.exports = {
10
- meta: {
11
- type: "problem",
12
- docs: {
13
- description:
14
- "Ensure user input is sanitized before use in mail functions",
15
- recommended: true,
16
- },
17
- schema: [],
18
- messages: {
19
- unsanitizedEmailInput:
20
- "Unsanitized user input '{{input}}' should not be passed directly to mail systems.",
21
- },
22
- },
23
-
24
- create(context) {
25
- const mailFunctionNames = ["sendMail", "sendEmail", "mailer.send"];
26
- const sanitizeFunctions = [
27
- "sanitize",
28
- "escape",
29
- "validateEmail",
30
- "cleanInput",
31
- ];
32
-
33
- // Identify common patterns of user input
34
- function isUserInput(name) {
35
- return (
36
- name.includes("req.body") ||
37
- name.includes("input") ||
38
- name.includes("form")
39
- );
40
- }
41
-
42
- // Check if a value has been sanitized
43
- function isSanitized(node) {
44
- return (
45
- node.type === "CallExpression" &&
46
- node.callee &&
47
- sanitizeFunctions.includes(node.callee.name)
48
- );
49
- }
50
-
51
- return {
52
- CallExpression(node) {
53
- const calleeName =
54
- (node.callee.type === "MemberExpression" &&
55
- node.callee.property.name) ||
56
- node.callee.name;
57
-
58
- // Only analyze known mail function calls
59
- if (!mailFunctionNames.includes(calleeName)) return;
60
-
61
- for (const arg of node.arguments) {
62
- // Direct use of user input in function arguments
63
- if (arg.type === "MemberExpression") {
64
- const fullName = context.getSourceCode().getText(arg);
65
- if (isUserInput(fullName)) {
66
- context.report({
67
- node: arg,
68
- messageId: "unsanitizedEmailInput",
69
- data: { input: fullName },
70
- });
71
- }
72
- }
73
-
74
- // If email parameters are passed in an object
75
- if (arg.type === "ObjectExpression") {
76
- for (const prop of arg.properties) {
77
- const value = prop.value;
78
-
79
- // User input used directly
80
- if (
81
- value.type === "MemberExpression" &&
82
- isUserInput(context.getSourceCode().getText(value))
83
- ) {
84
- context.report({
85
- node: value,
86
- messageId: "unsanitizedEmailInput",
87
- data: { input: context.getSourceCode().getText(value) },
88
- });
89
- }
90
-
91
- // User input passed through function without sanitization
92
- if (
93
- value.type === "CallExpression" &&
94
- !isSanitized(value) &&
95
- value.arguments.some(
96
- (a) =>
97
- a.type === "MemberExpression" &&
98
- isUserInput(context.getSourceCode().getText(a))
99
- )
100
- ) {
101
- context.report({
102
- node: value,
103
- messageId: "unsanitizedEmailInput",
104
- data: { input: context.getSourceCode().getText(value) },
105
- });
106
- }
107
- }
108
- }
109
- }
110
- },
111
- };
112
- },
113
- };
@@ -1,89 +0,0 @@
1
- /**
2
- * ESLint rule S020: Avoid eval() and dynamic code execution
3
- * Prevents Remote Code Execution vulnerabilities
4
- * OWASP ASVS 5.2.4 compliance
5
- */
6
-
7
- const ZERO_INDEX = 0;
8
-
9
- const s020Rule = {
10
- meta: {
11
- type: "problem",
12
- docs: {
13
- description: "Avoid eval() and dynamic code execution to prevent RCE vulnerabilities",
14
- category: "Security",
15
- recommended: "error"
16
- },
17
- messages: {
18
- avoidEval: "Avoid eval() - use JSON.parse() or safer alternatives to prevent RCE",
19
- avoidFunction: "Avoid Function constructor - use regular functions to prevent code injection",
20
- avoidStringTimeout: "Avoid string arguments in setTimeout/setInterval - use function references",
21
- avoidDynamicExecution: "Avoid dynamic code execution - sanitize and validate all inputs"
22
- }
23
- },
24
-
25
- create(context) {
26
- return {
27
- CallExpression(node) {
28
- // Check for eval() usage
29
- if (node.callee.type === 'Identifier' && node.callee.name === 'eval') {
30
- context.report({
31
- node,
32
- messageId: 'avoidEval'
33
- });
34
- }
35
-
36
- // Check for setTimeout/setInterval with string arguments
37
- if (node.callee.type === 'Identifier' &&
38
- (node.callee.name === 'setTimeout' || node.callee.name === 'setInterval')) {
39
- if (node.arguments.length > ZERO_INDEX &&
40
- node.arguments[ZERO_INDEX].type === 'Literal' &&
41
- typeof node.arguments[ZERO_INDEX].value === 'string') {
42
- context.report({
43
- node,
44
- messageId: 'avoidStringTimeout'
45
- });
46
- }
47
- }
48
-
49
- // Check for dynamic script execution methods
50
- if (node.callee.type === 'MemberExpression') {
51
- const methodName = node.callee.property.name;
52
- if (methodName === 'executeScript' || methodName === 'executeJavaScript') {
53
- context.report({
54
- node,
55
- messageId: 'avoidDynamicExecution'
56
- });
57
- }
58
- }
59
- },
60
-
61
- NewExpression(node) {
62
- // Check for new Function() constructor
63
- if (node.callee.type === 'Identifier' && node.callee.name === 'Function') {
64
- context.report({
65
- node,
66
- messageId: 'avoidFunction'
67
- });
68
- }
69
- },
70
-
71
- MemberExpression(node) {
72
- // Check for indirect eval access via window/global
73
- if (node.property.type === 'Identifier' && node.property.name === 'eval') {
74
- if (node.object.type === 'Identifier') {
75
- const objectName = node.object.name;
76
- if (objectName === 'window' || objectName === 'global' || objectName === 'globalThis') {
77
- context.report({
78
- node,
79
- messageId: 'avoidEval'
80
- });
81
- }
82
- }
83
- }
84
- }
85
- };
86
- }
87
- };
88
-
89
- module.exports = s020Rule;
@@ -1,78 +0,0 @@
1
- "use strict";
2
-
3
- module.exports = {
4
- meta: {
5
- type: "problem",
6
- docs: {
7
- description: "Ensure HTML output is properly encoded to prevent XSS.",
8
- recommended: true,
9
- },
10
- schema: [],
11
- messages: {
12
- unsafeOutput:
13
- "Output to HTML must use proper encoding (e.g., escapeHtml) to prevent XSS.",
14
- },
15
- },
16
-
17
- create(context) {
18
- const outputMethods = new Set(["write", "print", "println", "log"]);
19
- const outputObjects = new Set(["out", "writer", "response", "console", "System"]);
20
- const riskyVarNames = ["input", "user", "html", "token", "param", "data"];
21
-
22
- function isEscapeFunction(node) {
23
- return (
24
- node.type === "CallExpression" &&
25
- node.callee.type === "Identifier" &&
26
- (node.callee.name.includes("escape") || node.callee.name.includes("encode"))
27
- );
28
- }
29
-
30
- function isUnescapedArg(node) {
31
- if (!node) return false;
32
-
33
- if (node.type === "Literal") return false;
34
- if (isEscapeFunction(node)) return false;
35
-
36
- if (node.type === "BinaryExpression") {
37
- return isUnescapedArg(node.left) || isUnescapedArg(node.right);
38
- }
39
-
40
- if (node.type === "Identifier") {
41
- const varName = node.name.toLowerCase();
42
- return riskyVarNames.some((risky) => varName.includes(risky));
43
- }
44
-
45
- return true;
46
- }
47
-
48
- return {
49
- CallExpression(node) {
50
- const callee = node.callee;
51
-
52
- if (
53
- callee.type === "MemberExpression" &&
54
- outputMethods.has(callee.property.name)
55
- ) {
56
- const object = callee.object;
57
-
58
- const isLikelyHtmlOutput =
59
- (object.type === "Identifier" && outputObjects.has(object.name)) ||
60
- (object.type === "MemberExpression" &&
61
- object.property?.name === "getWriter");
62
-
63
- if (!isLikelyHtmlOutput) return;
64
-
65
- for (const arg of node.arguments) {
66
- if (isUnescapedArg(arg)) {
67
- context.report({
68
- node: arg,
69
- messageId: "unsafeOutput",
70
- });
71
- break;
72
- }
73
- }
74
- }
75
- },
76
- };
77
- },
78
- };