@sun-asterisk/sunlint 1.0.5

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 (192) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/LICENSE +21 -0
  3. package/README.md +490 -0
  4. package/cli-legacy.js +355 -0
  5. package/cli.js +35 -0
  6. package/config/default.json +22 -0
  7. package/config/presets/beginner.json +36 -0
  8. package/config/presets/ci.json +46 -0
  9. package/config/presets/recommended.json +24 -0
  10. package/config/presets/strict.json +32 -0
  11. package/config/rules-registry.json +681 -0
  12. package/config/sunlint-schema.json +166 -0
  13. package/config/typescript/custom-rules-new.js +0 -0
  14. package/config/typescript/custom-rules.js +9 -0
  15. package/config/typescript/eslint.config.js +110 -0
  16. package/config/typescript/package-lock.json +1585 -0
  17. package/config/typescript/package.json +13 -0
  18. package/config/typescript/security-rules/index.js +90 -0
  19. package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
  20. package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
  21. package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
  22. package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
  23. package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
  24. package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
  25. package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
  26. package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
  27. package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
  28. package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
  29. package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
  30. package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
  31. package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
  32. package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
  33. package/config/typescript/security-rules/s022-output-encoding.js +78 -0
  34. package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
  35. package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
  36. package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
  37. package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
  38. package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
  39. package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
  40. package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
  41. package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
  42. package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
  43. package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
  44. package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
  45. package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
  46. package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
  47. package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
  48. package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
  49. package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
  50. package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
  51. package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
  52. package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
  53. package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
  54. package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
  55. package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
  56. package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
  57. package/config/typescript/security-rules/s057-utc-logging.js +54 -0
  58. package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
  59. package/config/typescript/test-s005-working.ts +22 -0
  60. package/config/typescript/tsconfig.json +29 -0
  61. package/core/ai-analyzer.js +169 -0
  62. package/core/analysis-orchestrator.js +705 -0
  63. package/core/cli-action-handler.js +230 -0
  64. package/core/cli-program.js +106 -0
  65. package/core/config-manager.js +396 -0
  66. package/core/config-merger.js +136 -0
  67. package/core/config-override-processor.js +74 -0
  68. package/core/config-preset-resolver.js +65 -0
  69. package/core/config-source-loader.js +152 -0
  70. package/core/config-validator.js +126 -0
  71. package/core/dependency-manager.js +105 -0
  72. package/core/eslint-engine-service.js +312 -0
  73. package/core/eslint-instance-manager.js +104 -0
  74. package/core/eslint-integration-service.js +363 -0
  75. package/core/git-utils.js +170 -0
  76. package/core/multi-rule-runner.js +239 -0
  77. package/core/output-service.js +250 -0
  78. package/core/report-generator.js +320 -0
  79. package/core/rule-mapping-service.js +309 -0
  80. package/core/rule-selection-service.js +121 -0
  81. package/core/sunlint-engine-service.js +23 -0
  82. package/core/typescript-analyzer.js +262 -0
  83. package/core/typescript-engine.js +313 -0
  84. package/docs/AI.md +163 -0
  85. package/docs/ARCHITECTURE.md +78 -0
  86. package/docs/CI-CD-GUIDE.md +315 -0
  87. package/docs/COMMAND-EXAMPLES.md +256 -0
  88. package/docs/DEBUG.md +86 -0
  89. package/docs/DISTRIBUTION.md +153 -0
  90. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  91. package/docs/ESLINT_INTEGRATION.md +238 -0
  92. package/docs/FOLDER_STRUCTURE.md +59 -0
  93. package/docs/HEURISTIC_VS_AI.md +113 -0
  94. package/docs/README.md +32 -0
  95. package/docs/RELEASE_GUIDE.md +230 -0
  96. package/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
  97. package/eslint-integration/.eslintrc.js +98 -0
  98. package/eslint-integration/cli.js +35 -0
  99. package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
  100. package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
  101. package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
  102. package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
  103. package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
  104. package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
  105. package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
  106. package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
  107. package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
  108. package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
  109. package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
  110. package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
  111. package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
  112. package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
  113. package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
  114. package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
  115. package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
  116. package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
  117. package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
  118. package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
  119. package/eslint-integration/eslint-plugin-custom/index.js +155 -0
  120. package/eslint-integration/eslint-plugin-custom/package.json +13 -0
  121. package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
  122. package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
  123. package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
  124. package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
  125. package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
  126. package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
  127. package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
  128. package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
  129. package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
  130. package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
  131. package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
  132. package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
  133. package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
  134. package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
  135. package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
  136. package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
  137. package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
  138. package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
  139. package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
  140. package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
  141. package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
  142. package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
  143. package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
  144. package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
  145. package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
  146. package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
  147. package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
  148. package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
  149. package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
  150. package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
  151. package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
  152. package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
  153. package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
  154. package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
  155. package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
  156. package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
  157. package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
  158. package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
  159. package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
  160. package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
  161. package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
  162. package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  163. package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
  164. package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
  165. package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
  166. package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
  167. package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
  168. package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
  169. package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
  170. package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
  171. package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
  172. package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
  173. package/eslint-integration/eslint.config.js +125 -0
  174. package/eslint-integration/eslint.config.simple.js +24 -0
  175. package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
  176. package/eslint-integration/package.json +23 -0
  177. package/eslint-integration/sample.ts +53 -0
  178. package/eslint-integration/test-s003.js +5 -0
  179. package/eslint-integration/tsconfig.json +27 -0
  180. package/examples/.github/workflows/code-quality.yml +111 -0
  181. package/examples/.sunlint.json +42 -0
  182. package/examples/README.md +47 -0
  183. package/examples/package.json +33 -0
  184. package/package.json +100 -0
  185. package/rules/C006_function_naming/analyzer.js +338 -0
  186. package/rules/C006_function_naming/config.json +86 -0
  187. package/rules/C019_log_level_usage/analyzer.js +359 -0
  188. package/rules/C019_log_level_usage/config.json +121 -0
  189. package/rules/C029_catch_block_logging/analyzer.js +339 -0
  190. package/rules/C029_catch_block_logging/config.json +59 -0
  191. package/rules/C031_validation_separation/README.md +72 -0
  192. package/rules/C031_validation_separation/analyzer.js +186 -0
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * S014 – Insecure TLS Version
5
+ * OWASP ASVS 9.1.3
6
+ * Ensure that only secure versions of TLS (1.2 and 1.3) are enabled.
7
+ */
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Ensure that only secure versions of TLS are enabled. Do not use SSLv3, TLS 1.0, or TLS 1.1.",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ insecureTLS:
20
+ "Insecure TLS/SSL version detected. Only TLS 1.2 or TLS 1.3 should be used.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ Property(node) {
27
+ // Detect insecure minVersion in HTTPS server options
28
+ if (
29
+ node.key &&
30
+ node.key.name === "minVersion" &&
31
+ node.value.type === "Literal" &&
32
+ typeof node.value.value === "string"
33
+ ) {
34
+ const version = node.value.value.toLowerCase();
35
+ if (
36
+ version === "tlsv1" ||
37
+ version === "tlsv1.0" ||
38
+ version === "tlsv1.1" ||
39
+ version === "sslv3"
40
+ ) {
41
+ context.report({
42
+ node: node.value,
43
+ messageId: "insecureTLS",
44
+ });
45
+ }
46
+ }
47
+ },
48
+ };
49
+ },
50
+ };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * S015 – Insecure TLS Certificate
5
+ * OWASP ASVS 9.2.1
6
+ * Ensure that only trusted TLS certificates are accepted.
7
+ */
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Ensure only trusted TLS certificates are used. Reject self-signed, expired, or untrusted certificates unless explicitly allowed for internal use.",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ untrustedCert:
20
+ "Untrusted/self-signed/expired certificate accepted. Only use trusted certificates in production.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ Property(node) {
27
+ // Check if rejectUnauthorized: false is used (should not be used in production)
28
+ if (
29
+ node.key &&
30
+ (node.key.name === "rejectUnauthorized" ||
31
+ node.key.value === "rejectUnauthorized") &&
32
+ node.value.type === "Literal" &&
33
+ node.value.value === false
34
+ ) {
35
+ context.report({
36
+ node: node.value,
37
+ messageId: "untrustedCert",
38
+ });
39
+ }
40
+ },
41
+ };
42
+ },
43
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ESLint Rule: S016 - Sensitive Data in Query Parameters
3
+ * Rule ID: custom/s016
4
+ * Description: Sensitive data should not be passed via query parameters (e.g., @Query('password')).
5
+ */
6
+
7
+ "use strict";
8
+
9
+ const SENSITIVE_TERMS = [
10
+ "password",
11
+ "otp",
12
+ "token",
13
+ "creditCard",
14
+ "ssn",
15
+ "apiKey",
16
+ ];
17
+
18
+ module.exports = {
19
+ meta: {
20
+ type: "problem",
21
+ docs: {
22
+ description: "Detect sensitive parameters passed via query string.",
23
+ },
24
+ messages: {
25
+ sensitiveParam:
26
+ "Avoid passing sensitive data via query parameters: '{{param}}'",
27
+ },
28
+ schema: [],
29
+ },
30
+
31
+ create(context) {
32
+ return {
33
+ Decorator(node) {
34
+ if (
35
+ node.expression &&
36
+ node.expression.type === "CallExpression" &&
37
+ node.expression.callee &&
38
+ node.expression.callee.type === "Identifier" &&
39
+ node.expression.callee.name === "Query"
40
+ ) {
41
+ const arg = node.expression.arguments[0];
42
+ if (arg && arg.type === "Literal" && typeof arg.value === "string") {
43
+ const paramName = arg.value.toLowerCase();
44
+ for (const sensitive of SENSITIVE_TERMS) {
45
+ if (paramName.includes(sensitive.toLowerCase())) {
46
+ context.report({
47
+ node: arg,
48
+ messageId: "sensitiveParam",
49
+ data: { param: arg.value },
50
+ });
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ },
57
+ };
58
+ },
59
+ };
@@ -0,0 +1,193 @@
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
+ };
@@ -0,0 +1,56 @@
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
+ };
@@ -0,0 +1,113 @@
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
+ };
@@ -0,0 +1,89 @@
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;
@@ -0,0 +1,78 @@
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
+ };