@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,211 @@
1
+ /**
2
+ * Custom ESLint rule: S041 – Require session invalidation on logout
3
+ * Rule ID: custom/s041
4
+ * Purpose: Ensure logout handlers properly invalidate session tokens and clear cookies
5
+ * OWASP 3.3.1: Verify that logout and expiration invalidate the session token
6
+ */
7
+
8
+ "use strict";
9
+
10
+ module.exports = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Ensure logout handlers properly invalidate session tokens and prevent session reuse",
15
+ recommended: true,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ missingSessionInvalidation: "Logout method '{{method}}' must invalidate session token. Use session.invalidate(), req.session.destroy(), or equivalent session cleanup.",
20
+ missingCookieClear: "Logout method '{{method}}' should clear authentication cookies to prevent session reuse.",
21
+ missingCacheControl: "Logout method '{{method}}' should set cache-control headers to prevent back button authentication.",
22
+ },
23
+ },
24
+
25
+ create(context) {
26
+ // Keywords that indicate logout functionality
27
+ const logoutKeywords = [
28
+ "logout", "signout", "sign-out", "logoff", "signoff",
29
+ "disconnect", "terminate", "exit", "end-session"
30
+ ];
31
+
32
+ // Session invalidation methods
33
+ const sessionInvalidationMethods = [
34
+ "invalidate", "destroy", "remove", "clear", "delete",
35
+ "expire", "revoke", "blacklist"
36
+ ];
37
+
38
+ // Cookie clearing methods
39
+ const cookieClearMethods = [
40
+ "clearCookie", "removeCookie", "deleteCookie", "expireCookie"
41
+ ];
42
+
43
+ // Cache control methods
44
+ const cacheControlMethods = [
45
+ "setHeader", "header", "set", "no-cache", "no-store"
46
+ ];
47
+
48
+ function isLogoutMethod(name) {
49
+ if (!name) return false;
50
+ const lowerName = name.toLowerCase();
51
+ return logoutKeywords.some(keyword => lowerName.includes(keyword));
52
+ }
53
+
54
+ function checkLogoutMethodBody(node, methodName) {
55
+ let hasSessionInvalidation = false;
56
+ let hasCookieClearing = false;
57
+ let hasCacheControl = 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 invalidation
64
+ if (n.type === "CallExpression") {
65
+ const callee = n.callee;
66
+
67
+ // session.invalidate(), req.session.destroy(), etc.
68
+ if (callee.type === "MemberExpression") {
69
+ const property = callee.property.name;
70
+ const object = callee.object;
71
+
72
+ if (sessionInvalidationMethods.includes(property)) {
73
+ // Check if it's session-related: session.invalidate(), req.session.destroy()
74
+ if (object.type === "Identifier" && object.name === "session") {
75
+ hasSessionInvalidation = true;
76
+ } else if (object.type === "MemberExpression" &&
77
+ object.property && object.property.name === "session") {
78
+ hasSessionInvalidation = true;
79
+ }
80
+ }
81
+
82
+ // Check for cookie clearing: res.clearCookie()
83
+ if (cookieClearMethods.includes(property)) {
84
+ hasCookieClearing = true;
85
+ }
86
+
87
+ // Check for cache control headers
88
+ if (cacheControlMethods.includes(property)) {
89
+ const args = n.arguments;
90
+ if (args.length > 0) {
91
+ const firstArg = args[0];
92
+ if (firstArg.type === "Literal") {
93
+ const header = firstArg.value;
94
+ if (typeof header === "string" &&
95
+ (header.toLowerCase().includes("cache-control") ||
96
+ header.toLowerCase().includes("pragma"))) {
97
+ hasCacheControl = true;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ // Recursively check specific node types to avoid infinite loops
106
+ const nodeTypesToCheck = [
107
+ 'BlockStatement', 'ExpressionStatement', 'CallExpression',
108
+ 'MemberExpression', 'ArrowFunctionExpression', 'FunctionExpression'
109
+ ];
110
+
111
+ for (const key in n) {
112
+ if (n[key] && typeof n[key] === "object" && key !== 'parent') {
113
+ if (Array.isArray(n[key])) {
114
+ n[key].forEach(child => {
115
+ if (child && child.type && nodeTypesToCheck.includes(child.type)) {
116
+ checkNode(child, visited);
117
+ }
118
+ });
119
+ } else if (n[key].type && nodeTypesToCheck.includes(n[key].type)) {
120
+ checkNode(n[key], visited);
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ // Check method body
127
+ if (node.body) {
128
+ checkNode(node.body);
129
+ }
130
+
131
+ // Report missing requirements
132
+ if (!hasSessionInvalidation) {
133
+ context.report({
134
+ node,
135
+ messageId: "missingSessionInvalidation",
136
+ data: { method: methodName }
137
+ });
138
+ }
139
+
140
+ if (!hasCookieClearing) {
141
+ context.report({
142
+ node,
143
+ messageId: "missingCookieClear",
144
+ data: { method: methodName }
145
+ });
146
+ }
147
+
148
+ if (!hasCacheControl) {
149
+ context.report({
150
+ node,
151
+ messageId: "missingCacheControl",
152
+ data: { method: methodName }
153
+ });
154
+ }
155
+ }
156
+
157
+ return {
158
+ // Check class methods (NestJS controllers)
159
+ MethodDefinition(node) {
160
+ const methodName = node.key.name;
161
+ if (isLogoutMethod(methodName)) {
162
+ checkLogoutMethodBody(node.value, methodName);
163
+ }
164
+ },
165
+
166
+ // Check function declarations
167
+ FunctionDeclaration(node) {
168
+ const functionName = node.id?.name;
169
+ if (isLogoutMethod(functionName)) {
170
+ checkLogoutMethodBody(node, functionName);
171
+ }
172
+ },
173
+
174
+ // Check arrow functions and function expressions assigned to variables
175
+ VariableDeclarator(node) {
176
+ if (node.id.type === "Identifier" && node.init) {
177
+ const varName = node.id.name;
178
+ if (isLogoutMethod(varName)) {
179
+ if (node.init.type === "ArrowFunctionExpression" ||
180
+ node.init.type === "FunctionExpression") {
181
+ checkLogoutMethodBody(node.init, varName);
182
+ }
183
+ }
184
+ }
185
+ },
186
+
187
+ // Check route handlers with logout paths
188
+ CallExpression(node) {
189
+ const callee = node.callee;
190
+
191
+ // Express/NestJS route: app.post('/logout', handler)
192
+ if (callee.type === "MemberExpression" &&
193
+ ["post", "get", "put", "delete"].includes(callee.property.name) &&
194
+ node.arguments.length >= 2) {
195
+
196
+ const pathArg = node.arguments[0];
197
+ if (pathArg.type === "Literal" && typeof pathArg.value === "string") {
198
+ const path = pathArg.value.toLowerCase();
199
+ if (logoutKeywords.some(keyword => path.includes(keyword))) {
200
+ const handler = node.arguments[node.arguments.length - 1];
201
+ if (handler.type === "ArrowFunctionExpression" ||
202
+ handler.type === "FunctionExpression") {
203
+ checkLogoutMethodBody(handler, `route handler for ${pathArg.value}`);
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ };
210
+ },
211
+ };
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Custom ESLint rule: S042 – Require periodic re-authentication
3
+ * Rule ID: custom/s042
4
+ * Purpose: Verify that if authenticators permit users to remain logged in,
5
+ * re-authentication occurs periodically both when actively used or after an idle period
6
+ * OWASP 3.3.2: If authenticators permit users to remain logged in, verify that
7
+ * re-authentication occurs periodically both when actively used or after an idle period
8
+ */
9
+
10
+ "use strict";
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: "problem",
15
+ docs: {
16
+ description: "Ensure periodic re-authentication is implemented for long-lived sessions",
17
+ recommended: true,
18
+ },
19
+ schema: [],
20
+ messages: {
21
+ missingReauthentication: "Authentication method '{{method}}' should implement periodic re-authentication for long-lived sessions.",
22
+ missingIdleTimeout: "Session configuration should include idle timeout mechanism for automatic logout.",
23
+ missingActiveTimeout: "Session configuration should include maximum active session duration (e.g., 12 hours).",
24
+ missing2FAForSensitive: "Sensitive operations should require two-factor authentication (2FA) regardless of session state.",
25
+ missingReauthForSensitive: "Sensitive operations should require re-authentication even for active sessions.",
26
+ },
27
+ },
28
+
29
+ create(context) {
30
+ // Keywords that indicate authentication/session functionality
31
+ const authKeywords = [
32
+ "auth", "login", "signin", "sign-in", "authenticate", "session",
33
+ "passport", "jwt", "token", "guard", "middleware"
34
+ ];
35
+
36
+ // Session configuration keywords
37
+ const sessionConfigKeywords = [
38
+ "session", "maxage", "expires", "timeout", "idle", "duration",
39
+ "lifetime", "ttl", "expiry"
40
+ ];
41
+
42
+ // Sensitive operation keywords
43
+ const sensitiveOperationKeywords = [
44
+ "payment", "transfer", "withdraw", "deposit", "transaction",
45
+ "delete", "remove", "destroy", "admin", "privilege", "role",
46
+ "password", "email", "profile", "settings", "config"
47
+ ];
48
+
49
+ // Two-factor authentication keywords
50
+ const tfaKeywords = ["2fa", "mfa", "totp", "otp", "authenticator"];
51
+
52
+ // Re-authentication keywords
53
+ const reauthKeywords = ["reauth", "re-auth", "verify", "confirm"];
54
+
55
+ function isAuthenticationRelated(name) {
56
+ if (!name) return false;
57
+ const lowerName = name.toLowerCase();
58
+ return authKeywords.some(keyword => lowerName.includes(keyword));
59
+ }
60
+
61
+ function isSensitiveOperation(name) {
62
+ if (!name) return false;
63
+ const lowerName = name.toLowerCase();
64
+ return sensitiveOperationKeywords.some(keyword => lowerName.includes(keyword));
65
+ }
66
+
67
+ function hasSessionConfiguration(node) {
68
+ let hasIdleTimeout = false;
69
+ let hasMaxAge = false;
70
+
71
+ function checkConfigObject(obj) {
72
+ if (obj.type === "ObjectExpression") {
73
+ obj.properties.forEach(prop => {
74
+ if (prop.key && prop.key.name) {
75
+ const keyName = prop.key.name.toLowerCase();
76
+ if (keyName.includes("idle") || keyName.includes("timeout")) {
77
+ hasIdleTimeout = true;
78
+ }
79
+ if (keyName.includes("maxage") || keyName.includes("expires") ||
80
+ keyName.includes("duration") || keyName.includes("lifetime")) {
81
+ hasMaxAge = true;
82
+ }
83
+ }
84
+ });
85
+ }
86
+ }
87
+
88
+ // Check for session configuration in various patterns
89
+ if (node.type === "CallExpression") {
90
+ node.arguments.forEach(arg => {
91
+ checkConfigObject(arg);
92
+ });
93
+ }
94
+
95
+ return { hasIdleTimeout, hasMaxAge };
96
+ }
97
+
98
+ function hasReauthenticationLogic(node, visited = new Set()) {
99
+ if (!node || visited.has(node)) return { hasReauth: false, has2FA: false };
100
+ visited.add(node);
101
+
102
+ let hasReauth = false;
103
+ let has2FA = false;
104
+
105
+ function checkNode(n) {
106
+ if (!n || visited.has(n)) return;
107
+ visited.add(n);
108
+
109
+ // Check for re-authentication calls
110
+ if (n.type === "CallExpression" && n.callee && n.callee.type === "MemberExpression") {
111
+ const methodName = n.callee.property && n.callee.property.name;
112
+ if (methodName && reauthKeywords.some(keyword =>
113
+ methodName.toLowerCase().includes(keyword))) {
114
+ hasReauth = true;
115
+ }
116
+ }
117
+
118
+ // Check for 2FA implementation
119
+ if (n.type === "CallExpression") {
120
+ const callText = context.getSourceCode().getText(n).toLowerCase();
121
+ if (tfaKeywords.some(keyword => callText.includes(keyword))) {
122
+ has2FA = true;
123
+ }
124
+ }
125
+
126
+ // Check identifier names
127
+ if (n.type === "Identifier") {
128
+ const name = n.name.toLowerCase();
129
+ if (reauthKeywords.some(keyword => name.includes(keyword))) {
130
+ hasReauth = true;
131
+ }
132
+ if (tfaKeywords.some(keyword => name.includes(keyword))) {
133
+ has2FA = true;
134
+ }
135
+ }
136
+
137
+ // Only check direct children to avoid deep recursion
138
+ if (n.type === "BlockStatement" && n.body) {
139
+ n.body.forEach(stmt => checkNode(stmt));
140
+ } else if (n.type === "ExpressionStatement" && n.expression) {
141
+ checkNode(n.expression);
142
+ }
143
+ }
144
+
145
+ checkNode(node);
146
+ return { hasReauth, has2FA };
147
+ }
148
+
149
+ return {
150
+ // Check class methods (NestJS controllers/guards)
151
+ MethodDefinition(node) {
152
+ const methodName = node.key.name;
153
+
154
+ if (isAuthenticationRelated(methodName)) {
155
+ const { hasIdleTimeout, hasMaxAge } = hasSessionConfiguration(node);
156
+
157
+ if (!hasIdleTimeout) {
158
+ context.report({
159
+ node,
160
+ messageId: "missingIdleTimeout",
161
+ });
162
+ }
163
+
164
+ if (!hasMaxAge) {
165
+ context.report({
166
+ node,
167
+ messageId: "missingActiveTimeout",
168
+ });
169
+ }
170
+ }
171
+
172
+ if (isSensitiveOperation(methodName)) {
173
+ const { hasReauth, has2FA } = hasReauthenticationLogic(node.value);
174
+
175
+ if (!hasReauth) {
176
+ context.report({
177
+ node,
178
+ messageId: "missingReauthForSensitive",
179
+ });
180
+ }
181
+
182
+ if (!has2FA) {
183
+ context.report({
184
+ node,
185
+ messageId: "missing2FAForSensitive",
186
+ });
187
+ }
188
+ }
189
+ },
190
+
191
+ // Check function declarations
192
+ FunctionDeclaration(node) {
193
+ const functionName = node.id ? node.id.name : null;
194
+
195
+ if (isAuthenticationRelated(functionName)) {
196
+ const { hasIdleTimeout, hasMaxAge } = hasSessionConfiguration(node);
197
+
198
+ if (!hasIdleTimeout) {
199
+ context.report({
200
+ node,
201
+ messageId: "missingIdleTimeout",
202
+ });
203
+ }
204
+
205
+ if (!hasMaxAge) {
206
+ context.report({
207
+ node,
208
+ messageId: "missingActiveTimeout",
209
+ });
210
+ }
211
+ }
212
+
213
+ if (isSensitiveOperation(functionName)) {
214
+ const { hasReauth, has2FA } = hasReauthenticationLogic(node);
215
+
216
+ if (!hasReauth) {
217
+ context.report({
218
+ node,
219
+ messageId: "missingReauthForSensitive",
220
+ });
221
+ }
222
+ }
223
+ },
224
+
225
+ // Check arrow functions assigned to variables
226
+ VariableDeclarator(node) {
227
+ if (node.id.type === "Identifier" && node.init) {
228
+ const varName = node.id.name;
229
+
230
+ if (isAuthenticationRelated(varName) &&
231
+ (node.init.type === "ArrowFunctionExpression" ||
232
+ node.init.type === "FunctionExpression")) {
233
+
234
+ const { hasIdleTimeout, hasMaxAge } = hasSessionConfiguration(node.init);
235
+
236
+ if (!hasIdleTimeout) {
237
+ context.report({
238
+ node,
239
+ messageId: "missingIdleTimeout",
240
+ });
241
+ }
242
+
243
+ if (!hasMaxAge) {
244
+ context.report({
245
+ node,
246
+ messageId: "missingActiveTimeout",
247
+ });
248
+ }
249
+ }
250
+
251
+ if (isSensitiveOperation(varName) &&
252
+ (node.init.type === "ArrowFunctionExpression" ||
253
+ node.init.type === "FunctionExpression")) {
254
+
255
+ const { hasReauth, has2FA } = hasReauthenticationLogic(node.init);
256
+
257
+ if (!hasReauth) {
258
+ context.report({
259
+ node,
260
+ messageId: "missingReauthForSensitive",
261
+ });
262
+ }
263
+ }
264
+ }
265
+ },
266
+
267
+ // Check session configuration calls
268
+ CallExpression(node) {
269
+ const sourceCode = context.getSourceCode().getText(node).toLowerCase();
270
+
271
+ // Check for session middleware configuration
272
+ if (sourceCode.includes("session") &&
273
+ (sourceCode.includes("express") || sourceCode.includes("app.use"))) {
274
+
275
+ const { hasIdleTimeout, hasMaxAge } = hasSessionConfiguration(node);
276
+
277
+ if (!hasIdleTimeout) {
278
+ context.report({
279
+ node,
280
+ messageId: "missingIdleTimeout",
281
+ });
282
+ }
283
+
284
+ if (!hasMaxAge) {
285
+ context.report({
286
+ node,
287
+ messageId: "missingActiveTimeout",
288
+ });
289
+ }
290
+ }
291
+ }
292
+ };
293
+ },
294
+ };