@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,300 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ meta: {
5
+ type: "problem",
6
+ docs: {
7
+ description: "Prevent JSON injection attacks and unsafe JSON handling",
8
+ recommended: true,
9
+ },
10
+ schema: [],
11
+ messages: {
12
+ noEvalJson: "Never use eval() to process JSON data - use JSON.parse() instead",
13
+ unsafeJsonParse: "Unsafe JSON parsing - validate input before parsing",
14
+ jsonStringifyInHtml: "JSON.stringify output should be escaped when used in HTML context",
15
+ unsafeTemplateString: "Unsafe template string with user input",
16
+ unsafeDocumentWrite: "JSON.stringify output should be escaped when used in HTML context",
17
+ unsafeStorageParse: "Unsafe JSON parsing - validate input before parsing",
18
+ unsafeUrlParse: "Unsafe JSON parsing - validate input before parsing",
19
+ },
20
+ },
21
+
22
+ create(context) {
23
+ const sourceCode = context.getSourceCode();
24
+
25
+ // Rule C005 - Each function should do one thing
26
+ const validationDetector = createValidationDetector(context);
27
+ const userInputDetector = createUserInputDetector();
28
+ const htmlContextDetector = createHtmlContextDetector(context);
29
+ const jsonParseChecker = createJsonParseChecker(context, userInputDetector, validationDetector);
30
+ const jsonStringifyChecker = createJsonStringifyChecker(context, htmlContextDetector);
31
+ const evalChecker = createEvalChecker(context);
32
+ const templateChecker = createTemplateChecker(context, userInputDetector, htmlContextDetector);
33
+
34
+ return {
35
+ CallExpression(node) {
36
+ jsonParseChecker.checkNode(node);
37
+ jsonStringifyChecker.checkNode(node);
38
+ evalChecker.checkNode(node);
39
+ },
40
+ TemplateLiteral(node) {
41
+ templateChecker.checkNode(node);
42
+ }
43
+ };
44
+ }
45
+ };
46
+
47
+ // Rule C006 - Function names should be verb/verb-noun
48
+ // Rule C031 - Validation logic must be separate
49
+ function createValidationDetector(context) {
50
+ const sourceCode = context.getSourceCode();
51
+
52
+ return {
53
+ hasValidationContext(node) {
54
+ return detectTryCatchContext(node) || detectValidationStatements(node, sourceCode);
55
+ }
56
+ };
57
+ }
58
+
59
+ function detectTryCatchContext(node) {
60
+ let parent = node.parent;
61
+ while (parent) {
62
+ if (parent.type === 'TryStatement') {
63
+ return true;
64
+ }
65
+ parent = parent.parent;
66
+ }
67
+ return false;
68
+ }
69
+
70
+ function detectValidationStatements(node, sourceCode) {
71
+ let parent = node.parent;
72
+ let current = node;
73
+
74
+ while (parent) {
75
+ if (parent.type === 'BlockStatement') {
76
+ const statements = parent.body;
77
+ const currentIndex = statements.indexOf(current);
78
+
79
+ for (let i = 0; i < currentIndex; i++) {
80
+ const stmt = statements[i];
81
+ const stmtText = sourceCode.getText(stmt);
82
+
83
+ if (containsValidationPattern(stmtText)) {
84
+ return true;
85
+ }
86
+ }
87
+ }
88
+
89
+ current = parent;
90
+ parent = parent.parent;
91
+ }
92
+
93
+ return false;
94
+ }
95
+
96
+ function containsValidationPattern(text) {
97
+ const validationPatterns = ['validate', 'typeof', 'length', 'isValid'];
98
+ return validationPatterns.some(pattern => text.includes(pattern));
99
+ }
100
+
101
+ // Rule C015 - Use domain language in class/function names
102
+ function createUserInputDetector() {
103
+ const userInputPatterns = [
104
+ /req\.(body|query|params)/,
105
+ /request\.(body|query|params)/,
106
+ /localStorage\.getItem/,
107
+ /sessionStorage\.getItem/,
108
+ /window\.location/,
109
+ /location\.(search|hash)/,
110
+ /URLSearchParams/
111
+ ];
112
+
113
+ return {
114
+ isUserInput(node, sourceCode) {
115
+ if (!node) return false;
116
+ const text = sourceCode.getText(node);
117
+ return userInputPatterns.some(pattern => pattern.test(text));
118
+ }
119
+ };
120
+ }
121
+
122
+ function createHtmlContextDetector(context) {
123
+ const htmlContextMethods = ['innerHTML', 'outerHTML', 'insertAdjacentHTML'];
124
+
125
+ return {
126
+ isInHtmlContext(node) {
127
+ return detectHtmlAssignment(node, htmlContextMethods) ||
128
+ detectDocumentWrite(node) ||
129
+ detectHtmlTemplate(node, htmlContextMethods);
130
+ }
131
+ };
132
+ }
133
+
134
+ function detectHtmlAssignment(node, htmlContextMethods) {
135
+ let parent = node.parent;
136
+
137
+ while (parent) {
138
+ if (parent.type === 'AssignmentExpression' &&
139
+ parent.left.type === 'MemberExpression' &&
140
+ htmlContextMethods.includes(parent.left.property.name)) {
141
+ return true;
142
+ }
143
+ parent = parent.parent;
144
+ }
145
+ return false;
146
+ }
147
+
148
+ function detectDocumentWrite(node) {
149
+ let parent = node.parent;
150
+
151
+ while (parent) {
152
+ if (parent.type === 'CallExpression' &&
153
+ parent.callee.type === 'MemberExpression' &&
154
+ parent.callee.object.name === 'document' &&
155
+ parent.callee.property.name === 'write') {
156
+ return true;
157
+ }
158
+ parent = parent.parent;
159
+ }
160
+ return false;
161
+ }
162
+
163
+ function detectHtmlTemplate(node, htmlContextMethods) {
164
+ let parent = node.parent;
165
+
166
+ while (parent) {
167
+ if (parent.type === 'TemplateLiteral' &&
168
+ parent.parent &&
169
+ parent.parent.type === 'AssignmentExpression' &&
170
+ parent.parent.left.type === 'MemberExpression' &&
171
+ htmlContextMethods.includes(parent.parent.left.property.name)) {
172
+ return true;
173
+ }
174
+ parent = parent.parent;
175
+ }
176
+ return false;
177
+ }
178
+
179
+ // Rule C012 - Separate Command and Query: Clear single responsibility and side-effects
180
+ function createJsonParseChecker(context, userInputDetector, validationDetector) {
181
+ const sourceCode = context.getSourceCode();
182
+
183
+ return {
184
+ checkNode(node) {
185
+ if (isJsonParseCall(node)) {
186
+ const argument = node.arguments[0];
187
+ if (argument && userInputDetector.isUserInput(argument, sourceCode)) {
188
+ if (!validationDetector.hasValidationContext(node)) {
189
+ reportUnsafeJsonParse(context, node);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ };
195
+ }
196
+
197
+ function isJsonParseCall(node) {
198
+ return node.type === 'CallExpression' &&
199
+ node.callee.type === 'MemberExpression' &&
200
+ node.callee.object.name === 'JSON' &&
201
+ node.callee.property.name === 'parse';
202
+ }
203
+
204
+ function reportUnsafeJsonParse(context, node) {
205
+ context.report({
206
+ node,
207
+ messageId: "unsafeJsonParse"
208
+ });
209
+ }
210
+
211
+ function createJsonStringifyChecker(context, htmlContextDetector) {
212
+ return {
213
+ checkNode(node) {
214
+ if (isJsonStringifyCall(node)) {
215
+ if (htmlContextDetector.isInHtmlContext(node)) {
216
+ reportJsonStringifyInHtml(context, node);
217
+ }
218
+ }
219
+ }
220
+ };
221
+ }
222
+
223
+ function isJsonStringifyCall(node) {
224
+ return node.type === 'CallExpression' &&
225
+ node.callee.type === 'MemberExpression' &&
226
+ node.callee.object.name === 'JSON' &&
227
+ node.callee.property.name === 'stringify';
228
+ }
229
+
230
+ function reportJsonStringifyInHtml(context, node) {
231
+ context.report({
232
+ node,
233
+ messageId: "jsonStringifyInHtml"
234
+ });
235
+ }
236
+
237
+ function createEvalChecker(context) {
238
+ const sourceCode = context.getSourceCode();
239
+
240
+ return {
241
+ checkNode(node) {
242
+ if (isEvalCall(node)) {
243
+ const argument = node.arguments[0];
244
+ if (argument && containsJsonPattern(argument, sourceCode)) {
245
+ reportEvalWithJson(context, node);
246
+ }
247
+ }
248
+ }
249
+ };
250
+ }
251
+
252
+ function isEvalCall(node) {
253
+ return node.type === 'CallExpression' && node.callee.name === 'eval';
254
+ }
255
+
256
+ function containsJsonPattern(argument, sourceCode) {
257
+ const text = sourceCode.getText(argument);
258
+ const jsonPatterns = ['JSON', 'userJson', 'req.', 'request.'];
259
+ return jsonPatterns.some(pattern => text.includes(pattern));
260
+ }
261
+
262
+ function reportEvalWithJson(context, node) {
263
+ context.report({
264
+ node,
265
+ messageId: "noEvalJson"
266
+ });
267
+ }
268
+
269
+ function createTemplateChecker(context, userInputDetector, htmlContextDetector) {
270
+ const sourceCode = context.getSourceCode();
271
+
272
+ return {
273
+ checkNode(node) {
274
+ if (node.type === 'TemplateLiteral') {
275
+ const hasUserInput = node.expressions.some(expr =>
276
+ userInputDetector.isUserInput(expr, sourceCode)
277
+ );
278
+
279
+ if (hasUserInput && isUnsafeHtmlTemplate(node, sourceCode, htmlContextDetector)) {
280
+ reportUnsafeTemplate(context, node);
281
+ }
282
+ }
283
+ }
284
+ };
285
+ }
286
+
287
+ function isUnsafeHtmlTemplate(node, sourceCode, htmlContextDetector) {
288
+ const text = sourceCode.getText(node);
289
+ const htmlPatterns = ['<script>', '<div>', 'innerHTML'];
290
+
291
+ return htmlPatterns.some(pattern => text.includes(pattern)) ||
292
+ htmlContextDetector.isInHtmlContext(node);
293
+ }
294
+
295
+ function reportUnsafeTemplate(context, node) {
296
+ context.report({
297
+ node,
298
+ messageId: "unsafeTemplateString"
299
+ });
300
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Custom ESLint rule for: S025 – Server-side input validation required
3
+ * Rule ID: custom/s025
4
+ * Purpose: Verify that input validation is enforced on a trusted service layer
5
+ * Target: NestJS applications - ensure all client input is validated server-side before processing
6
+ */
7
+
8
+ "use strict";
9
+
10
+ module.exports = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description:
15
+ "Ensure all client input is validated on server-side before processing in NestJS applications",
16
+ recommended: true,
17
+ },
18
+ schema: [],
19
+ messages: {
20
+ missingValidationDecorator:
21
+ "Controller method parameter '{{param}}' should use validation decorators (@Body, @Query, @Param with DTO class).",
22
+ missingValidationPipe:
23
+ "Controller method '{{method}}' should use ValidationPipe to validate input data.",
24
+ missingDtoValidation:
25
+ "DTO class '{{dto}}' should have validation decorators (@IsString, @IsNumber, etc.) for its properties.",
26
+ directInputAccess:
27
+ "Direct access to request object '{{access}}' without validation. Use validated DTOs instead.",
28
+ },
29
+ },
30
+
31
+ create(context) {
32
+ const validatedParams = new Set();
33
+ const controllerMethods = new Set();
34
+ const dtoClasses = new Map();
35
+ const validationDecorators = new Set([
36
+ 'IsString', 'IsNumber', 'IsBoolean', 'IsArray', 'IsObject', 'IsEmail',
37
+ 'IsUrl', 'IsUUID', 'IsOptional', 'IsNotEmpty', 'Length', 'Min', 'Max',
38
+ 'IsPositive', 'IsNegative', 'IsDate', 'IsEnum', 'ValidateNested',
39
+ 'IsInt', 'IsDecimal', 'IsJSON', 'Matches', 'Contains', 'IsAlpha',
40
+ 'IsAlphanumeric', 'IsPhoneNumber', 'IsISO8601', 'IsBase64'
41
+ ]);
42
+
43
+ function hasValidationDecorator(decorators) {
44
+ if (!decorators) return false;
45
+ return decorators.some(decorator => {
46
+ if (decorator.expression && decorator.expression.callee) {
47
+ const name = decorator.expression.callee.name;
48
+ return validationDecorators.has(name);
49
+ }
50
+ return false;
51
+ });
52
+ }
53
+
54
+ function isNestJSController(node) {
55
+ if (!node.decorators) return false;
56
+ return node.decorators.some(decorator => {
57
+ if (decorator.expression && decorator.expression.callee) {
58
+ return decorator.expression.callee.name === 'Controller';
59
+ }
60
+ return false;
61
+ });
62
+ }
63
+
64
+ function isControllerMethod(node) {
65
+ if (!node.decorators) return false;
66
+ const httpDecorators = ['Get', 'Post', 'Put', 'Delete', 'Patch', 'Head', 'Options'];
67
+ return node.decorators.some(decorator => {
68
+ if (decorator.expression && decorator.expression.callee) {
69
+ return httpDecorators.includes(decorator.expression.callee.name);
70
+ }
71
+ return false;
72
+ });
73
+ }
74
+
75
+ function hasNestJSValidationDecorator(param) {
76
+ if (!param.decorators) return false;
77
+ const nestValidationDecorators = ['Body', 'Query', 'Param', 'Headers'];
78
+ return param.decorators.some(decorator => {
79
+ if (decorator.expression && decorator.expression.callee) {
80
+ return nestValidationDecorators.includes(decorator.expression.callee.name);
81
+ }
82
+ return false;
83
+ });
84
+ }
85
+
86
+ function hasValidationPipe(node) {
87
+ if (!node.decorators) return false;
88
+ return node.decorators.some(decorator => {
89
+ if (decorator.expression && decorator.expression.callee) {
90
+ if (decorator.expression.callee.name === 'UsePipes') {
91
+ return decorator.expression.arguments.some(arg => {
92
+ return arg.type === 'NewExpression' &&
93
+ arg.callee &&
94
+ arg.callee.name === 'ValidationPipe';
95
+ });
96
+ }
97
+ }
98
+ return false;
99
+ });
100
+ }
101
+
102
+ return {
103
+ // Check DTO classes for validation decorators
104
+ ClassDeclaration(node) {
105
+ if (node.id && node.id.name.endsWith('Dto')) {
106
+ const className = node.id.name;
107
+ const properties = [];
108
+
109
+ node.body.body.forEach(member => {
110
+ if (member.type === 'PropertyDefinition' && member.key) {
111
+ const hasValidation = hasValidationDecorator(member.decorators);
112
+ properties.push({
113
+ name: member.key.name,
114
+ hasValidation
115
+ });
116
+ }
117
+ });
118
+
119
+ dtoClasses.set(className, properties);
120
+
121
+ // Check if DTO has at least one validation decorator
122
+ const hasAnyValidation = properties.some(prop => prop.hasValidation);
123
+ if (properties.length > 0 && !hasAnyValidation) {
124
+ context.report({
125
+ node,
126
+ messageId: "missingDtoValidation",
127
+ data: {
128
+ dto: className,
129
+ },
130
+ });
131
+ }
132
+ }
133
+ },
134
+
135
+ // Check controller methods
136
+ MethodDefinition(node) {
137
+ const parentClass = node.parent.parent;
138
+ if (isNestJSController(parentClass) && isControllerMethod(node)) {
139
+ const methodName = node.key.name;
140
+ controllerMethods.add(methodName);
141
+
142
+ // Check if method has ValidationPipe
143
+ const hasValidationPipeDecorator = hasValidationPipe(node);
144
+
145
+ // Check parameters for validation decorators
146
+ if (node.value.params) {
147
+ node.value.params.forEach(param => {
148
+ if (param.type === 'Identifier') {
149
+ const hasNestValidation = hasNestJSValidationDecorator(param);
150
+
151
+ if (hasNestValidation) {
152
+ validatedParams.add(param.name);
153
+ } else {
154
+ // If no ValidationPipe and no parameter validation decorator
155
+ if (!hasValidationPipeDecorator) {
156
+ context.report({
157
+ node: param,
158
+ messageId: "missingValidationDecorator",
159
+ data: {
160
+ param: param.name,
161
+ },
162
+ });
163
+ }
164
+ }
165
+ }
166
+ });
167
+ }
168
+
169
+ // Check if method uses ValidationPipe when dealing with request bodies
170
+ const hasBodyParam = node.value.params.some(param =>
171
+ param.decorators && param.decorators.some(decorator =>
172
+ decorator.expression &&
173
+ decorator.expression.callee &&
174
+ decorator.expression.callee.name === 'Body'
175
+ )
176
+ );
177
+
178
+ if (hasBodyParam && !hasValidationPipeDecorator) {
179
+ context.report({
180
+ node,
181
+ messageId: "missingValidationPipe",
182
+ data: {
183
+ method: methodName,
184
+ },
185
+ });
186
+ }
187
+ }
188
+ },
189
+
190
+ // Check for direct access to request object without validation
191
+ MemberExpression(node) {
192
+ if (node.object && node.property) {
193
+ const objectName = node.object.name;
194
+ const propertyName = node.property.name;
195
+
196
+ // Check for direct access to req.body, req.query, req.params without validation
197
+ if (objectName === 'req' || objectName === 'request') {
198
+ if (['body', 'query', 'params', 'headers'].includes(propertyName)) {
199
+ const accessPath = `${objectName}.${propertyName}`;
200
+
201
+ // Check if this access is in a validated context
202
+ if (!validatedParams.has(objectName)) {
203
+ context.report({
204
+ node,
205
+ messageId: "directInputAccess",
206
+ data: {
207
+ access: accessPath,
208
+ },
209
+ });
210
+ }
211
+ }
212
+ }
213
+ }
214
+ },
215
+ };
216
+ },
217
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Custom ESLint rule for: S026 – JSON schema validation required for inputs
3
+ * Rule ID: custom/s026
4
+ * Purpose: Ensure that all input JSON is validated against a schema
5
+ */
6
+
7
+ "use strict";
8
+
9
+ module.exports = {
10
+ meta: {
11
+ type: "problem",
12
+ docs: {
13
+ description:
14
+ "Ensure JSON schema validation is in place for input objects",
15
+ recommended: false,
16
+ },
17
+ schema: [],
18
+ messages: {
19
+ missingValidation:
20
+ "JSON input '{{input}}' should be validated using a JSON schema before use.",
21
+ },
22
+ },
23
+
24
+ create(context) {
25
+ let validatedInputs = new Set();
26
+
27
+ return {
28
+ CallExpression(node) {
29
+ // Detect validation functions like schema.validate(req.body)
30
+ if (
31
+ node.callee &&
32
+ node.callee.property &&
33
+ node.arguments &&
34
+ node.arguments.length > 0
35
+ ) {
36
+ const arg = node.arguments[0];
37
+ if (
38
+ arg.type === "MemberExpression" &&
39
+ arg.object &&
40
+ arg.property &&
41
+ (arg.property.name === "body" || arg.property.name === "query")
42
+ ) {
43
+ const validatedInput = `${arg.object.name}.${arg.property.name}`;
44
+ validatedInputs.add(validatedInput);
45
+ }
46
+ }
47
+ },
48
+
49
+ MemberExpression(node) {
50
+ if (
51
+ node.property &&
52
+ (node.property.name === "body" || node.property.name === "query")
53
+ ) {
54
+ const fullInput = `${node.object.name}.${node.property.name}`;
55
+ if (!validatedInputs.has(fullInput)) {
56
+ context.report({
57
+ node,
58
+ messageId: "missingValidation",
59
+ data: {
60
+ input: fullInput,
61
+ },
62
+ });
63
+ }
64
+ }
65
+ },
66
+ };
67
+ },
68
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Custom ESLint rule for: S027 – No hardcoded secrets
3
+ * Rule ID: custom/s027
4
+ * Purpose: Prevent passwords, API keys, secrets from being hardcoded
5
+ */
6
+
7
+ "use strict";
8
+
9
+ const sensitiveKeywords = [
10
+ "password",
11
+ "pass",
12
+ "pwd",
13
+ "secret",
14
+ "apiKey",
15
+ "token",
16
+ "auth",
17
+ "key",
18
+ "seed",
19
+ ];
20
+
21
+ module.exports = {
22
+ meta: {
23
+ type: "problem",
24
+ docs: {
25
+ description: "Prevent hardcoded passwords, API keys, and secrets",
26
+ recommended: true,
27
+ },
28
+ schema: [],
29
+ messages: {
30
+ hardcodedSecret:
31
+ "Avoid hardcoding sensitive information such as '{{name}}'. Use secure storage instead.",
32
+ },
33
+ },
34
+
35
+ create(context) {
36
+ function isSensitiveName(name) {
37
+ const lower = name.toLowerCase();
38
+ return sensitiveKeywords.some((keyword) => lower.includes(keyword));
39
+ }
40
+
41
+ return {
42
+ VariableDeclarator(node) {
43
+ if (
44
+ node.id &&
45
+ node.init &&
46
+ (node.init.type === "Literal" || node.init.type === "TemplateLiteral")
47
+ ) {
48
+ const name =
49
+ node.id.name || (node.id.property && node.id.property.name);
50
+ if (name && isSensitiveName(name)) {
51
+ context.report({
52
+ node,
53
+ messageId: "hardcodedSecret",
54
+ data: { name },
55
+ });
56
+ }
57
+ }
58
+ },
59
+
60
+ AssignmentExpression(node) {
61
+ if (
62
+ node.left &&
63
+ node.right &&
64
+ (node.right.type === "Literal" ||
65
+ node.right.type === "TemplateLiteral")
66
+ ) {
67
+ const name =
68
+ node.left.name || (node.left.property && node.left.property.name);
69
+ if (name && isSensitiveName(name)) {
70
+ context.report({
71
+ node,
72
+ messageId: "hardcodedSecret",
73
+ data: { name },
74
+ });
75
+ }
76
+ }
77
+ },
78
+ };
79
+ },
80
+ };