@sun-asterisk/sunlint 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +108 -169
  2. package/README.md +7 -1
  3. package/config/presets/beginner.json +1 -1
  4. package/config/presets/ci.json +3 -2
  5. package/config/presets/recommended.json +1 -1
  6. package/config/presets/strict.json +3 -2
  7. package/config/rules-registry.json +60 -0
  8. package/config/sunlint-schema.json +0 -7
  9. package/config/typescript/eslint.config.js +4 -0
  10. package/core/config-manager.js +9 -8
  11. package/core/config-merger.js +12 -0
  12. package/core/file-targeting-service.js +1 -6
  13. package/core/rule-mapping-service.js +8 -0
  14. package/package.json +2 -5
  15. package/cli-legacy.js +0 -355
  16. package/docs/AI.md +0 -163
  17. package/docs/ARCHITECTURE.md +0 -78
  18. package/docs/CI-CD-GUIDE.md +0 -315
  19. package/docs/COMMAND-EXAMPLES.md +0 -256
  20. package/docs/DEBUG.md +0 -86
  21. package/docs/DISTRIBUTION.md +0 -153
  22. package/docs/ENHANCED_FILE_TARGETING.md +0 -0
  23. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  24. package/docs/ESLINT_INTEGRATION.md +0 -238
  25. package/docs/FILE_TARGETING_COMPARISON.md +0 -0
  26. package/docs/FOLDER_STRUCTURE.md +0 -59
  27. package/docs/HEURISTIC_VS_AI.md +0 -113
  28. package/docs/README.md +0 -32
  29. package/docs/RELEASE_GUIDE.md +0 -230
  30. package/docs/RULE-RESPONSIBILITY-MATRIX.md +0 -204
  31. package/eslint-integration/.eslintrc.js +0 -98
  32. package/eslint-integration/cli.js +0 -35
  33. package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +0 -204
  34. package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +0 -246
  35. package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +0 -207
  36. package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +0 -90
  37. package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +0 -43
  38. package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +0 -38
  39. package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +0 -39
  40. package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +0 -335
  41. package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +0 -142
  42. package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +0 -50
  43. package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +0 -80
  44. package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +0 -294
  45. package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +0 -34
  46. package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +0 -32
  47. package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +0 -64
  48. package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +0 -406
  49. package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +0 -300
  50. package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +0 -239
  51. package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +0 -31
  52. package/eslint-integration/eslint-plugin-custom/index.js +0 -155
  53. package/eslint-integration/eslint-plugin-custom/package.json +0 -13
  54. package/eslint-integration/eslint-plugin-custom/package.json.bak +0 -9
  55. package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +0 -86
  56. package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +0 -95
  57. package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +0 -69
  58. package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +0 -62
  59. package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +0 -103
  60. package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +0 -123
  61. package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +0 -66
  62. package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +0 -71
  63. package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +0 -50
  64. package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +0 -43
  65. package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +0 -59
  66. package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +0 -193
  67. package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +0 -56
  68. package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +0 -113
  69. package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +0 -89
  70. package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +0 -78
  71. package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +0 -300
  72. package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +0 -217
  73. package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +0 -68
  74. package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +0 -80
  75. package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +0 -79
  76. package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +0 -78
  77. package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +0 -80
  78. package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +0 -77
  79. package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +0 -74
  80. package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +0 -68
  81. package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +0 -70
  82. package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +0 -74
  83. package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +0 -63
  84. package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +0 -211
  85. package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +0 -294
  86. package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +0 -254
  87. package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +0 -292
  88. package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +0 -46
  89. package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +0 -44
  90. package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +0 -108
  91. package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +0 -54
  92. package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +0 -94
  93. package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +0 -66
  94. package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +0 -109
  95. package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +0 -143
  96. package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +0 -54
  97. package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +0 -73
  98. package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +0 -42
  99. package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +0 -48
  100. package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +0 -160
  101. package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +0 -52
  102. package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +0 -175
  103. package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +0 -95
  104. package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +0 -48
  105. package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +0 -377
  106. package/eslint-integration/eslint.config.js +0 -125
  107. package/eslint-integration/eslint.config.simple.js +0 -24
  108. package/eslint-integration/package.json +0 -23
  109. package/eslint-integration/sample.ts +0 -53
  110. package/eslint-integration/test-s003.js +0 -5
  111. package/eslint-integration/tsconfig.json +0 -27
  112. package/examples/.github/workflows/code-quality.yml +0 -111
  113. package/examples/README.md +0 -69
  114. package/examples/basic-typescript-demo/.eslintrc.json +0 -18
  115. package/examples/basic-typescript-demo/.next/cache/eslint/.cache_1othrmo +0 -1
  116. package/examples/basic-typescript-demo/.sunlint.json +0 -29
  117. package/examples/basic-typescript-demo/eslint.config.mjs +0 -37
  118. package/examples/basic-typescript-demo/next-env.d.ts +0 -5
  119. package/examples/basic-typescript-demo/next.config.mjs +0 -4
  120. package/examples/basic-typescript-demo/package-lock.json +0 -5656
  121. package/examples/basic-typescript-demo/package.json +0 -34
  122. package/examples/basic-typescript-demo/src/app/layout.tsx +0 -18
  123. package/examples/basic-typescript-demo/src/app/page.tsx +0 -48
  124. package/examples/basic-typescript-demo/src/config.ts +0 -14
  125. package/examples/basic-typescript-demo/src/good-practices.ts +0 -58
  126. package/examples/basic-typescript-demo/src/types.generated.ts +0 -13
  127. package/examples/basic-typescript-demo/src/user.test.ts +0 -19
  128. package/examples/basic-typescript-demo/src/violations.ts +0 -61
  129. package/examples/basic-typescript-demo/test-config-priority.sh +0 -0
  130. package/examples/basic-typescript-demo/test-file-targeting.sh +0 -0
  131. package/examples/basic-typescript-demo/tsconfig.json +0 -27
  132. package/examples/enhanced-config.json +0 -0
  133. package/examples/eslint-integration-demo/.eslintrc.js +0 -38
  134. package/examples/eslint-integration-demo/.sunlint.json +0 -42
  135. package/examples/eslint-integration-demo/next-env.d.ts +0 -5
  136. package/examples/eslint-integration-demo/next.config.js +0 -8
  137. package/examples/eslint-integration-demo/package-lock.json +0 -5740
  138. package/examples/eslint-integration-demo/package.json +0 -37
  139. package/examples/eslint-integration-demo/src/api.test.ts +0 -20
  140. package/examples/eslint-integration-demo/src/conflict-test.tsx +0 -44
  141. package/examples/eslint-integration-demo/src/naming-conflicts.ts +0 -50
  142. package/examples/eslint-integration-demo/test-file-targeting.sh +0 -0
  143. package/examples/eslint-integration-demo/tsconfig.json +0 -26
  144. package/examples/file-targeting-demo/global.d.ts +0 -11
  145. package/examples/file-targeting-demo/jest.config.js +0 -8
  146. package/examples/file-targeting-demo/sample.ts +0 -53
  147. package/examples/file-targeting-demo/src/server.js +0 -11
  148. package/examples/file-targeting-demo/src/server.test.js +0 -11
  149. package/examples/file-targeting-demo/src/types.d.ts +0 -4
  150. package/examples/file-targeting-demo/src/types.generated.ts +0 -10
  151. package/examples/file-targeting-demo/user-service.test.ts +0 -15
  152. package/examples/file-targeting-demo/user-service.ts +0 -13
  153. package/examples/file-targeting-demo/utils.js +0 -15
  154. package/examples/multi-language-project/.eslintrc.json +0 -38
  155. package/examples/multi-language-project/package.json +0 -37
  156. package/examples/multi-language-project/src/sample.ts +0 -39
  157. package/examples/rule-test-fixtures/README.md +0 -67
  158. package/examples/rule-test-fixtures/rules/C006_function_naming/clean/typescript-clean.ts +0 -64
  159. package/examples/rule-test-fixtures/rules/C006_function_naming/violations/dart-violations.dart +0 -56
  160. package/examples/rule-test-fixtures/rules/C006_function_naming/violations/typescript-violations.ts +0 -47
  161. package/examples/rule-test-fixtures/rules/C019_log_level_usage/clean/typescript-clean.ts +0 -93
  162. package/examples/rule-test-fixtures/rules/C019_log_level_usage/violations/dart-violations.dart +0 -75
  163. package/examples/rule-test-fixtures/rules/C019_log_level_usage/violations/typescript-violations.ts +0 -84
  164. package/examples/rule-test-fixtures/rules/C029_catch_block_logging/clean/typescript-clean.ts +0 -0
  165. package/examples/rule-test-fixtures/rules/C029_catch_block_logging/violations/typescript-violations.ts +0 -37
@@ -1,211 +0,0 @@
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
- };
@@ -1,294 +0,0 @@
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
- };