@sun-asterisk/sunlint 1.0.7 → 1.1.4

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 (219) hide show
  1. package/.sunlint.json +35 -0
  2. package/CHANGELOG.md +30 -3
  3. package/CONTRIBUTING.md +235 -0
  4. package/PROJECT_STRUCTURE.md +60 -0
  5. package/README.md +146 -58
  6. package/cli.js +1 -0
  7. package/config/README.md +88 -0
  8. package/config/defaults/ai-rules-context.json +231 -0
  9. package/config/engines/engines.json +49 -0
  10. package/config/engines/eslint-rule-mapping.json +74 -0
  11. package/config/eslint-rule-mapping.json +126 -0
  12. package/config/integrations/eslint/base.config.js +125 -0
  13. package/config/integrations/eslint/simple.config.js +24 -0
  14. package/config/presets/strict.json +0 -1
  15. package/config/rule-analysis-strategies.js +74 -0
  16. package/config/{rules-registry.json → rules/rules-registry.json} +30 -7
  17. package/core/analysis-orchestrator.js +383 -591
  18. package/core/ast-modules/README.md +103 -0
  19. package/core/ast-modules/base-parser.js +90 -0
  20. package/core/ast-modules/index.js +97 -0
  21. package/core/ast-modules/package.json +37 -0
  22. package/core/ast-modules/parsers/eslint-js-parser.js +153 -0
  23. package/core/ast-modules/parsers/eslint-ts-parser.js +98 -0
  24. package/core/ast-modules/parsers/javascript-parser.js +187 -0
  25. package/core/ast-modules/parsers/typescript-parser.js +187 -0
  26. package/core/cli-action-handler.js +271 -255
  27. package/core/cli-program.js +18 -4
  28. package/core/config-manager.js +9 -3
  29. package/core/config-merger.js +40 -1
  30. package/core/config-validator.js +2 -2
  31. package/core/dependency-checker.js +125 -0
  32. package/core/enhanced-rules-registry.js +331 -0
  33. package/core/file-targeting-service.js +92 -23
  34. package/core/interfaces/analysis-engine.interface.js +100 -0
  35. package/core/multi-rule-runner.js +0 -221
  36. package/core/output-service.js +1 -1
  37. package/core/rule-mapping-service.js +1 -1
  38. package/core/rule-selection-service.js +10 -2
  39. package/core/smart-installer.js +164 -0
  40. package/docs/AI.md +163 -0
  41. package/docs/ARCHITECTURE.md +78 -0
  42. package/docs/CI-CD-GUIDE.md +315 -0
  43. package/docs/COMMAND-EXAMPLES.md +256 -0
  44. package/docs/CONFIGURATION.md +414 -0
  45. package/docs/DEBUG.md +86 -0
  46. package/docs/DEPENDENCIES.md +90 -0
  47. package/docs/DEPLOYMENT-STRATEGIES.md +270 -0
  48. package/docs/DISTRIBUTION.md +153 -0
  49. package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
  50. package/docs/ESLINT_INTEGRATION.md +238 -0
  51. package/docs/FOLDER_STRUCTURE.md +59 -0
  52. package/docs/FUTURE_PACKAGES.md +83 -0
  53. package/docs/HEURISTIC_VS_AI.md +113 -0
  54. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +112 -0
  55. package/docs/PRODUCTION_SIZE_IMPACT.md +183 -0
  56. package/docs/README.md +32 -0
  57. package/docs/RELEASE_GUIDE.md +230 -0
  58. package/engines/eslint-engine.js +610 -0
  59. package/engines/heuristic-engine.js +864 -0
  60. package/engines/openai-engine.js +374 -0
  61. package/engines/tree-sitter-parser.js +0 -0
  62. package/engines/universal-ast-engine.js +0 -0
  63. package/integrations/eslint/README.md +99 -0
  64. package/integrations/eslint/configs/.eslintrc.js +98 -0
  65. package/integrations/eslint/configs/eslint.config.js +133 -0
  66. package/integrations/eslint/configs/eslint.config.simple.js +24 -0
  67. package/integrations/eslint/package.json +23 -0
  68. package/integrations/eslint/plugin/index.js +164 -0
  69. package/integrations/eslint/plugin/package.json +13 -0
  70. package/integrations/eslint/plugin/rules/common/c002-no-duplicate-code.js +204 -0
  71. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +246 -0
  72. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +216 -0
  73. package/integrations/eslint/plugin/rules/common/c010-limit-block-nesting.js +90 -0
  74. package/integrations/eslint/plugin/rules/common/c013-no-dead-code.js +78 -0
  75. package/integrations/eslint/plugin/rules/common/c014-abstract-dependency-preferred.js +38 -0
  76. package/integrations/eslint/plugin/rules/common/c017-limit-constructor-logic.js +146 -0
  77. package/integrations/eslint/plugin/rules/common/c018-no-generic-throw.js +335 -0
  78. package/integrations/eslint/plugin/rules/common/c023-no-duplicate-variable-name-in-scope.js +142 -0
  79. package/integrations/eslint/plugin/rules/common/c029-catch-block-logging.js +115 -0
  80. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +294 -0
  81. package/integrations/eslint/plugin/rules/common/c035-no-empty-catch.js +162 -0
  82. package/integrations/eslint/plugin/rules/common/c041-no-config-inline.js +122 -0
  83. package/integrations/eslint/plugin/rules/common/c042-boolean-name-prefix.js +406 -0
  84. package/integrations/eslint/plugin/rules/common/c043-no-console-or-print.js +300 -0
  85. package/integrations/eslint/plugin/rules/common/c047-no-duplicate-retry-logic.js +239 -0
  86. package/integrations/eslint/plugin/rules/common/c072-one-assert-per-test.js +184 -0
  87. package/integrations/eslint/plugin/rules/common/c075-explicit-function-return-types.js +168 -0
  88. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +254 -0
  89. package/integrations/eslint/plugin/rules/security/s001-fail-securely.js +381 -0
  90. package/integrations/eslint/plugin/rules/security/s002-idor-check.js +945 -0
  91. package/integrations/eslint/plugin/rules/security/s003-no-unvalidated-redirect.js +86 -0
  92. package/integrations/eslint/plugin/rules/security/s007-no-plaintext-otp.js +74 -0
  93. package/integrations/eslint/plugin/rules/security/s013-verify-tls-connection.js +47 -0
  94. package/integrations/eslint/plugin/rules/security/s047-secure-random-passwords.js +108 -0
  95. package/integrations/eslint/plugin/rules/security/s055-verification-rest-check-the-incoming-content-type.js +143 -0
  96. package/integrations/eslint/plugin/rules/typescript/t002-interface-prefix-i.js +42 -0
  97. package/integrations/eslint/plugin/rules/typescript/t003-ts-ignore-reason.js +48 -0
  98. package/integrations/eslint/plugin/rules/typescript/t004-no-empty-type.js +95 -0
  99. package/integrations/eslint/plugin/rules/typescript/t007-no-fn-in-constructor.js +52 -0
  100. package/integrations/eslint/plugin/rules/typescript/t010-no-nested-union-tuple.js +48 -0
  101. package/integrations/eslint/plugin/rules/typescript/t019-no-this-assign.js +81 -0
  102. package/integrations/eslint/plugin/rules/typescript/t020-no-default-multi-export.js +127 -0
  103. package/integrations/eslint/plugin/rules/typescript/t021-limit-nested-generics.js +150 -0
  104. package/integrations/eslint/tsconfig.json +27 -0
  105. package/package.json +61 -21
  106. package/rules/README.md +252 -0
  107. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  108. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  109. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  110. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  111. package/rules/{C006_function_naming → common/C006_function_naming}/analyzer.js +13 -2
  112. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  113. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  114. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  115. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  116. package/rules/{C019_log_level_usage → common/C019_log_level_usage}/analyzer.js +5 -2
  117. package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/analyzer.js +49 -15
  118. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  119. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  120. package/rules/common/C043_no_console_or_print/analyzer.js +304 -0
  121. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +351 -0
  122. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  123. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  124. package/rules/docs/C002_no_duplicate_code.md +57 -0
  125. package/rules/index.js +149 -0
  126. package/rules/migration/converter.js +385 -0
  127. package/rules/migration/mapping.json +164 -0
  128. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  129. package/rules/security/S026_json_schema_validation/config.json +27 -0
  130. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +263 -0
  131. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  132. package/rules/security/S029_csrf_protection/analyzer.js +264 -0
  133. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  134. package/rules/universal/C010/generic.js +0 -0
  135. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  136. package/rules/utils/ast-utils.js +191 -0
  137. package/rules/utils/base-analyzer.js +98 -0
  138. package/rules/utils/pattern-matchers.js +239 -0
  139. package/rules/utils/rule-helpers.js +264 -0
  140. package/rules/utils/severity-constants.js +93 -0
  141. package/scripts/build-release.sh +117 -0
  142. package/scripts/ci-report.js +179 -0
  143. package/scripts/install.sh +196 -0
  144. package/scripts/manual-release.sh +338 -0
  145. package/scripts/merge-reports.js +424 -0
  146. package/scripts/pre-release-test.sh +175 -0
  147. package/scripts/prepare-release.sh +202 -0
  148. package/scripts/setup-github-registry.sh +42 -0
  149. package/scripts/test-scripts/README.md +22 -0
  150. package/scripts/test-scripts/test-c041-comparison.js +114 -0
  151. package/scripts/test-scripts/test-c041-eslint.js +67 -0
  152. package/scripts/test-scripts/test-eslint-rules.js +146 -0
  153. package/scripts/test-scripts/test-real-world.js +44 -0
  154. package/scripts/test-scripts/test-rules-on-real-projects.js +86 -0
  155. package/scripts/trigger-release.sh +285 -0
  156. package/scripts/validate-rule-structure.js +148 -0
  157. package/scripts/verify-install.sh +82 -0
  158. package/config/sunlint-schema.json +0 -159
  159. package/config/typescript/custom-rules.js +0 -9
  160. package/config/typescript/package-lock.json +0 -1585
  161. package/config/typescript/package.json +0 -13
  162. package/config/typescript/security-rules/index.js +0 -90
  163. package/config/typescript/tsconfig.json +0 -29
  164. package/core/ai-analyzer.js +0 -169
  165. package/core/eslint-engine-service.js +0 -312
  166. package/core/eslint-instance-manager.js +0 -104
  167. package/core/eslint-integration-service.js +0 -363
  168. package/core/sunlint-engine-service.js +0 -23
  169. package/core/typescript-analyzer.js +0 -262
  170. package/core/typescript-engine.js +0 -313
  171. /package/config/{default.json → defaults/default.json} +0 -0
  172. /package/config/{typescript/eslint.config.js → integrations/eslint/typescript.config.js} +0 -0
  173. /package/config/{typescript/custom-rules-new.js → schemas/sunlint-schema.json} +0 -0
  174. /package/config/{typescript → testing}/test-s005-working.ts +0 -0
  175. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s005-no-origin-auth.js +0 -0
  176. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s006-activation-recovery-secret-not-plaintext.js +0 -0
  177. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s008-crypto-agility.js +0 -0
  178. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s009-no-insecure-crypto.js +0 -0
  179. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s010-no-insecure-random-in-sensitive-context.js +0 -0
  180. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s011-no-insecure-uuid.js +0 -0
  181. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s012-hardcode-secret.js +0 -0
  182. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s014-insecure-tls-version.js +0 -0
  183. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s015-insecure-tls-certificate.js +0 -0
  184. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s016-sensitive-query-parameter.js +0 -0
  185. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s017-no-sql-injection.js +0 -0
  186. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s018-positive-input-validation.js +0 -0
  187. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s019-no-raw-user-input-in-email.js +0 -0
  188. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s020-no-eval-dynamic-execution.js +0 -0
  189. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s022-output-encoding.js +0 -0
  190. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s023-no-json-injection.js +0 -0
  191. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s025-server-side-input-validation.js +0 -0
  192. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s026-json-schema-validation.js +0 -0
  193. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s027-no-hardcoded-secrets.js +0 -0
  194. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s029-require-csrf-protection.js +0 -0
  195. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s030-no-directory-browsing.js +0 -0
  196. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s033-require-samesite-cookie.js +0 -0
  197. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s034-require-host-cookie-prefix.js +0 -0
  198. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s035-cookie-specific-path.js +0 -0
  199. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s036-no-unsafe-file-include.js +0 -0
  200. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s037-require-anti-cache-headers.js +0 -0
  201. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s038-no-version-disclosure.js +0 -0
  202. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s039-no-session-token-in-url.js +0 -0
  203. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s041-require-session-invalidate-on-logout.js +0 -0
  204. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s042-require-periodic-reauthentication.js +0 -0
  205. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s043-terminate-sessions-on-password-change.js +0 -0
  206. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s044-require-full-session-for-sensitive-operations.js +0 -0
  207. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s045-anti-automation-controls.js +0 -0
  208. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s046-secure-notification-on-auth-change.js +0 -0
  209. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s048-password-credential-recovery.js +0 -0
  210. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s050-session-token-weak-hash.js +0 -0
  211. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s052-secure-random-authentication-code.js +0 -0
  212. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s054-verification-default-account.js +0 -0
  213. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s057-utc-logging.js +0 -0
  214. /package/{config/typescript/security-rules → integrations/eslint/plugin/rules/security}/s058-no-ssrf.js +0 -0
  215. /package/rules/{C006_function_naming → common/C006_function_naming}/config.json +0 -0
  216. /package/rules/{C019_log_level_usage → common/C019_log_level_usage}/config.json +0 -0
  217. /package/rules/{C029_catch_block_logging → common/C029_catch_block_logging}/config.json +0 -0
  218. /package/rules/{C031_validation_separation → common/C031_validation_separation}/analyzer.js +0 -0
  219. /package/rules/{C031_validation_separation/README.md → docs/C031_validation_separation.md} +0 -0
@@ -0,0 +1,945 @@
1
+ /**
2
+ * ESLint rule: S002 – IDOR Check
3
+ * Rule ID: custom/s002
4
+ * Description: Verify IDOR (Insecure Direct Object Reference) security in REST APIs.
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: 'problem',
12
+ docs: {
13
+ description: 'Verify IDOR (Insecure Direct Object Reference) security in REST APIs',
14
+ recommended: true,
15
+ url: 'https://owasp.org/www-project-top-ten/2017/A5_2017-Broken_Access_Control'
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ restEndpointMissingAuth: 'IDOR Risk: REST endpoint with ID parameter lacks authorization check. Add proper authentication middleware or manual ownership validation.',
20
+ repositoryMissingConstraint: 'IDOR Risk: Database query should include user/owner constraint (e.g., WHERE user_id = $userId) to prevent unauthorized data access.',
21
+ directEntityReturn: 'IDOR Risk: Avoid returning Entity/Model directly from REST endpoints. Use DTO/ResponseModel to control data exposure.',
22
+ sequentialIdUsage: 'IDOR Risk: Consider using UUID instead of sequential ID (number) to make resource identifiers unpredictable.',
23
+ directRepositoryAccess: 'IDOR Risk: Direct repository/database access with ID from URL without authorization check. Verify user ownership before accessing resources.',
24
+ },
25
+ },
26
+
27
+ create(context) {
28
+ return {
29
+ // Check REST endpoints (Express.js, NestJS, etc.)
30
+ CallExpression(node) {
31
+ checkRestEndpointAuthorization(node, context);
32
+ checkDirectRepositoryAccess(node, context);
33
+ },
34
+
35
+ // Check method definitions
36
+ MethodDefinition(node) {
37
+ checkRepositorySecurityConstraint(node, context);
38
+ checkDirectEntityReturn(node, context);
39
+ },
40
+
41
+ // Check function declarations
42
+ FunctionDeclaration(node) {
43
+ checkRepositorySecurityConstraint(node, context);
44
+ checkDirectEntityReturn(node, context);
45
+ },
46
+
47
+ // Check variable declarations for ID fields
48
+ VariableDeclarator(node) {
49
+ checkSequentialIdUsage(node, context);
50
+ },
51
+
52
+ // Check property definitions (for class properties)
53
+ PropertyDefinition(node) {
54
+ checkSequentialIdUsage(node, context);
55
+ },
56
+
57
+ // Check arrow function expressions
58
+ ArrowFunctionExpression(node) {
59
+ checkRepositorySecurityConstraint(node, context);
60
+ checkDirectEntityReturn(node, context);
61
+ },
62
+
63
+ // Check function expressions
64
+ FunctionExpression(node) {
65
+ checkRepositorySecurityConstraint(node, context);
66
+ checkDirectEntityReturn(node, context);
67
+ },
68
+ };
69
+
70
+ // =================== CHECK METHODS ===================
71
+
72
+ /**
73
+ * Check if the endpoint has security middleware
74
+ */
75
+ function hasSecurityMiddleware(node) {
76
+ if (node.type !== 'CallExpression') return false;
77
+
78
+ // Check for middleware functions like auth, authenticate, authorize
79
+ return node.arguments.some(arg => {
80
+ if (arg.type === 'Identifier') {
81
+ const argName = arg.name.toLowerCase();
82
+ return argName.includes('auth') ||
83
+ argName.includes('guard') ||
84
+ argName.includes('protect') ||
85
+ argName.includes('secure') ||
86
+ argName.includes('jwt') ||
87
+ argName.includes('token');
88
+ }
89
+ return false;
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Check if the function has manual security check
95
+ */
96
+ function hasManualSecurityCheck(node) {
97
+ let bodyNode;
98
+
99
+ if (node.type === 'CallExpression') {
100
+ // Check callback function in Express.js
101
+ const callback = node.arguments.find(arg =>
102
+ arg.type === 'FunctionExpression' ||
103
+ arg.type === 'ArrowFunctionExpression'
104
+ );
105
+ if (callback && callback.body) {
106
+ bodyNode = callback.body;
107
+ }
108
+ } else if (node.body) {
109
+ bodyNode = node.body;
110
+ }
111
+
112
+ if (!bodyNode) return false;
113
+
114
+ // If arrow function with expression body
115
+ if (bodyNode.type !== 'BlockStatement') {
116
+ const bodyText = context.getSourceCode().getText(bodyNode).toLowerCase();
117
+ return containsSecurityCheck(bodyText);
118
+ }
119
+
120
+ // Check block statement
121
+ return bodyNode.body.some(statement => {
122
+ const statementText = context.getSourceCode().getText(statement).toLowerCase();
123
+ return containsSecurityCheck(statementText);
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Check REST endpoint with ID parameter but missing authorization
129
+ */
130
+ function checkRestEndpointAuthorization(node, context) {
131
+ if (!isRestEndpoint(node)) return;
132
+
133
+ const hasIdParameter = hasPathParameterId(node);
134
+ const hasSecurityMiddlewareResult = hasSecurityMiddleware(node);
135
+ const hasManualSecurityCheckResult = hasManualSecurityCheck(node);
136
+
137
+ if (hasIdParameter && !hasSecurityMiddlewareResult && !hasManualSecurityCheckResult) {
138
+ context.report({
139
+ node,
140
+ messageId: 'restEndpointMissingAuth',
141
+ });
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Check if the method has custom query
147
+ */
148
+ function hasCustomQuery(node) {
149
+ if (!node.body || node.body.type !== 'BlockStatement') return false;
150
+
151
+ return node.body.body.some(statement => {
152
+ const statementText = context.getSourceCode().getText(statement).toLowerCase();
153
+ return statementText.includes('query') ||
154
+ statementText.includes('sql') ||
155
+ statementText.includes('select') ||
156
+ statementText.includes('from') ||
157
+ statementText.includes('where') ||
158
+ statementText.includes('findby') ||
159
+ statementText.includes('createquerybuilder') ||
160
+ statementText.includes('rawquery');
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Check if query contains user constraint
166
+ */
167
+ function hasUserConstraintInQuery(node) {
168
+ if (!node.body || node.body.type !== 'BlockStatement') return false;
169
+
170
+ return node.body.body.some(statement => {
171
+ const statementText = context.getSourceCode().getText(statement).toLowerCase();
172
+ return statementText.includes('user_id') ||
173
+ statementText.includes('owner_id') ||
174
+ statementText.includes('created_by') ||
175
+ statementText.includes('userid') ||
176
+ statementText.includes('ownerid') ||
177
+ statementText.includes('createdby') ||
178
+ (statementText.includes('where') &&
179
+ (statementText.includes('user') || statementText.includes('owner')));
180
+ });
181
+ }
182
+
183
+ /**
184
+ * Check Repository query missing user/owner constraint
185
+ */
186
+ function checkRepositorySecurityConstraint(node, context) {
187
+ if (!isRepositoryMethod(node)) return;
188
+
189
+ const hasCustomQueryResult = hasCustomQuery(node);
190
+ const hasUserConstraint = hasUserConstraintInQuery(node);
191
+
192
+ if (hasCustomQueryResult && !hasUserConstraint) {
193
+ context.report({
194
+ node,
195
+ messageId: 'repositoryMissingConstraint',
196
+ });
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Check if method returns Entity directly instead of DTO
202
+ */
203
+ function checkDirectEntityReturn(node, context) {
204
+ if (!isRestEndpoint(node)) return;
205
+
206
+ const returnsEntity = returnsEntityDirectly(node);
207
+
208
+ if (returnsEntity) {
209
+ context.report({
210
+ node,
211
+ messageId: 'directEntityReturn',
212
+ });
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Check if ID field uses sequential number
218
+ */
219
+ function checkSequentialIdUsage(node, context) {
220
+ const isEntityId = isEntityIdField(node);
221
+ const isSequentialType = isSequentialIdType(node);
222
+
223
+ if (isEntityId && isSequentialType) {
224
+ context.report({
225
+ node,
226
+ messageId: 'sequentialIdUsage',
227
+ });
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Check direct repository access without authorization check
233
+ */
234
+ function checkDirectRepositoryAccess(node, context) {
235
+ if (!isRepositoryFindMethod(node)) return;
236
+
237
+ const enclosingFunction = getEnclosingFunction(node);
238
+ if (!enclosingFunction) return;
239
+
240
+ const isInRestControllerResult = isInRestController(enclosingFunction);
241
+ const hasIdParameter = hasPathParameterId(enclosingFunction);
242
+ const hasAuthCheck = hasAuthorizationLogicNearby(node, enclosingFunction);
243
+
244
+ if (isInRestControllerResult && hasIdParameter && !hasAuthCheck) {
245
+ context.report({
246
+ node,
247
+ messageId: 'directRepositoryAccess',
248
+ });
249
+ }
250
+ }
251
+
252
+ // =================== HELPER METHODS ===================
253
+
254
+ /**
255
+ * Check if the call expression is a REST endpoint
256
+ */
257
+ function isRestEndpoint(node) {
258
+ // Express.js style: app.get(), router.post(), etc.
259
+ if (node.type === 'CallExpression') {
260
+ const callee = node.callee;
261
+ if (callee.type === 'MemberExpression') {
262
+ const property = callee.property;
263
+ if (property.type === 'Identifier') {
264
+ const methodName = property.name;
265
+ return ['get', 'post', 'put', 'delete', 'patch', 'use', 'all'].includes(methodName);
266
+ }
267
+ }
268
+ }
269
+
270
+ // NestJS style: @Get(), @Post(), etc.
271
+ if (node.decorators) {
272
+ return node.decorators.some(decorator => {
273
+ if (decorator.expression.type === 'Identifier') {
274
+ const decoratorName = decorator.expression.name;
275
+ return ['Get', 'Post', 'Put', 'Delete', 'Patch', 'All'].includes(decoratorName);
276
+ }
277
+ if (decorator.expression.type === 'CallExpression' &&
278
+ decorator.expression.callee.type === 'Identifier') {
279
+ const decoratorName = decorator.expression.callee.name;
280
+ return ['Get', 'Post', 'Put', 'Delete', 'Patch', 'All'].includes(decoratorName);
281
+ }
282
+ return false;
283
+ });
284
+ }
285
+
286
+ return false;
287
+ }
288
+
289
+ /**
290
+ * Check if the endpoint has a path parameter ID
291
+ */
292
+ function hasPathParameterId(node) {
293
+ // Express.js style: app.get('/users/:id', ...)
294
+ if (node.type === 'CallExpression') {
295
+ const firstArg = node.arguments[0];
296
+ if (firstArg?.type === 'Literal' && typeof firstArg.value === 'string') {
297
+ return firstArg.value.includes(':id') ||
298
+ firstArg.value.includes(':uuid') ||
299
+ firstArg.value.match(/:[\w]*id/i);
300
+ }
301
+ }
302
+
303
+ // NestJS style: @Get(':id')
304
+ if (node.decorators) {
305
+ return node.decorators.some(decorator => {
306
+ if (decorator.expression.type === 'CallExpression') {
307
+ const firstArg = decorator.expression.arguments[0];
308
+ if (firstArg?.type === 'Literal' && typeof firstArg.value === 'string') {
309
+ return firstArg.value.includes(':id') ||
310
+ firstArg.value.includes(':uuid') ||
311
+ firstArg.value.match(/:[\w]*id/i);
312
+ }
313
+ }
314
+ return false;
315
+ });
316
+ }
317
+
318
+ // Check function parameters
319
+ if (node.params) {
320
+ return node.params.some(param => {
321
+ if (param.type === 'Identifier') {
322
+ const paramName = param.name.toLowerCase();
323
+ return paramName.includes('id') || paramName === 'uuid';
324
+ }
325
+ // Destructured parameter: { id }
326
+ if (param.type === 'ObjectPattern') {
327
+ return param.properties.some(prop => {
328
+ if (prop.type === 'Property' && prop.key.type === 'Identifier') {
329
+ const propName = prop.key.name.toLowerCase();
330
+ return propName.includes('id') || propName === 'uuid';
331
+ }
332
+ return false;
333
+ });
334
+ }
335
+ return false;
336
+ });
337
+ }
338
+
339
+ return false;
340
+ }
341
+
342
+ /**
343
+ * Check if the endpoint has security middleware
344
+ */
345
+ function hasSecurityMiddleware(node) {
346
+ if (node.type !== 'CallExpression') return false;
347
+
348
+ // Check for middleware functions like auth, authenticate, authorize
349
+ return node.arguments.some(arg => {
350
+ if (arg.type === 'Identifier') {
351
+ const argName = arg.name.toLowerCase();
352
+ return argName.includes('auth') ||
353
+ argName.includes('guard') ||
354
+ argName.includes('protect') ||
355
+ argName.includes('secure') ||
356
+ argName.includes('jwt') ||
357
+ argName.includes('token');
358
+ }
359
+ return false;
360
+ });
361
+ }
362
+
363
+ /**
364
+ * Check if the method has custom query
365
+ */
366
+ function hasCustomQuery(node) {
367
+ if (!node.body || node.body.type !== 'BlockStatement') return false;
368
+
369
+ return node.body.body.some(statement => {
370
+ const statementText = context.getSourceCode().getText(statement).toLowerCase();
371
+ return statementText.includes('query') ||
372
+ statementText.includes('sql') ||
373
+ statementText.includes('select') ||
374
+ statementText.includes('from') ||
375
+ statementText.includes('where') ||
376
+ statementText.includes('findby') ||
377
+ statementText.includes('createquerybuilder') ||
378
+ statementText.includes('rawquery');
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Check if query contains user constraint
384
+ */
385
+ function hasUserConstraintInQuery(node) {
386
+ if (!node.body || node.body.type !== 'BlockStatement') return false;
387
+
388
+ return node.body.body.some(statement => {
389
+ const statementText = context.getSourceCode().getText(statement).toLowerCase();
390
+ return statementText.includes('user_id') ||
391
+ statementText.includes('owner_id') ||
392
+ statementText.includes('created_by') ||
393
+ statementText.includes('userid') ||
394
+ statementText.includes('ownerid') ||
395
+ statementText.includes('createdby') ||
396
+ (statementText.includes('where') &&
397
+ (statementText.includes('user') || statementText.includes('owner')));
398
+ });
399
+ }
400
+
401
+ /**
402
+ * Check Repository query missing user/owner constraint
403
+ */
404
+ function checkRepositorySecurityConstraint(node, context) {
405
+ if (!isRepositoryMethod(node)) return;
406
+
407
+ const hasCustomQueryResult = hasCustomQuery(node);
408
+ const hasUserConstraint = hasUserConstraintInQuery(node);
409
+
410
+ if (hasCustomQueryResult && !hasUserConstraint) {
411
+ context.report({
412
+ node,
413
+ messageId: 'repositoryMissingConstraint',
414
+ });
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Check if method returns Entity directly instead of DTO
420
+ */
421
+ function checkDirectEntityReturn(node, context) {
422
+ if (!isRestEndpoint(node)) return;
423
+
424
+ const returnsEntity = returnsEntityDirectly(node);
425
+
426
+ if (returnsEntity) {
427
+ context.report({
428
+ node,
429
+ messageId: 'directEntityReturn',
430
+ });
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Check if ID field uses sequential number
436
+ */
437
+ function checkSequentialIdUsage(node, context) {
438
+ const isEntityId = isEntityIdField(node);
439
+ const isSequentialType = isSequentialIdType(node);
440
+
441
+ if (isEntityId && isSequentialType) {
442
+ context.report({
443
+ node,
444
+ messageId: 'sequentialIdUsage',
445
+ });
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Check direct repository access without authorization check
451
+ */
452
+ function checkDirectRepositoryAccess(node, context) {
453
+ if (!isRepositoryFindMethod(node)) return;
454
+
455
+ const enclosingFunction = getEnclosingFunction(node);
456
+ if (!enclosingFunction) return;
457
+
458
+ const isInRestControllerResult = isInRestController(enclosingFunction);
459
+ const hasIdParameter = hasPathParameterId(enclosingFunction);
460
+ const hasAuthCheck = hasAuthorizationLogicNearby(node, enclosingFunction);
461
+
462
+ if (isInRestControllerResult && hasIdParameter && !hasAuthCheck) {
463
+ context.report({
464
+ node,
465
+ messageId: 'directRepositoryAccess',
466
+ });
467
+ }
468
+ }
469
+
470
+ // =================== HELPER METHODS ===================
471
+
472
+ /**
473
+ * Check if the call expression is a REST endpoint
474
+ */
475
+ function isRestEndpoint(node) {
476
+ // Express.js style: app.get(), router.post(), etc.
477
+ if (node.type === 'CallExpression') {
478
+ const callee = node.callee;
479
+ if (callee.type === 'MemberExpression') {
480
+ const property = callee.property;
481
+ if (property.type === 'Identifier') {
482
+ const methodName = property.name;
483
+ return ['get', 'post', 'put', 'delete', 'patch', 'use', 'all'].includes(methodName);
484
+ }
485
+ }
486
+ }
487
+
488
+ // NestJS style: @Get(), @Post(), etc.
489
+ if (node.decorators) {
490
+ return node.decorators.some(decorator => {
491
+ if (decorator.expression.type === 'Identifier') {
492
+ const decoratorName = decorator.expression.name;
493
+ return ['Get', 'Post', 'Put', 'Delete', 'Patch', 'All'].includes(decoratorName);
494
+ }
495
+ if (decorator.expression.type === 'CallExpression' &&
496
+ decorator.expression.callee.type === 'Identifier') {
497
+ const decoratorName = decorator.expression.callee.name;
498
+ return ['Get', 'Post', 'Put', 'Delete', 'Patch', 'All'].includes(decoratorName);
499
+ }
500
+ return false;
501
+ });
502
+ }
503
+
504
+ return false;
505
+ }
506
+
507
+ /**
508
+ * Check if the endpoint has a path parameter ID
509
+ */
510
+ function hasPathParameterId(node) {
511
+ // Express.js style: app.get('/users/:id', ...)
512
+ if (node.type === 'CallExpression') {
513
+ const firstArg = node.arguments[0];
514
+ if (firstArg?.type === 'Literal' && typeof firstArg.value === 'string') {
515
+ return firstArg.value.includes(':id') ||
516
+ firstArg.value.includes(':uuid') ||
517
+ firstArg.value.match(/:[\w]*id/i);
518
+ }
519
+ }
520
+
521
+ // NestJS style: @Get(':id')
522
+ if (node.decorators) {
523
+ return node.decorators.some(decorator => {
524
+ if (decorator.expression.type === 'CallExpression') {
525
+ const firstArg = decorator.expression.arguments[0];
526
+ if (firstArg?.type === 'Literal' && typeof firstArg.value === 'string') {
527
+ return firstArg.value.includes(':id') ||
528
+ firstArg.value.includes(':uuid') ||
529
+ firstArg.value.match(/:[\w]*id/i);
530
+ }
531
+ }
532
+ return false;
533
+ });
534
+ }
535
+
536
+ // Check function parameters
537
+ if (node.params) {
538
+ return node.params.some(param => {
539
+ if (param.type === 'Identifier') {
540
+ const paramName = param.name.toLowerCase();
541
+ return paramName.includes('id') || paramName === 'uuid';
542
+ }
543
+ // Destructured parameter: { id }
544
+ if (param.type === 'ObjectPattern') {
545
+ return param.properties.some(prop => {
546
+ if (prop.type === 'Property' && prop.key.type === 'Identifier') {
547
+ const propName = prop.key.name.toLowerCase();
548
+ return propName.includes('id') || propName === 'uuid';
549
+ }
550
+ return false;
551
+ });
552
+ }
553
+ return false;
554
+ });
555
+ }
556
+
557
+ return false;
558
+ }
559
+
560
+ /**
561
+ * Check if the endpoint has security middleware
562
+ */
563
+ function hasSecurityMiddleware(node) {
564
+ if (node.type !== 'CallExpression') return false;
565
+
566
+ // Check for middleware functions like auth, authenticate, authorize
567
+ return node.arguments.some(arg => {
568
+ if (arg.type === 'Identifier') {
569
+ const argName = arg.name.toLowerCase();
570
+ return argName.includes('auth') ||
571
+ argName.includes('guard') ||
572
+ argName.includes('protect') ||
573
+ argName.includes('secure') ||
574
+ argName.includes('jwt') ||
575
+ argName.includes('token');
576
+ }
577
+ return false;
578
+ });
579
+ }
580
+
581
+ /**
582
+ * Check if the method has custom query
583
+ */
584
+ function hasCustomQuery(node) {
585
+ if (!node.body || node.body.type !== 'BlockStatement') return false;
586
+
587
+ return node.body.body.some(statement => {
588
+ const statementText = context.getSourceCode().getText(statement).toLowerCase();
589
+ return statementText.includes('query') ||
590
+ statementText.includes('sql') ||
591
+ statementText.includes('select') ||
592
+ statementText.includes('from') ||
593
+ statementText.includes('where') ||
594
+ statementText.includes('findby') ||
595
+ statementText.includes('createquerybuilder') ||
596
+ statementText.includes('rawquery');
597
+ });
598
+ }
599
+
600
+ /**
601
+ * Check if query contains user constraint
602
+ */
603
+ function hasUserConstraintInQuery(node) {
604
+ if (!node.body || node.body.type !== 'BlockStatement') return false;
605
+
606
+ return node.body.body.some(statement => {
607
+ const statementText = context.getSourceCode().getText(statement).toLowerCase();
608
+ return statementText.includes('user_id') ||
609
+ statementText.includes('owner_id') ||
610
+ statementText.includes('created_by') ||
611
+ statementText.includes('userid') ||
612
+ statementText.includes('ownerid') ||
613
+ statementText.includes('createdby') ||
614
+ (statementText.includes('where') &&
615
+ (statementText.includes('user') || statementText.includes('owner')));
616
+ });
617
+ }
618
+
619
+ /**
620
+ * Check Repository query missing user/owner constraint
621
+ */
622
+ function checkRepositorySecurityConstraint(node, context) {
623
+ if (!isRepositoryMethod(node)) return;
624
+
625
+ const hasCustomQueryResult = hasCustomQuery(node);
626
+ const hasUserConstraint = hasUserConstraintInQuery(node);
627
+
628
+ if (hasCustomQueryResult && !hasUserConstraint) {
629
+ context.report({
630
+ node,
631
+ messageId: 'repositoryMissingConstraint',
632
+ });
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Check if method returns Entity directly instead of DTO
638
+ */
639
+ function checkDirectEntityReturn(node, context) {
640
+ if (!isRestEndpoint(node)) return;
641
+
642
+ const returnsEntity = returnsEntityDirectly(node);
643
+
644
+ if (returnsEntity) {
645
+ context.report({
646
+ node,
647
+ messageId: 'directEntityReturn',
648
+ });
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Check if ID field uses sequential number
654
+ */
655
+ function checkSequentialIdUsage(node, context) {
656
+ const isEntityId = isEntityIdField(node);
657
+ const isSequentialType = isSequentialIdType(node);
658
+
659
+ if (isEntityId && isSequentialType) {
660
+ context.report({
661
+ node,
662
+ messageId: 'sequentialIdUsage',
663
+ });
664
+ }
665
+ }
666
+
667
+ /**
668
+ * Check direct repository access without authorization check
669
+ */
670
+ function checkDirectRepositoryAccess(node, context) {
671
+ if (!isRepositoryFindMethod(node)) return;
672
+
673
+ const enclosingFunction = getEnclosingFunction(node);
674
+ if (!enclosingFunction) return;
675
+
676
+ const isInRestControllerResult = isInRestController(enclosingFunction);
677
+ const hasIdParameter = hasPathParameterId(enclosingFunction);
678
+ const hasAuthCheck = hasAuthorizationLogicNearby(node, enclosingFunction);
679
+
680
+ if (isInRestControllerResult && hasIdParameter && !hasAuthCheck) {
681
+ context.report({
682
+ node,
683
+ messageId: 'directRepositoryAccess',
684
+ });
685
+ }
686
+ }
687
+
688
+ // =================== HELPER METHODS ===================
689
+
690
+ /**
691
+ * Check if the method is a Repository method
692
+ */
693
+ function isRepositoryMethod(node) {
694
+ const className = getEnclosingClassName(node);
695
+ if (className) {
696
+ const lowerClassName = className.toLowerCase();
697
+ return lowerClassName.includes('repository') ||
698
+ lowerClassName.includes('service') ||
699
+ lowerClassName.includes('dao') ||
700
+ lowerClassName.includes('model');
701
+ }
702
+
703
+ // Check for repository-related decorators
704
+ if (node.decorators) {
705
+ return node.decorators.some(decorator => {
706
+ if (decorator.expression.type === 'Identifier') {
707
+ const decoratorName = decorator.expression.name.toLowerCase();
708
+ return decoratorName.includes('repository') ||
709
+ decoratorName.includes('injectable') ||
710
+ decoratorName.includes('service');
711
+ }
712
+ return false;
713
+ });
714
+ }
715
+
716
+ return false;
717
+ }
718
+
719
+ /**
720
+ * Check if the method returns Entity directly
721
+ */
722
+ function returnsEntityDirectly(node) {
723
+ // Check TypeScript return type annotation
724
+ if (node.returnType) {
725
+ const returnTypeText = context.getSourceCode().getText(node.returnType).toLowerCase();
726
+
727
+ // Exclude safe types
728
+ if (returnTypeText.includes('dto') ||
729
+ returnTypeText.includes('response') ||
730
+ returnTypeText.includes('view') ||
731
+ returnTypeText.includes('model') ||
732
+ returnTypeText.includes('string') ||
733
+ returnTypeText.includes('number') ||
734
+ returnTypeText.includes('boolean') ||
735
+ returnTypeText.includes('void') ||
736
+ returnTypeText.includes('promise') ||
737
+ returnTypeText.includes('observable')) {
738
+ return false;
739
+ }
740
+
741
+ // Check if it's likely an Entity type
742
+ return isLikelyEntityType(returnTypeText);
743
+ }
744
+
745
+ // Check return statements
746
+ if (node.body && node.body.type === 'BlockStatement') {
747
+ return node.body.body.some(statement => {
748
+ if (statement.type === 'ReturnStatement' && statement.argument) {
749
+ const returnText = context.getSourceCode().getText(statement.argument);
750
+ return !returnText.includes('dto') &&
751
+ !returnText.includes('response') &&
752
+ !returnText.includes('view');
753
+ }
754
+ return false;
755
+ });
756
+ }
757
+
758
+ return false;
759
+ }
760
+
761
+ /**
762
+ * Check if the type is likely an Entity
763
+ */
764
+ function isLikelyEntityType(returnType) {
765
+ // Entity usually starts with uppercase and is not a primitive type
766
+ const cleanType = returnType.replace(/[<>[\]{}]/g, '');
767
+ return /^[A-Z][a-zA-Z]*$/.test(cleanType) &&
768
+ !['Boolean', 'Number', 'String', 'Date', 'Array', 'Object', 'Promise', 'Observable'].includes(cleanType);
769
+ }
770
+
771
+ /**
772
+ * Check if the variable is an Entity ID field
773
+ */
774
+ function isEntityIdField(node) {
775
+ let name;
776
+
777
+ if (node.type === 'VariableDeclarator' && node.id.type === 'Identifier') {
778
+ name = node.id.name.toLowerCase();
779
+ } else if (node.type === 'PropertyDefinition' && node.key.type === 'Identifier') {
780
+ name = node.key.name.toLowerCase();
781
+ } else {
782
+ return false;
783
+ }
784
+
785
+ return name === 'id' || name === '_id' || name.endsWith('id');
786
+ }
787
+
788
+ /**
789
+ * Check if the ID type is sequential
790
+ */
791
+ function isSequentialIdType(node) {
792
+ // Check TypeScript type annotation
793
+ if (node.typeAnnotation) {
794
+ const typeText = context.getSourceCode().getText(node.typeAnnotation).toLowerCase();
795
+ return typeText.includes('number') || typeText.includes('int');
796
+ }
797
+
798
+ // Check initializer value
799
+ if (node.init && node.init.type === 'Literal') {
800
+ return typeof node.init.value === 'number';
801
+ }
802
+
803
+ return false;
804
+ }
805
+
806
+ /**
807
+ * Check if the method invocation is a repository find method
808
+ */
809
+ function isRepositoryFindMethod(node) {
810
+ if (node.type !== 'CallExpression') return false;
811
+
812
+ if (node.callee.type === 'MemberExpression') {
813
+ const property = node.callee.property;
814
+ if (property.type === 'Identifier') {
815
+ const methodName = property.name.toLowerCase();
816
+ return methodName.startsWith('findby') ||
817
+ methodName.startsWith('getby') ||
818
+ methodName.startsWith('find') ||
819
+ methodName.startsWith('get') ||
820
+ methodName.startsWith('delete') ||
821
+ methodName.includes('id');
822
+ }
823
+ }
824
+ return false;
825
+ }
826
+
827
+ /**
828
+ * Check if the method is in a RestController
829
+ */
830
+ function isInRestController(node) {
831
+ const className = getEnclosingClassName(node);
832
+ if (className) {
833
+ const lowerClassName = className.toLowerCase();
834
+ return lowerClassName.includes('controller') ||
835
+ lowerClassName.includes('route') ||
836
+ lowerClassName.includes('handler');
837
+ }
838
+ return false;
839
+ }
840
+
841
+ /**
842
+ * Check if there is authorization logic near the repository call
843
+ */
844
+ function hasAuthorizationLogicNearby(repoCall, method) {
845
+ const enclosingFunction = getEnclosingFunction(repoCall);
846
+ if (!enclosingFunction || !enclosingFunction.body) return false;
847
+
848
+ const bodyNode = enclosingFunction.body;
849
+ if (bodyNode.type !== 'BlockStatement') return false;
850
+
851
+ return bodyNode.body.some(statement => {
852
+ const stmtText = context.getSourceCode().getText(statement).toLowerCase();
853
+ return containsSecurityCheck(stmtText);
854
+ });
855
+ }
856
+
857
+ /**
858
+ * Check if text contains security-related keywords
859
+ */
860
+ function containsSecurityCheck(text) {
861
+ return text.includes('getcurrentuser') ||
862
+ text.includes('req.user') ||
863
+ text.includes('request.user') ||
864
+ text.includes('authentication') ||
865
+ text.includes('checkowner') ||
866
+ text.includes('haspermission') ||
867
+ text.includes('canaccess') ||
868
+ text.includes('isowner') ||
869
+ text.includes('validateaccess') ||
870
+ text.includes('authorize') ||
871
+ text.includes('jwt') ||
872
+ text.includes('token') ||
873
+ text.includes('permission') ||
874
+ text.includes('role') ||
875
+ text.includes('guard');
876
+ }
877
+
878
+ /**
879
+ * Check if the function has manual security check
880
+ */
881
+ function hasManualSecurityCheck(node) {
882
+ let bodyNode;
883
+
884
+ if (node.type === 'CallExpression') {
885
+ // Check callback function in Express.js
886
+ const callback = node.arguments.find(arg =>
887
+ arg.type === 'FunctionExpression' ||
888
+ arg.type === 'ArrowFunctionExpression'
889
+ );
890
+ if (callback && callback.body) {
891
+ bodyNode = callback.body;
892
+ }
893
+ } else if (node.body) {
894
+ bodyNode = node.body;
895
+ }
896
+
897
+ if (!bodyNode) return false;
898
+
899
+ // If arrow function with expression body
900
+ if (bodyNode.type !== 'BlockStatement') {
901
+ const bodyText = context.getSourceCode().getText(bodyNode).toLowerCase();
902
+ return containsSecurityCheck(bodyText);
903
+ }
904
+
905
+ // Check block statement
906
+ return bodyNode.body.some(statement => {
907
+ const statementText = context.getSourceCode().getText(statement).toLowerCase();
908
+ return containsSecurityCheck(statementText);
909
+ });
910
+ }
911
+
912
+ // =================== UTILITY METHODS ===================
913
+
914
+ /**
915
+ * Get enclosing function of a node
916
+ */
917
+ function getEnclosingFunction(node) {
918
+ let parent = node.parent;
919
+ while (parent) {
920
+ if (parent.type === 'FunctionDeclaration' ||
921
+ parent.type === 'MethodDefinition' ||
922
+ parent.type === 'FunctionExpression' ||
923
+ parent.type === 'ArrowFunctionExpression') {
924
+ return parent;
925
+ }
926
+ parent = parent.parent;
927
+ }
928
+ return null;
929
+ }
930
+
931
+ /**
932
+ * Get enclosing class name
933
+ */
934
+ function getEnclosingClassName(node) {
935
+ let parent = node.parent;
936
+ while (parent) {
937
+ if (parent.type === 'ClassDeclaration' && parent.id) {
938
+ return parent.id.name;
939
+ }
940
+ parent = parent.parent;
941
+ }
942
+ return null;
943
+ }
944
+ }
945
+ };