@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,254 @@
1
+ /**
2
+ * Custom ESLint rule: S043 – Terminate all sessions on password change
3
+ * Rule ID: custom/s043
4
+ * Purpose: Ensure password change methods terminate all other active sessions
5
+ * OWASP 3.3.3: Verify that the application gives the option to terminate all other active sessions
6
+ * after a successful password change (including change via password reset/recovery)
7
+ */
8
+
9
+ "use strict";
10
+
11
+ module.exports = {
12
+ meta: {
13
+ type: "problem",
14
+ docs: {
15
+ description: "Ensure password change methods terminate all other active sessions and require re-authentication",
16
+ recommended: true,
17
+ },
18
+ schema: [],
19
+ messages: {
20
+ missingSessionTermination: "Password change method '{{method}}' must terminate all other active sessions. Use sessionManager.terminateAllSessions(), tokenService.revokeAllTokens(), or equivalent session cleanup.",
21
+ missingReAuthRequirement: "Password change method '{{method}}' should require re-authentication after successful password change.",
22
+ incompleteImplementation: "Password change method '{{method}}' should both terminate existing sessions and require user re-authentication for security compliance.",
23
+ },
24
+ },
25
+
26
+ create(context) {
27
+ // Keywords that indicate password change functionality
28
+ const passwordChangeKeywords = [
29
+ "password", "passwd", "pwd", "changepassword", "change-password",
30
+ "updatepassword", "update-password", "resetpassword", "reset-password",
31
+ "newpassword", "new-password", "setpassword", "set-password"
32
+ ];
33
+
34
+ // Session termination methods
35
+ const sessionTerminationMethods = [
36
+ "terminateAllSessions", "revokeAllTokens", "invalidateAllSessions",
37
+ "destroyAllSessions", "clearAllSessions", "terminateOtherSessions",
38
+ "revokeOtherTokens", "logoutAllDevices", "signOutAllDevices",
39
+ "terminateAll", "revokeAll", "invalidateAll", "destroyAll"
40
+ ];
41
+
42
+ // Re-authentication requirement methods
43
+ const reAuthMethods = [
44
+ "requireReAuth", "forceReAuth", "requireLogin", "forceLogin",
45
+ "redirectToLogin", "requireAuthentication", "invalidateCurrentSession",
46
+ "logout", "signOut", "clearCurrentSession"
47
+ ];
48
+
49
+ function isPasswordChangeMethod(name) {
50
+ if (!name) return false;
51
+ const lowerName = name.toLowerCase();
52
+ return passwordChangeKeywords.some(keyword => lowerName.includes(keyword));
53
+ }
54
+
55
+ function checkPasswordChangeMethodBody(node, methodName) {
56
+ let hasSessionTermination = false;
57
+ let hasReAuthRequirement = false;
58
+
59
+ function checkNode(n, visited = new Set()) {
60
+ if (!n || visited.has(n)) return;
61
+ visited.add(n);
62
+
63
+ // Check for session termination calls
64
+ if (n.type === "CallExpression") {
65
+ const callee = n.callee;
66
+
67
+ // Direct method calls: terminateAllSessions(), revokeAllTokens()
68
+ if (callee.type === "Identifier") {
69
+ if (sessionTerminationMethods.includes(callee.name)) {
70
+ hasSessionTermination = true;
71
+ }
72
+ if (reAuthMethods.includes(callee.name)) {
73
+ hasReAuthRequirement = true;
74
+ }
75
+ }
76
+
77
+ // Member expressions: sessionManager.terminateAllSessions(), tokenService.revokeAllTokens()
78
+ else if (callee.type === "MemberExpression") {
79
+ const property = callee.property.name;
80
+
81
+ if (sessionTerminationMethods.includes(property)) {
82
+ hasSessionTermination = true;
83
+ }
84
+ if (reAuthMethods.includes(property)) {
85
+ hasReAuthRequirement = true;
86
+ }
87
+
88
+ // Check for patterns like: sessionManager.terminateAll(), authService.revokeAll()
89
+ if (property && (
90
+ property.includes("terminate") || property.includes("revoke") ||
91
+ property.includes("invalidate") || property.includes("destroy") ||
92
+ property.includes("clear")
93
+ )) {
94
+ const methodCall = property.toLowerCase();
95
+ if (methodCall.includes("all") || methodCall.includes("other")) {
96
+ hasSessionTermination = true;
97
+ }
98
+ }
99
+ }
100
+
101
+ // Check function arguments and nested calls
102
+ if (n.arguments) {
103
+ n.arguments.forEach(arg => checkNode(arg, visited));
104
+ }
105
+ }
106
+
107
+ // Check await expressions
108
+ if (n.type === "AwaitExpression") {
109
+ checkNode(n.argument, visited);
110
+ }
111
+
112
+ // Recursively check nested structures
113
+ for (const key in n) {
114
+ if (n[key] && typeof n[key] === "object") {
115
+ if (Array.isArray(n[key])) {
116
+ n[key].forEach(child => checkNode(child, visited));
117
+ } else if (n[key].type) {
118
+ checkNode(n[key], visited);
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ // Analyze method body
125
+ if (node.body) {
126
+ if (node.body.type === "BlockStatement") {
127
+ node.body.body.forEach(stmt => checkNode(stmt));
128
+ } else {
129
+ checkNode(node.body);
130
+ }
131
+ }
132
+
133
+ return { hasSessionTermination, hasReAuthRequirement };
134
+ }
135
+
136
+ return {
137
+ // Check method definitions
138
+ MethodDefinition(node) {
139
+ const methodName = node.key.name;
140
+ if (isPasswordChangeMethod(methodName)) {
141
+ const analysis = checkPasswordChangeMethodBody(node.value, methodName);
142
+
143
+ if (!analysis.hasSessionTermination && !analysis.hasReAuthRequirement) {
144
+ context.report({
145
+ node: node.key,
146
+ messageId: "incompleteImplementation",
147
+ data: { method: methodName }
148
+ });
149
+ } else if (!analysis.hasSessionTermination) {
150
+ context.report({
151
+ node: node.key,
152
+ messageId: "missingSessionTermination",
153
+ data: { method: methodName }
154
+ });
155
+ } else if (!analysis.hasReAuthRequirement) {
156
+ context.report({
157
+ node: node.key,
158
+ messageId: "missingReAuthRequirement",
159
+ data: { method: methodName }
160
+ });
161
+ }
162
+ }
163
+ },
164
+
165
+ // Check function declarations
166
+ FunctionDeclaration(node) {
167
+ const functionName = node.id ? node.id.name : null;
168
+ if (isPasswordChangeMethod(functionName)) {
169
+ const analysis = checkPasswordChangeMethodBody(node, functionName);
170
+
171
+ if (!analysis.hasSessionTermination && !analysis.hasReAuthRequirement) {
172
+ context.report({
173
+ node: node.id,
174
+ messageId: "incompleteImplementation",
175
+ data: { method: functionName }
176
+ });
177
+ } else if (!analysis.hasSessionTermination) {
178
+ context.report({
179
+ node: node.id,
180
+ messageId: "missingSessionTermination",
181
+ data: { method: functionName }
182
+ });
183
+ } else if (!analysis.hasReAuthRequirement) {
184
+ context.report({
185
+ node: node.id,
186
+ messageId: "missingReAuthRequirement",
187
+ data: { method: functionName }
188
+ });
189
+ }
190
+ }
191
+ },
192
+
193
+ // Check function expressions and arrow functions assigned to variables
194
+ VariableDeclarator(node) {
195
+ if (node.init && (node.init.type === "FunctionExpression" || node.init.type === "ArrowFunctionExpression")) {
196
+ const varName = node.id.name;
197
+ if (isPasswordChangeMethod(varName)) {
198
+ const analysis = checkPasswordChangeMethodBody(node.init, varName);
199
+
200
+ if (!analysis.hasSessionTermination && !analysis.hasReAuthRequirement) {
201
+ context.report({
202
+ node: node.id,
203
+ messageId: "incompleteImplementation",
204
+ data: { method: varName }
205
+ });
206
+ } else if (!analysis.hasSessionTermination) {
207
+ context.report({
208
+ node: node.id,
209
+ messageId: "missingSessionTermination",
210
+ data: { method: varName }
211
+ });
212
+ } else if (!analysis.hasReAuthRequirement) {
213
+ context.report({
214
+ node: node.id,
215
+ messageId: "missingReAuthRequirement",
216
+ data: { method: varName }
217
+ });
218
+ }
219
+ }
220
+ }
221
+ },
222
+
223
+ // Check property assignments (for object methods)
224
+ Property(node) {
225
+ if (node.value && (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression")) {
226
+ const propName = node.key.name;
227
+ if (isPasswordChangeMethod(propName)) {
228
+ const analysis = checkPasswordChangeMethodBody(node.value, propName);
229
+
230
+ if (!analysis.hasSessionTermination && !analysis.hasReAuthRequirement) {
231
+ context.report({
232
+ node: node.key,
233
+ messageId: "incompleteImplementation",
234
+ data: { method: propName }
235
+ });
236
+ } else if (!analysis.hasSessionTermination) {
237
+ context.report({
238
+ node: node.key,
239
+ messageId: "missingSessionTermination",
240
+ data: { method: propName }
241
+ });
242
+ } else if (!analysis.hasReAuthRequirement) {
243
+ context.report({
244
+ node: node.key,
245
+ messageId: "missingReAuthRequirement",
246
+ data: { method: propName }
247
+ });
248
+ }
249
+ }
250
+ }
251
+ },
252
+ };
253
+ },
254
+ };
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Custom ESLint rule: S044 – Require full session for sensitive operations
3
+ * Rule ID: custom/s044
4
+ * Purpose: Verify the application ensures a full, valid login session or requires re-authentication
5
+ * or secondary verification before allowing any sensitive transactions or account modifications
6
+ * OWASP 3.7.1: Verify the application ensures a full, valid login session or requires re-authentication
7
+ * or secondary verification before allowing any sensitive transactions or account modifications
8
+ */
9
+
10
+ "use strict";
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: "problem",
15
+ docs: {
16
+ description: "Require full session validation or re-authentication before sensitive operations",
17
+ recommended: true,
18
+ },
19
+ schema: [],
20
+ messages: {
21
+ missingSensitiveOperationProtection: "Sensitive operation '{{method}}' requires full session validation or re-authentication. Use sessionManager.validateFullSession(), requireReAuth(), or 2FA verification.",
22
+ incompleteSessionValidation: "Method '{{method}}' should validate complete session state before allowing sensitive operations.",
23
+ missingSecondaryVerification: "Sensitive operation '{{method}}' should require secondary verification (2FA, password confirmation) for enhanced security.",
24
+ halfOpenSessionAccess: "Method '{{method}}' may allow access with incomplete session. Ensure full session validation before sensitive operations.",
25
+ },
26
+ },
27
+
28
+ create(context) {
29
+ // Keywords that indicate sensitive operations
30
+ const sensitiveOperationKeywords = [
31
+ // Account modifications
32
+ "password", "passwd", "pwd", "changepassword", "updatepassword", "resetpassword",
33
+ "email", "changeemail", "updateemail", "setemail",
34
+ "profile", "updateprofile", "changeprofile", "editprofile",
35
+ "account", "updateaccount", "changeaccount", "deleteaccount",
36
+ "user", "updateuser", "changeuser", "deleteuser",
37
+
38
+ // Financial/Transaction operations
39
+ "payment", "pay", "transfer", "transaction", "withdraw", "deposit",
40
+ "purchase", "buy", "order", "checkout", "billing",
41
+ "balance", "fund", "money", "amount", "financial",
42
+
43
+ // Security operations
44
+ "permission", "role", "access", "privilege", "authorization",
45
+ "security", "admin", "superuser", "root",
46
+ "settings", "config", "configuration", "sensitive",
47
+
48
+ // Data operations
49
+ "delete", "remove", "destroy", "drop", "truncate",
50
+ "export", "import", "backup", "restore", "migration"
51
+ ];
52
+
53
+ // Session validation methods
54
+ const sessionValidationMethods = [
55
+ "validateFullSession", "checkFullSession", "ensureFullSession",
56
+ "validateSession", "checkSession", "ensureSession",
57
+ "isFullyAuthenticated", "hasFullSession", "isSessionValid",
58
+ "validateAuthState", "checkAuthState", "ensureAuthState",
59
+ "verifySession", "confirmSession", "validateUser"
60
+ ];
61
+
62
+ // Re-authentication methods
63
+ const reAuthMethods = [
64
+ "requireReAuth", "forceReAuth", "requireAuthentication",
65
+ "requestReAuth", "validateReAuth", "checkReAuth",
66
+ "confirmPassword", "verifyPassword", "validatePassword",
67
+ "requireLogin", "forceLogin", "redirectToLogin"
68
+ ];
69
+
70
+ // Secondary verification methods (2FA, etc.)
71
+ const secondaryVerificationMethods = [
72
+ "require2FA", "requireTwoFactor", "verifyTwoFactor",
73
+ "requireOTP", "verifyOTP", "validateOTP",
74
+ "requireMFA", "verifyMFA", "validateMFA",
75
+ "requireSMS", "verifySMS", "validateSMS",
76
+ "requireEmail", "verifyEmail", "validateEmail",
77
+ "requireSecondaryAuth", "verifySecondaryAuth"
78
+ ];
79
+
80
+ // Patterns indicating half-open or incomplete sessions
81
+ const halfOpenSessionPatterns = [
82
+ "partial", "incomplete", "temp", "temporary", "pending",
83
+ "halfopen", "half-open", "inprogress", "in-progress"
84
+ ];
85
+
86
+ function isSensitiveOperation(name) {
87
+ if (!name) return false;
88
+ const lowerName = name.toLowerCase();
89
+ return sensitiveOperationKeywords.some(keyword =>
90
+ lowerName.includes(keyword) ||
91
+ name.toLowerCase().includes(keyword)
92
+ );
93
+ }
94
+
95
+ function checkSensitiveOperationMethodBody(node, methodName) {
96
+ let hasSessionValidation = false;
97
+ let hasReAuthentication = false;
98
+ let hasSecondaryVerification = false;
99
+ let hasHalfOpenSessionCheck = false;
100
+
101
+ function analyzeNode(n, visited = new Set()) {
102
+ if (!n || visited.has(n)) return;
103
+ visited.add(n);
104
+
105
+ // Check for method calls
106
+ if (n.type === "CallExpression") {
107
+ const callee = n.callee;
108
+
109
+ // Direct method calls
110
+ if (callee.type === "Identifier") {
111
+ const methodName = callee.name;
112
+
113
+ if (sessionValidationMethods.includes(methodName)) {
114
+ hasSessionValidation = true;
115
+ }
116
+ if (reAuthMethods.includes(methodName)) {
117
+ hasReAuthentication = true;
118
+ }
119
+ if (secondaryVerificationMethods.includes(methodName)) {
120
+ hasSecondaryVerification = true;
121
+ }
122
+ }
123
+
124
+ // Member expressions: sessionManager.validateFullSession()
125
+ else if (callee.type === "MemberExpression") {
126
+ const property = callee.property.name;
127
+
128
+ if (sessionValidationMethods.includes(property)) {
129
+ hasSessionValidation = true;
130
+ }
131
+ if (reAuthMethods.includes(property)) {
132
+ hasReAuthentication = true;
133
+ }
134
+ if (secondaryVerificationMethods.includes(property)) {
135
+ hasSecondaryVerification = true;
136
+ }
137
+
138
+ // Check for session validation patterns
139
+ if (property && (
140
+ property.includes("validate") || property.includes("check") ||
141
+ property.includes("verify") || property.includes("ensure")
142
+ )) {
143
+ const methodCall = property.toLowerCase();
144
+ if (methodCall.includes("session") || methodCall.includes("auth")) {
145
+ hasSessionValidation = true;
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // Check for conditional statements that might validate session
152
+ if (n.type === "IfStatement") {
153
+ const test = n.test;
154
+ if (test && test.type === "CallExpression") {
155
+ const callee = test.callee;
156
+
157
+ // Check for session validation in if conditions
158
+ if (callee.type === "MemberExpression") {
159
+ const property = callee.property.name;
160
+ if (property && (
161
+ property.includes("isAuthenticated") ||
162
+ property.includes("hasSession") ||
163
+ property.includes("isValid")
164
+ )) {
165
+ hasSessionValidation = true;
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // Check for guard clauses
172
+ if (n.type === "ThrowStatement" || n.type === "ReturnStatement") {
173
+ // Look for early returns/throws that might be session guards
174
+ if (n.argument && n.argument.type === "CallExpression") {
175
+ const callee = n.argument.callee;
176
+ if (callee.type === "Identifier" && callee.name === "Error") {
177
+ hasSessionValidation = true; // Assume error throwing is session validation
178
+ }
179
+ }
180
+ }
181
+
182
+ // Recursively check child nodes
183
+ for (const key in n) {
184
+ if (key === "parent" || key === "range" || key === "loc") continue;
185
+ const child = n[key];
186
+
187
+ if (Array.isArray(child)) {
188
+ child.forEach(item => {
189
+ if (item && typeof item === "object") {
190
+ analyzeNode(item, visited);
191
+ }
192
+ });
193
+ } else if (child && typeof child === "object") {
194
+ analyzeNode(child, visited);
195
+ }
196
+ }
197
+ }
198
+
199
+ // Analyze the method body
200
+ if (node.body) {
201
+ analyzeNode(node.body);
202
+ }
203
+
204
+ return {
205
+ hasSessionValidation,
206
+ hasReAuthentication,
207
+ hasSecondaryVerification,
208
+ hasHalfOpenSessionCheck
209
+ };
210
+ }
211
+
212
+ function checkMethodDeclaration(node) {
213
+ if (!node.key || !node.key.name) return;
214
+
215
+ const methodName = node.key.name;
216
+
217
+ if (isSensitiveOperation(methodName)) {
218
+ const analysis = checkSensitiveOperationMethodBody(node, methodName);
219
+
220
+ // Report if no session validation found
221
+ if (!analysis.hasSessionValidation && !analysis.hasReAuthentication) {
222
+ context.report({
223
+ node: node.key,
224
+ messageId: "missingSensitiveOperationProtection",
225
+ data: { method: methodName }
226
+ });
227
+ }
228
+ // Report if only partial validation found
229
+ else if (analysis.hasSessionValidation && !analysis.hasReAuthentication && !analysis.hasSecondaryVerification) {
230
+ context.report({
231
+ node: node.key,
232
+ messageId: "incompleteSessionValidation",
233
+ data: { method: methodName }
234
+ });
235
+ }
236
+ }
237
+ }
238
+
239
+ function checkFunctionDeclaration(node) {
240
+ if (!node.id || !node.id.name) return;
241
+
242
+ const functionName = node.id.name;
243
+
244
+ if (isSensitiveOperation(functionName)) {
245
+ const analysis = checkSensitiveOperationMethodBody(node, functionName);
246
+
247
+ if (!analysis.hasSessionValidation && !analysis.hasReAuthentication) {
248
+ context.report({
249
+ node: node.id,
250
+ messageId: "missingSensitiveOperationProtection",
251
+ data: { method: functionName }
252
+ });
253
+ }
254
+ else if (analysis.hasSessionValidation && !analysis.hasReAuthentication && !analysis.hasSecondaryVerification) {
255
+ context.report({
256
+ node: node.id,
257
+ messageId: "incompleteSessionValidation",
258
+ data: { method: functionName }
259
+ });
260
+ }
261
+ }
262
+ }
263
+
264
+ function checkArrowFunction(node) {
265
+ // Check if arrow function is assigned to a variable with sensitive name
266
+ const parent = node.parent;
267
+ if (parent && parent.type === "VariableDeclarator" && parent.id && parent.id.name) {
268
+ const functionName = parent.id.name;
269
+
270
+ if (isSensitiveOperation(functionName)) {
271
+ const analysis = checkSensitiveOperationMethodBody(node, functionName);
272
+
273
+ if (!analysis.hasSessionValidation && !analysis.hasReAuthentication) {
274
+ context.report({
275
+ node: parent.id,
276
+ messageId: "missingSensitiveOperationProtection",
277
+ data: { method: functionName }
278
+ });
279
+ }
280
+ }
281
+ }
282
+ }
283
+
284
+ return {
285
+ MethodDefinition: checkMethodDeclaration,
286
+ Property: checkMethodDeclaration, // For object method properties
287
+ FunctionDeclaration: checkFunctionDeclaration,
288
+ ArrowFunctionExpression: checkArrowFunction,
289
+ FunctionExpression: checkArrowFunction
290
+ };
291
+ }
292
+ };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * S045 – Anti Automation Controls
5
+ * OWASP ASVS 2.2.1
6
+ * Ensure that anti-automation controls are in place to mitigate brute force and automated attacks.
7
+ */
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Ensure anti-automation controls are in place (rate limiting, CAPTCHA, account lockout, etc.).",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ noAntiAutomation:
20
+ "Missing or ineffective anti-automation controls. Consider rate limiting, CAPTCHA, or account lockout.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ CallExpression(node) {
27
+ // Check for missing rate limiter middleware in Express apps (demo only)
28
+ if (
29
+ node.callee.type === "Identifier" &&
30
+ node.callee.name === "app" &&
31
+ node.parent &&
32
+ node.parent.type === "ExpressionStatement" &&
33
+ node.arguments.length &&
34
+ node.arguments[0].type === "Literal" &&
35
+ node.arguments[0].value === "/login"
36
+ ) {
37
+ // This is a simplified check; real implementation should be more robust
38
+ context.report({
39
+ node,
40
+ messageId: "noAntiAutomation",
41
+ });
42
+ }
43
+ },
44
+ };
45
+ },
46
+ };
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * S046 – Secure Notification On Auth Change
5
+ * OWASP ASVS 2.2.3
6
+ * Ensure that users are securely notified after authentication changes.
7
+ */
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Ensure secure notification is sent on authentication changes (password reset, email/phone change, new device login, etc.).",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ missingNotification:
20
+ "Missing secure notification after authentication change. Notify users via secure channel when credentials or auth details change.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ CallExpression(node) {
27
+ // Example: after password change, should call sendNotification/sendMail/sendPushNotification
28
+ if (
29
+ node.callee.type === "Identifier" &&
30
+ (node.callee.name === "resetPassword" ||
31
+ node.callee.name === "changeEmail") &&
32
+ node.parent &&
33
+ node.parent.type === "ExpressionStatement"
34
+ ) {
35
+ // This is a simplified check; real implementation should be more robust
36
+ context.report({
37
+ node,
38
+ messageId: "missingNotification",
39
+ });
40
+ }
41
+ },
42
+ };
43
+ },
44
+ };
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * S048 – Password Credential Recovery
5
+ * OWASP ASVS 2.4.3
6
+ * Ensure password credential recovery does not reveal the current password in any way.
7
+ */
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Ensure password recovery does not reveal the current password. Users must set a new password via a secure, one-time token.",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ revealPassword:
20
+ "Never reveal or send the current password during recovery. Always require user to set a new password.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ return {
26
+ CallExpression(node) {
27
+ // Example: sending current password in email (should not happen)
28
+ if (
29
+ node.callee.type === "Identifier" &&
30
+ node.callee.name === "sendMail" &&
31
+ node.arguments.length &&
32
+ node.arguments[0].type === "ObjectExpression"
33
+ ) {
34
+ const bodyProp = node.arguments[0].properties.find(
35
+ (prop) =>
36
+ prop.key &&
37
+ (prop.key.name === "text" || prop.key.name === "html") &&
38
+ prop.value.type === "Literal" &&
39
+ (prop.value.value
40
+ .toLowerCase()
41
+ .includes("your current password") ||
42
+ prop.value.value.toLowerCase().includes("mật khẩu hiện tại"))
43
+ );
44
+ if (bodyProp) {
45
+ context.report({
46
+ node: bodyProp.value,
47
+ messageId: "revealPassword",
48
+ });
49
+ }
50
+ }
51
+ },
52
+ };
53
+ },
54
+ };