@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,86 @@
1
+ /**
2
+ * Custom ESLint rule: S003 – Prevent unvalidated redirects
3
+ * Rule ID: custom/s003
4
+ */
5
+
6
+ "use strict";
7
+
8
+ module.exports = {
9
+ meta: {
10
+ type: "problem",
11
+ docs: {
12
+ description: "Prevent unvalidated redirects to user-controlled URLs",
13
+ recommended: true,
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ unvalidatedRedirect:
18
+ "Redirect to user-controlled URL '{{target}}' without validation. Use allow list or warning page.",
19
+ },
20
+ },
21
+
22
+ create(context) {
23
+ const userInputSources = [
24
+ "req.query",
25
+ "req.body",
26
+ "input",
27
+ "form",
28
+ "params",
29
+ ];
30
+
31
+ function isUserControlled(nodeText) {
32
+ return userInputSources.some((source) => nodeText.includes(source));
33
+ }
34
+
35
+ function reportIfUserControlled(node, nodeText) {
36
+ if (isUserControlled(nodeText)) {
37
+ context.report({
38
+ node,
39
+ messageId: "unvalidatedRedirect",
40
+ data: { target: nodeText },
41
+ });
42
+ }
43
+ }
44
+
45
+ return {
46
+ CallExpression(node) {
47
+ const callee = node.callee;
48
+
49
+ // res.redirect(...)
50
+ if (
51
+ callee.type === "MemberExpression" &&
52
+ callee.property.name === "redirect" &&
53
+ node.arguments.length > 0
54
+ ) {
55
+ const argText = context.getSourceCode().getText(node.arguments[0]);
56
+ reportIfUserControlled(node, argText);
57
+ }
58
+ },
59
+
60
+ AssignmentExpression(node) {
61
+ const left = node.left;
62
+ const right = node.right;
63
+
64
+ if (
65
+ left.type === "MemberExpression" &&
66
+ left.object.name === "window" &&
67
+ ["location", "location.href"].includes(left.property.name || "") &&
68
+ right
69
+ ) {
70
+ const text = context.getSourceCode().getText(right);
71
+ reportIfUserControlled(node, text);
72
+ }
73
+
74
+ if (
75
+ left.type === "MemberExpression" &&
76
+ left.object.name === "location" &&
77
+ ["href", "replace"].includes(left.property.name || "") &&
78
+ right
79
+ ) {
80
+ const text = context.getSourceCode().getText(right);
81
+ reportIfUserControlled(node, text);
82
+ }
83
+ },
84
+ };
85
+ },
86
+ };
@@ -0,0 +1,95 @@
1
+ /**
2
+ * ESLint rule: S005 – Do not use Origin header for authentication/access control
3
+ * Rule ID: custom/s005
4
+ * Description: Prevent usage of request.getHeader("Origin") for security decisions.
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Do not use the Origin header to make authentication or access control decisions.",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ noOriginCheck:
20
+ "Do not use the Origin header for authentication or access control decisions. It can be easily spoofed.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ const trackedVariables = new Set();
26
+
27
+ function isOriginHeaderCall(node) {
28
+ // Match: request.getHeader("Origin")
29
+ return (
30
+ node.type === "CallExpression" &&
31
+ node.callee.type === "MemberExpression" &&
32
+ node.callee.property.name === "getHeader" &&
33
+ node.arguments.length &&
34
+ node.arguments[0].type === "Literal" &&
35
+ node.arguments[0].value === "Origin"
36
+ );
37
+ }
38
+
39
+ return {
40
+ VariableDeclarator(node) {
41
+ // Track: const origin = request.getHeader("Origin");
42
+ if (node.init && isOriginHeaderCall(node.init)) {
43
+ trackedVariables.add(node.id.name);
44
+ }
45
+ },
46
+
47
+ AssignmentExpression(node) {
48
+ // Track: origin = request.getHeader("Origin");
49
+ if (
50
+ node.left.type === "Identifier" &&
51
+ isOriginHeaderCall(node.right)
52
+ ) {
53
+ trackedVariables.add(node.left.name);
54
+ }
55
+ },
56
+
57
+ CallExpression(node) {
58
+ // Detect: origin.equals(...) or origin.includes(...) or "xyz" === origin
59
+ if (
60
+ node.callee.type === "MemberExpression" &&
61
+ ["includes", "startsWith", "endsWith", "indexOf", "equals"].includes(
62
+ node.callee.property.name
63
+ )
64
+ ) {
65
+ const arg = node.callee.object;
66
+ if (arg.type === "Identifier" && trackedVariables.has(arg.name)) {
67
+ context.report({ node, messageId: "noOriginCheck" });
68
+ }
69
+ }
70
+
71
+ // Detect: "admin" === origin (binary expression inside if)
72
+ if (
73
+ node.parent &&
74
+ node.parent.type === "IfStatement" &&
75
+ node.arguments.some(
76
+ (arg) =>
77
+ arg.type === "Identifier" && trackedVariables.has(arg.name)
78
+ )
79
+ ) {
80
+ context.report({ node, messageId: "noOriginCheck" });
81
+ }
82
+ },
83
+
84
+ BinaryExpression(node) {
85
+ // Detect: if (origin === "some-origin")
86
+ const ids = [node.left, node.right].filter(
87
+ (e) => e.type === "Identifier" && trackedVariables.has(e.name)
88
+ );
89
+ if (ids.length > 0) {
90
+ context.report({ node, messageId: "noOriginCheck" });
91
+ }
92
+ },
93
+ };
94
+ },
95
+ };
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * S006 – Activation/Recovery Secret Not Plaintext
5
+ * OWASP ASVS 2.5.1
6
+ * Ensure that system-generated activation or recovery secrets are not sent in plaintext to the user.
7
+ */
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Disallow sending activation or recovery secrets in plaintext to the user (e.g., via email/SMS). Use secure channels or one-time tokens.",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ plaintextSecret:
20
+ "Do not send activation or recovery secrets in plaintext. Use a hashed or one-time token instead.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ function isActivationOrRecovery(text) {
26
+ if (!text || typeof text !== "string") return false;
27
+ const keywords = [
28
+ "activation code",
29
+ "activation token",
30
+ "recovery code",
31
+ "recovery token",
32
+ ];
33
+ return keywords.some((kw) => text.toLowerCase().includes(kw));
34
+ }
35
+
36
+ return {
37
+ CallExpression(node) {
38
+ // Check for sendMail({ text: ... }) or sendSMS(...)
39
+ if (
40
+ node.callee.type === "Identifier" &&
41
+ (node.callee.name === "sendMail" || node.callee.name === "sendSMS") &&
42
+ node.arguments.length
43
+ ) {
44
+ const arg = node.arguments[0];
45
+ if (arg.type === "ObjectExpression") {
46
+ const bodyProp = arg.properties.find(
47
+ (prop) =>
48
+ prop.key &&
49
+ (prop.key.name === "text" || prop.key.name === "html") &&
50
+ prop.value.type === "Literal"
51
+ );
52
+ if (bodyProp && isActivationOrRecovery(bodyProp.value.value)) {
53
+ context.report({
54
+ node: bodyProp.value,
55
+ messageId: "plaintextSecret",
56
+ });
57
+ }
58
+ }
59
+ if (arg.type === "Literal" && isActivationOrRecovery(arg.value)) {
60
+ context.report({
61
+ node: arg,
62
+ messageId: "plaintextSecret",
63
+ });
64
+ }
65
+ }
66
+ },
67
+ };
68
+ },
69
+ };
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * S008 – Crypto Agility
5
+ * OWASP ASVS 6.2.4
6
+ * Ensure that cryptographic algorithms and their parameters can be reconfigured or upgraded.
7
+ */
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Ensure cryptographic algorithms, key lengths, and modes are not hardcoded and can be configured or upgraded easily.",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ hardcodedAlgo:
20
+ "Do not hardcode cryptographic algorithm or parameters. Use configurable values instead.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ function isHardcodedCrypto(node) {
26
+ if (node.type === "Literal" && typeof node.value === "string") {
27
+ const lower = node.value.toLowerCase();
28
+ const algos = [
29
+ "aes-128-cbc",
30
+ "aes-256-cbc",
31
+ "aes-256-gcm",
32
+ "sha1",
33
+ "sha256",
34
+ "sha512",
35
+ "md5",
36
+ "des",
37
+ "rc4",
38
+ "blowfish",
39
+ ];
40
+ return algos.some((algo) => lower.includes(algo));
41
+ }
42
+ return false;
43
+ }
44
+
45
+ return {
46
+ CallExpression(node) {
47
+ // Detect hardcoded crypto algorithm in common Node.js crypto calls
48
+ if (
49
+ node.callee.type === "MemberExpression" &&
50
+ node.callee.object.name === "crypto" &&
51
+ node.arguments.length > 0 &&
52
+ isHardcodedCrypto(node.arguments[0])
53
+ ) {
54
+ context.report({
55
+ node: node.arguments[0],
56
+ messageId: "hardcodedAlgo",
57
+ });
58
+ }
59
+ },
60
+ };
61
+ },
62
+ };
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description:
8
+ "Disallow use of insecure cryptographic algorithms, cipher modes, paddings, and hash functions",
9
+ recommended: true,
10
+ },
11
+ schema: [],
12
+ messages: {
13
+ insecureCipher: "Do not use insecure cipher '{{algo}}'. Use AES with 128-bit block size or higher.",
14
+ insecureMode: "Do not use insecure cipher mode '{{mode}}'. Use CBC or GCM instead.",
15
+ insecurePadding: "Do not use insecure padding '{{padding}}'. Use OAEP or other secure paddings.",
16
+ weakHash: "Do not use weak hash function '{{hash}}'. Use SHA-256 or stronger.",
17
+ },
18
+ },
19
+
20
+ create(context) {
21
+ const insecureCiphers = ["DES", "DESede", "Blowfish"];
22
+ const insecureModes = ["ECB"];
23
+ const insecurePaddings = ["PKCS1Padding"];
24
+ const weakHashes = ["MD5", "SHA1", "SHA-1"];
25
+
26
+ function checkAlgorithmString(node, rawValue) {
27
+ const value = rawValue.toUpperCase();
28
+
29
+ for (const cipher of insecureCiphers) {
30
+ if (value.includes(cipher.toUpperCase())) {
31
+ context.report({
32
+ node,
33
+ messageId: "insecureCipher",
34
+ data: { algo: cipher },
35
+ });
36
+ return;
37
+ }
38
+ }
39
+
40
+ for (const mode of insecureModes) {
41
+ if (value.includes(mode.toUpperCase())) {
42
+ context.report({
43
+ node,
44
+ messageId: "insecureMode",
45
+ data: { mode },
46
+ });
47
+ return;
48
+ }
49
+ }
50
+
51
+ for (const padding of insecurePaddings) {
52
+ if (value.includes(padding.toUpperCase())) {
53
+ context.report({
54
+ node,
55
+ messageId: "insecurePadding",
56
+ data: { padding },
57
+ });
58
+ return;
59
+ }
60
+ }
61
+
62
+ for (const hash of weakHashes) {
63
+ if (value === hash.toUpperCase()) {
64
+ context.report({
65
+ node,
66
+ messageId: "weakHash",
67
+ data: { hash },
68
+ });
69
+ return;
70
+ }
71
+ }
72
+ }
73
+
74
+ return {
75
+ CallExpression(node) {
76
+ if (
77
+ node.callee.type === "MemberExpression" &&
78
+ node.callee.object.name === "crypto"
79
+ ) {
80
+ const method = node.callee.property.name;
81
+
82
+ // Check hash functions
83
+ if (
84
+ method === "createHash" &&
85
+ node.arguments.length &&
86
+ node.arguments[0].type === "Literal"
87
+ ) {
88
+ checkAlgorithmString(node.arguments[0], node.arguments[0].value);
89
+ }
90
+
91
+ // Check ciphers and paddings
92
+ if (
93
+ (method === "createCipher" || method === "createCipheriv") &&
94
+ node.arguments.length &&
95
+ node.arguments[0].type === "Literal"
96
+ ) {
97
+ checkAlgorithmString(node.arguments[0], node.arguments[0].value);
98
+ }
99
+ }
100
+ },
101
+ };
102
+ },
103
+ };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Custom ESLint rule for: S010 – Insecure random generator used in sensitive context
3
+ * Rule ID: custom/s010
4
+ * Purpose: Disallow Math.random(), UUID, or insecure Random-like usage in sensitive variables (token, password, etc.)
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Disallow insecure random generators (Math.random, UUID, etc.) for sensitive values like tokens, passwords",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ insecureRandom:
20
+ "Do not use insecure random generator '{{name}}' for sensitive values. Use crypto.randomBytes or a CSPRNG instead.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ const sensitiveKeywords = [
26
+ "token",
27
+ "otp",
28
+ "session",
29
+ "reset",
30
+ "password",
31
+ "link",
32
+ "key",
33
+ "auth",
34
+ ];
35
+
36
+ // Check if a string contains any sensitive keyword
37
+ function isSensitiveName(name) {
38
+ return sensitiveKeywords.some((kw) => name.toLowerCase().includes(kw));
39
+ }
40
+
41
+ function isInSensitiveContext(node) {
42
+ const variable = findNearestVariable(node);
43
+ const func = findNearestFunction(node);
44
+
45
+ return (
46
+ (variable && isSensitiveName(variable.name)) ||
47
+ (func && isSensitiveName(func.name))
48
+ );
49
+ }
50
+
51
+ function findNearestVariable(node) {
52
+ while (node) {
53
+ if (node.type === "VariableDeclarator" && node.id.type === "Identifier") {
54
+ return node.id;
55
+ }
56
+ node = node.parent;
57
+ }
58
+ return null;
59
+ }
60
+
61
+ function findNearestFunction(node) {
62
+ while (node) {
63
+ if (
64
+ (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") &&
65
+ node.id
66
+ ) {
67
+ return node.id;
68
+ }
69
+ node = node.parent;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ return {
75
+ CallExpression(node) {
76
+ const callee = node.callee;
77
+
78
+ // Math.random()
79
+ if (
80
+ callee.type === "MemberExpression" &&
81
+ callee.object.name === "Math" &&
82
+ callee.property.name === "random"
83
+ ) {
84
+ if (isInSensitiveContext(node)) {
85
+ context.report({
86
+ node,
87
+ messageId: "insecureRandom",
88
+ data: { name: "Math.random" },
89
+ });
90
+ }
91
+ }
92
+
93
+ // UUID.v4() or uuid()
94
+ if (
95
+ callee.type === "Identifier" &&
96
+ (callee.name === "uuid" || callee.name === "uuidv4")
97
+ ) {
98
+ if (isInSensitiveContext(node)) {
99
+ context.report({
100
+ node,
101
+ messageId: "insecureRandom",
102
+ data: { name: callee.name },
103
+ });
104
+ }
105
+ }
106
+ },
107
+ NewExpression(node) {
108
+ // new Random() – simulate Java-like pattern in JS context if exists
109
+ if (
110
+ node.callee.type === "Identifier" &&
111
+ node.callee.name === "Random" &&
112
+ isInSensitiveContext(node)
113
+ ) {
114
+ context.report({
115
+ node,
116
+ messageId: "insecureRandom",
117
+ data: { name: "Random" },
118
+ });
119
+ }
120
+ },
121
+ };
122
+ },
123
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Custom ESLint rule for: S011 – UUID must be version 4 and use CSPRNG
3
+ * Rule ID: custom/s011
4
+ * Purpose: Prevent usage of UUID.randomUUID() in security-sensitive contexts.
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description: "Disallow use of UUID.randomUUID() for sensitive identifiers. Use SecureRandom-based UUIDs instead.",
14
+ recommended: true,
15
+ },
16
+ schema: [],
17
+ messages: {
18
+ insecureUuid: "Do not use UUID.randomUUID() for '{{context}}'. Use a UUID v4 generator with SecureRandom instead.",
19
+ },
20
+ },
21
+
22
+ create(context) {
23
+ const sensitiveKeywords = [
24
+ "token", "session", "reset", "password", "link", "key", "auth", "uuid", "guid",
25
+ ];
26
+
27
+ function containsSensitiveKeyword(name = "") {
28
+ const lower = name.toLowerCase();
29
+ return sensitiveKeywords.some(keyword => lower.includes(keyword));
30
+ }
31
+
32
+ function getEnclosingFunctionOrVariableName(node) {
33
+ let current = node;
34
+ while (current) {
35
+ if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
36
+ return current.id?.name || "<anonymous>";
37
+ }
38
+ if (current.type === "VariableDeclarator" && current.id?.type === "Identifier") {
39
+ return current.id.name;
40
+ }
41
+ current = current.parent;
42
+ }
43
+ return "";
44
+ }
45
+
46
+ return {
47
+ CallExpression(node) {
48
+ if (
49
+ node.callee &&
50
+ node.callee.type === "MemberExpression" &&
51
+ node.callee.object.name === "UUID" &&
52
+ node.callee.property.name === "randomUUID"
53
+ ) {
54
+ const contextName = getEnclosingFunctionOrVariableName(node);
55
+ if (containsSensitiveKeyword(contextName)) {
56
+ context.report({
57
+ node,
58
+ messageId: "insecureUuid",
59
+ data: { context: contextName },
60
+ });
61
+ }
62
+ }
63
+ },
64
+ };
65
+ },
66
+ };
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * S012 – Hardcoded Secret
5
+ * OWASP ASVS 1.6.2
6
+ * Ensure that secrets such as passwords, API keys, and cryptographic keys are not hardcoded in source code.
7
+ */
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Do not hardcode any secrets (API keys, passwords, cryptographic keys) in the source code.",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ hardcodedSecret:
20
+ "Do not hardcode secrets (API keys, passwords, cryptographic keys) in source code.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ const secretKeywords = [
26
+ "SECRET",
27
+ "TOKEN",
28
+ "APIKEY",
29
+ "PASSWORD",
30
+ "PRIVATEKEY",
31
+ "JWT_SECRET",
32
+ ];
33
+ return {
34
+ VariableDeclarator(node) {
35
+ if (
36
+ node.id &&
37
+ node.id.type === "Identifier" &&
38
+ secretKeywords.some((kw) => node.id.name.toUpperCase().includes(kw))
39
+ ) {
40
+ if (
41
+ node.init &&
42
+ node.init.type === "Literal" &&
43
+ typeof node.init.value === "string" &&
44
+ node.init.value.length > 0
45
+ ) {
46
+ context.report({
47
+ node: node.init,
48
+ messageId: "hardcodedSecret",
49
+ });
50
+ }
51
+ }
52
+ },
53
+ AssignmentExpression(node) {
54
+ if (
55
+ node.left.type === "Identifier" &&
56
+ secretKeywords.some((kw) =>
57
+ node.left.name.toUpperCase().includes(kw)
58
+ ) &&
59
+ node.right.type === "Literal" &&
60
+ typeof node.right.value === "string" &&
61
+ node.right.value.length > 0
62
+ ) {
63
+ context.report({
64
+ node: node.right,
65
+ messageId: "hardcodedSecret",
66
+ });
67
+ }
68
+ },
69
+ };
70
+ },
71
+ };