eslint-plugin-ai-guard 1.0.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,928 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ default: () => index_default,
24
+ recommended: () => recommended_default,
25
+ rules: () => allRules,
26
+ security: () => security_default,
27
+ strict: () => strict_default
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/rules/error-handling/no-empty-catch.ts
32
+ var import_utils = require("@typescript-eslint/utils");
33
+ var createRule = import_utils.ESLintUtils.RuleCreator(
34
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
35
+ );
36
+ var noEmptyCatch = createRule({
37
+ name: "no-empty-catch",
38
+ meta: {
39
+ type: "problem",
40
+ docs: {
41
+ description: "Disallow empty catch blocks. AI tools frequently generate try/catch with empty catch bodies that silently swallow errors, hiding failures in production."
42
+ },
43
+ fixable: void 0,
44
+ schema: [],
45
+ messages: {
46
+ emptyCatch: "Catch block is empty. AI tools frequently generate empty catch blocks that silently swallow errors. Handle the error (log, rethrow, or recover), or add a comment explaining why it is intentionally empty."
47
+ }
48
+ },
49
+ defaultOptions: [],
50
+ create(context) {
51
+ return {
52
+ CatchClause(node) {
53
+ if (node.body.body.length === 0) {
54
+ const sourceCode = context.sourceCode;
55
+ const comments = sourceCode.getCommentsInside(node.body);
56
+ if (comments.length > 0) {
57
+ return;
58
+ }
59
+ context.report({
60
+ node,
61
+ messageId: "emptyCatch"
62
+ });
63
+ }
64
+ }
65
+ };
66
+ }
67
+ });
68
+
69
+ // src/rules/error-handling/no-broad-exception.ts
70
+ var import_utils2 = require("@typescript-eslint/utils");
71
+ var createRule2 = import_utils2.ESLintUtils.RuleCreator(
72
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
73
+ );
74
+ var noBroadException = createRule2({
75
+ name: "no-broad-exception",
76
+ meta: {
77
+ type: "suggestion",
78
+ docs: {
79
+ description: "Disallow overly broad catch clause types such as `catch (e: any)` or `catch (e: unknown)` without narrowing. AI tools frequently use `any` type annotations on catch parameters, hiding the actual error type and preventing proper error handling."
80
+ },
81
+ fixable: void 0,
82
+ schema: [],
83
+ messages: {
84
+ broadException: "Catch parameter has an overly broad type annotation `{{type}}`. AI tools frequently generate `catch (e: any)` which hides the real error type. Use a specific error type or narrow with `instanceof` checks inside the catch block."
85
+ }
86
+ },
87
+ defaultOptions: [],
88
+ create(context) {
89
+ return {
90
+ CatchClause(node) {
91
+ const param = node.param;
92
+ if (!param) return;
93
+ if (param.type === import_utils2.AST_NODE_TYPES.Identifier && param.typeAnnotation && param.typeAnnotation.typeAnnotation) {
94
+ const typeAnnotation = param.typeAnnotation.typeAnnotation;
95
+ if (typeAnnotation.type === import_utils2.AST_NODE_TYPES.TSAnyKeyword) {
96
+ context.report({
97
+ node: param,
98
+ messageId: "broadException",
99
+ data: { type: "any" }
100
+ });
101
+ return;
102
+ }
103
+ if (typeAnnotation.type === import_utils2.AST_NODE_TYPES.TSUnknownKeyword) {
104
+ const hasNarrowing = checkForNarrowing(node, param.name);
105
+ if (!hasNarrowing) {
106
+ context.report({
107
+ node: param,
108
+ messageId: "broadException",
109
+ data: { type: "unknown" }
110
+ });
111
+ }
112
+ }
113
+ }
114
+ }
115
+ };
116
+ }
117
+ });
118
+ function checkForNarrowing(catchClause, paramName) {
119
+ const bodyStatements = catchClause.body.body;
120
+ for (const stmt of bodyStatements) {
121
+ if (containsInstanceofCheck(stmt, paramName)) {
122
+ return true;
123
+ }
124
+ }
125
+ return false;
126
+ }
127
+ function containsInstanceofCheck(node, paramName) {
128
+ if (!node || typeof node !== "object") return false;
129
+ if (node.type === import_utils2.AST_NODE_TYPES.BinaryExpression && node.operator === "instanceof" && node.left?.type === import_utils2.AST_NODE_TYPES.Identifier && node.left.name === paramName) {
130
+ return true;
131
+ }
132
+ for (const key of Object.keys(node)) {
133
+ if (key === "parent") continue;
134
+ const child = node[key];
135
+ if (Array.isArray(child)) {
136
+ for (const item of child) {
137
+ if (containsInstanceofCheck(item, paramName)) return true;
138
+ }
139
+ } else if (child && typeof child === "object" && child.type) {
140
+ if (containsInstanceofCheck(child, paramName)) return true;
141
+ }
142
+ }
143
+ return false;
144
+ }
145
+
146
+ // src/rules/async/no-async-array-callback.ts
147
+ var import_utils3 = require("@typescript-eslint/utils");
148
+ var createRule3 = import_utils3.ESLintUtils.RuleCreator(
149
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
150
+ );
151
+ var ARRAY_CALLBACK_METHODS = ["map", "filter", "forEach", "reduce", "flatMap", "find", "findIndex", "some", "every"];
152
+ var PROMISE_COMBINATORS = ["all", "allSettled", "race", "any"];
153
+ function isWrappedInPromiseCombinator(node, context) {
154
+ const ancestors = context.sourceCode.getAncestors(node);
155
+ for (let i = ancestors.length - 1; i >= 0; i--) {
156
+ const ancestor = ancestors[i];
157
+ if (ancestor.type === import_utils3.AST_NODE_TYPES.CallExpression && ancestor.callee.type === import_utils3.AST_NODE_TYPES.MemberExpression && ancestor.callee.object.type === import_utils3.AST_NODE_TYPES.Identifier && ancestor.callee.object.name === "Promise" && ancestor.callee.property.type === import_utils3.AST_NODE_TYPES.Identifier && PROMISE_COMBINATORS.includes(
158
+ ancestor.callee.property.name
159
+ )) {
160
+ return true;
161
+ }
162
+ if (ancestor.type === import_utils3.AST_NODE_TYPES.CallExpression) {
163
+ break;
164
+ }
165
+ }
166
+ return false;
167
+ }
168
+ function isAsyncCallback(node) {
169
+ return (node.type === import_utils3.AST_NODE_TYPES.FunctionExpression || node.type === import_utils3.AST_NODE_TYPES.ArrowFunctionExpression) && node.async;
170
+ }
171
+ var noAsyncArrayCallback = createRule3({
172
+ name: "no-async-array-callback",
173
+ meta: {
174
+ type: "problem",
175
+ docs: {
176
+ description: "Disallow async callbacks in array iteration methods (map, filter, forEach, reduce). AI tools generate `array.map(async ...)` which returns `Promise[]` instead of resolved values \u2014 a silent bug."
177
+ },
178
+ fixable: void 0,
179
+ schema: [],
180
+ messages: {
181
+ asyncArrayCallback: "Async callback passed to Array.{{method}}(). This returns an array of Promises, not resolved values. AI tools frequently generate this pattern. Wrap with `await Promise.all(array.{{method}}(...))` or use a for...of loop."
182
+ }
183
+ },
184
+ defaultOptions: [],
185
+ create(context) {
186
+ return {
187
+ CallExpression(node) {
188
+ if (node.callee.type !== import_utils3.AST_NODE_TYPES.MemberExpression || node.callee.property.type !== import_utils3.AST_NODE_TYPES.Identifier) {
189
+ return;
190
+ }
191
+ const methodName = node.callee.property.name;
192
+ if (!ARRAY_CALLBACK_METHODS.includes(
193
+ methodName
194
+ )) {
195
+ return;
196
+ }
197
+ const callback = node.arguments[0];
198
+ if (!callback) {
199
+ return;
200
+ }
201
+ if (isAsyncCallback(callback)) {
202
+ if (isWrappedInPromiseCombinator(node, context)) {
203
+ return;
204
+ }
205
+ context.report({
206
+ node: callback,
207
+ messageId: "asyncArrayCallback",
208
+ data: {
209
+ method: methodName
210
+ }
211
+ });
212
+ }
213
+ }
214
+ };
215
+ }
216
+ });
217
+
218
+ // src/rules/async/no-floating-promise.ts
219
+ var import_utils4 = require("@typescript-eslint/utils");
220
+ var createRule4 = import_utils4.ESLintUtils.RuleCreator(
221
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
222
+ );
223
+ function looksLikeAsyncCall(node) {
224
+ if (node.callee.type === import_utils4.AST_NODE_TYPES.Identifier) {
225
+ return hasAsyncishName(node.callee.name);
226
+ }
227
+ if (node.callee.type === import_utils4.AST_NODE_TYPES.MemberExpression && node.callee.property.type === import_utils4.AST_NODE_TYPES.Identifier) {
228
+ return hasAsyncishName(node.callee.property.name);
229
+ }
230
+ return false;
231
+ }
232
+ function hasAsyncishName(name) {
233
+ const lower = name.toLowerCase();
234
+ return lower.startsWith("fetch") || lower.startsWith("load") || lower.startsWith("save") || lower.startsWith("delete") || lower.startsWith("create") || lower.startsWith("update") || lower.startsWith("get") || lower.startsWith("post") || lower.startsWith("put") || lower.startsWith("patch") || lower.startsWith("send") || lower.startsWith("upload") || lower.startsWith("download") || lower.startsWith("connect") || lower.startsWith("disconnect") || lower.startsWith("subscribe") || lower.startsWith("publish") || lower.startsWith("request") || lower.startsWith("query") || lower === "axios" || lower === "fetch";
235
+ }
236
+ function isCalleeAsync(node, asyncFunctionNames) {
237
+ if (node.callee.type === import_utils4.AST_NODE_TYPES.Identifier) {
238
+ if (asyncFunctionNames.has(node.callee.name)) {
239
+ return true;
240
+ }
241
+ }
242
+ return looksLikeAsyncCall(node);
243
+ }
244
+ function isExpressionHandled(node) {
245
+ const expr = node.expression;
246
+ if (expr.type === import_utils4.AST_NODE_TYPES.CallExpression) {
247
+ if (expr.callee.type === import_utils4.AST_NODE_TYPES.MemberExpression) {
248
+ const prop = expr.callee.property;
249
+ if (prop.type === import_utils4.AST_NODE_TYPES.Identifier) {
250
+ if (prop.name === "then" || prop.name === "catch" || prop.name === "finally") {
251
+ return true;
252
+ }
253
+ }
254
+ }
255
+ }
256
+ if (expr.type === import_utils4.AST_NODE_TYPES.UnaryExpression && expr.operator === "void") {
257
+ return true;
258
+ }
259
+ return false;
260
+ }
261
+ var noFloatingPromise = createRule4({
262
+ name: "no-floating-promise",
263
+ meta: {
264
+ type: "problem",
265
+ docs: {
266
+ description: "Disallow calling an async function or Promise-returning function without awaiting or handling the result. AI tools frequently generate floating promises where errors disappear silently."
267
+ },
268
+ fixable: void 0,
269
+ schema: [],
270
+ messages: {
271
+ floatingPromise: "This async call is not awaited, returned, or error-handled (.catch). AI tools frequently generate floating promises, causing errors to be silently lost. Add `await`, `.catch()`, or assign the result."
272
+ }
273
+ },
274
+ defaultOptions: [],
275
+ create(context) {
276
+ const asyncFunctionNames = /* @__PURE__ */ new Set();
277
+ return {
278
+ // Track async function declarations
279
+ FunctionDeclaration(node) {
280
+ if (node.async && node.id) {
281
+ asyncFunctionNames.add(node.id.name);
282
+ }
283
+ },
284
+ // Track async arrow functions assigned to variables
285
+ VariableDeclarator(node) {
286
+ if (node.init && (node.init.type === import_utils4.AST_NODE_TYPES.ArrowFunctionExpression || node.init.type === import_utils4.AST_NODE_TYPES.FunctionExpression) && node.init.async && node.id.type === import_utils4.AST_NODE_TYPES.Identifier) {
287
+ asyncFunctionNames.add(node.id.name);
288
+ }
289
+ },
290
+ // Check ExpressionStatements — standalone call expressions
291
+ ExpressionStatement(node) {
292
+ if (node.expression.type !== import_utils4.AST_NODE_TYPES.CallExpression) {
293
+ return;
294
+ }
295
+ if (isExpressionHandled(node)) {
296
+ return;
297
+ }
298
+ const callExpr = node.expression;
299
+ if (isCalleeAsync(callExpr, asyncFunctionNames)) {
300
+ context.report({
301
+ node,
302
+ messageId: "floatingPromise"
303
+ });
304
+ }
305
+ }
306
+ };
307
+ }
308
+ });
309
+
310
+ // src/rules/async/no-await-in-loop.ts
311
+ var import_utils5 = require("@typescript-eslint/utils");
312
+ var createRule5 = import_utils5.ESLintUtils.RuleCreator(
313
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
314
+ );
315
+ var LOOP_TYPES = /* @__PURE__ */ new Set([
316
+ import_utils5.AST_NODE_TYPES.ForStatement,
317
+ import_utils5.AST_NODE_TYPES.ForInStatement,
318
+ import_utils5.AST_NODE_TYPES.ForOfStatement,
319
+ import_utils5.AST_NODE_TYPES.WhileStatement,
320
+ import_utils5.AST_NODE_TYPES.DoWhileStatement
321
+ ]);
322
+ var noAwaitInLoop = createRule5({
323
+ name: "no-await-in-loop",
324
+ meta: {
325
+ type: "suggestion",
326
+ docs: {
327
+ description: "Disallow `await` inside loops. AI tools frequently generate sequential awaits inside for/while loops where `Promise.all()` would be more efficient and correct. Each iteration waits for the previous one to complete, causing O(n) latency instead of O(1)."
328
+ },
329
+ fixable: void 0,
330
+ schema: [],
331
+ messages: {
332
+ awaitInLoop: "Unexpected `await` inside a {{loopType}}. AI tools frequently generate sequential awaits in loops, causing O(n) latency. Consider collecting promises and using `Promise.all()` for parallel execution."
333
+ }
334
+ },
335
+ defaultOptions: [],
336
+ create(context) {
337
+ const functionBoundary = [];
338
+ function enterFunction(node) {
339
+ functionBoundary.push(node);
340
+ }
341
+ function exitFunction() {
342
+ functionBoundary.pop();
343
+ }
344
+ return {
345
+ FunctionDeclaration: enterFunction,
346
+ FunctionExpression: enterFunction,
347
+ ArrowFunctionExpression: enterFunction,
348
+ "FunctionDeclaration:exit": exitFunction,
349
+ "FunctionExpression:exit": exitFunction,
350
+ "ArrowFunctionExpression:exit": exitFunction,
351
+ AwaitExpression(node) {
352
+ const ancestors = context.sourceCode.getAncestors(node);
353
+ const currentFunction = functionBoundary[functionBoundary.length - 1];
354
+ for (let i = ancestors.length - 1; i >= 0; i--) {
355
+ const ancestor = ancestors[i];
356
+ if (ancestor === currentFunction) {
357
+ break;
358
+ }
359
+ if (LOOP_TYPES.has(ancestor.type)) {
360
+ const loopName = getLoopName(ancestor.type);
361
+ context.report({
362
+ node,
363
+ messageId: "awaitInLoop",
364
+ data: { loopType: loopName }
365
+ });
366
+ return;
367
+ }
368
+ }
369
+ }
370
+ };
371
+ }
372
+ });
373
+ function getLoopName(type) {
374
+ switch (type) {
375
+ case import_utils5.AST_NODE_TYPES.ForStatement:
376
+ return "for loop";
377
+ case import_utils5.AST_NODE_TYPES.ForInStatement:
378
+ return "for...in loop";
379
+ case import_utils5.AST_NODE_TYPES.ForOfStatement:
380
+ return "for...of loop";
381
+ case import_utils5.AST_NODE_TYPES.WhileStatement:
382
+ return "while loop";
383
+ case import_utils5.AST_NODE_TYPES.DoWhileStatement:
384
+ return "do...while loop";
385
+ default:
386
+ return "loop";
387
+ }
388
+ }
389
+
390
+ // src/rules/security/no-hardcoded-secret.ts
391
+ var import_utils6 = require("@typescript-eslint/utils");
392
+ var createRule6 = import_utils6.ESLintUtils.RuleCreator(
393
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
394
+ );
395
+ var SECRET_NAME_PATTERN = /(?:secret|password|passwd|api[_-]?key|auth[_-]?token|access[_-]?token|private[_-]?key|client[_-]?secret|jwt[_-]?secret|encryption[_-]?key|signing[_-]?key)/i;
396
+ var FALSE_POSITIVE_VALUES = /* @__PURE__ */ new Set([
397
+ "password",
398
+ "secret",
399
+ "token",
400
+ "key",
401
+ "api_key",
402
+ "apikey",
403
+ "",
404
+ "undefined",
405
+ "null",
406
+ "test",
407
+ "example",
408
+ "placeholder",
409
+ "changeme",
410
+ "your-api-key",
411
+ "your-secret",
412
+ "YOUR_API_KEY",
413
+ "YOUR_SECRET",
414
+ "xxx",
415
+ "TODO"
416
+ ]);
417
+ var noHardcodedSecret = createRule6({
418
+ name: "no-hardcoded-secret",
419
+ meta: {
420
+ type: "problem",
421
+ docs: {
422
+ description: "Disallow hardcoded secrets, API keys, passwords, and tokens in source code. AI tools frequently generate placeholder credentials that get committed to version control, creating security vulnerabilities."
423
+ },
424
+ fixable: void 0,
425
+ schema: [],
426
+ messages: {
427
+ hardcodedSecret: "Possible hardcoded secret in variable `{{name}}`. AI tools frequently generate placeholder credentials that get committed to version control. Use environment variables (e.g., `process.env.{{envName}}`) instead."
428
+ }
429
+ },
430
+ defaultOptions: [],
431
+ create(context) {
432
+ return {
433
+ VariableDeclarator(node) {
434
+ if (!node.init) return;
435
+ if (!node.id || node.id.type !== import_utils6.AST_NODE_TYPES.Identifier) return;
436
+ const varName = node.id.name;
437
+ if (!SECRET_NAME_PATTERN.test(varName)) return;
438
+ const value = getStringValue(node.init);
439
+ if (value === null) return;
440
+ if (FALSE_POSITIVE_VALUES.has(value)) return;
441
+ if (value.length < 8) return;
442
+ if (isProcessEnvAccess(node.init)) return;
443
+ context.report({
444
+ node: node.init,
445
+ messageId: "hardcodedSecret",
446
+ data: {
447
+ name: varName,
448
+ envName: toEnvVarName(varName)
449
+ }
450
+ });
451
+ },
452
+ // Also check: obj.secret = 'literal_value'
453
+ AssignmentExpression(node) {
454
+ if (node.left.type !== import_utils6.AST_NODE_TYPES.MemberExpression) return;
455
+ if (node.left.property.type !== import_utils6.AST_NODE_TYPES.Identifier) return;
456
+ const propName = node.left.property.name;
457
+ if (!SECRET_NAME_PATTERN.test(propName)) return;
458
+ const value = getStringValue(node.right);
459
+ if (value === null) return;
460
+ if (FALSE_POSITIVE_VALUES.has(value)) return;
461
+ if (value.length < 8) return;
462
+ if (isProcessEnvAccess(node.right)) return;
463
+ context.report({
464
+ node: node.right,
465
+ messageId: "hardcodedSecret",
466
+ data: {
467
+ name: propName,
468
+ envName: toEnvVarName(propName)
469
+ }
470
+ });
471
+ },
472
+ // Check property assignments in object literals: { secret: 'value' }
473
+ Property(node) {
474
+ if (node.key.type !== import_utils6.AST_NODE_TYPES.Identifier) return;
475
+ const propName = node.key.name;
476
+ if (!SECRET_NAME_PATTERN.test(propName)) return;
477
+ const value = getStringValue(node.value);
478
+ if (value === null) return;
479
+ if (FALSE_POSITIVE_VALUES.has(value)) return;
480
+ if (value.length < 8) return;
481
+ if (isProcessEnvAccess(node.value)) return;
482
+ context.report({
483
+ node: node.value,
484
+ messageId: "hardcodedSecret",
485
+ data: {
486
+ name: propName,
487
+ envName: toEnvVarName(propName)
488
+ }
489
+ });
490
+ }
491
+ };
492
+ }
493
+ });
494
+ function getStringValue(node) {
495
+ if (node.type === import_utils6.AST_NODE_TYPES.Literal && typeof node.value === "string") {
496
+ return node.value;
497
+ }
498
+ if (node.type === import_utils6.AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0 && node.quasis.length === 1) {
499
+ return node.quasis[0].value.cooked ?? null;
500
+ }
501
+ return null;
502
+ }
503
+ function isProcessEnvAccess(node) {
504
+ return node.type === import_utils6.AST_NODE_TYPES.MemberExpression && node.object.type === import_utils6.AST_NODE_TYPES.MemberExpression && node.object.object.type === import_utils6.AST_NODE_TYPES.Identifier && node.object.object.name === "process" && node.object.property.type === import_utils6.AST_NODE_TYPES.Identifier && node.object.property.name === "env";
505
+ }
506
+ function toEnvVarName(name) {
507
+ return name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]/g, "_").toUpperCase();
508
+ }
509
+
510
+ // src/rules/security/no-eval-dynamic.ts
511
+ var import_utils7 = require("@typescript-eslint/utils");
512
+ var createRule7 = import_utils7.ESLintUtils.RuleCreator(
513
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
514
+ );
515
+ var noEvalDynamic = createRule7({
516
+ name: "no-eval-dynamic",
517
+ meta: {
518
+ type: "problem",
519
+ docs: {
520
+ description: "Disallow `eval()` and `new Function()` with non-literal arguments. AI tools frequently generate dynamic code evaluation patterns that create serious security vulnerabilities including code injection and XSS."
521
+ },
522
+ fixable: void 0,
523
+ schema: [],
524
+ messages: {
525
+ evalDynamic: "Dangerous use of `{{callee}}` with a dynamic argument. AI tools frequently generate `eval()` or `new Function()` patterns that enable code injection attacks. Use safer alternatives like JSON.parse(), a lookup table, or a sandboxed evaluator.",
526
+ newFunctionDynamic: "Dangerous use of `new Function()` with a dynamic argument. AI tools frequently generate this pattern which enables arbitrary code execution. Use safer alternatives like a lookup table or pre-defined functions."
527
+ }
528
+ },
529
+ defaultOptions: [],
530
+ create(context) {
531
+ return {
532
+ CallExpression(node) {
533
+ if (node.callee.type === import_utils7.AST_NODE_TYPES.Identifier && node.callee.name === "eval") {
534
+ if (node.arguments.length === 0) return;
535
+ const firstArg = node.arguments[0];
536
+ if (isLiteral(firstArg)) return;
537
+ context.report({
538
+ node,
539
+ messageId: "evalDynamic",
540
+ data: { callee: "eval()" }
541
+ });
542
+ return;
543
+ }
544
+ if (node.callee.type === import_utils7.AST_NODE_TYPES.MemberExpression && node.callee.property.type === import_utils7.AST_NODE_TYPES.Identifier && node.callee.property.name === "eval" && node.callee.object.type === import_utils7.AST_NODE_TYPES.Identifier && (node.callee.object.name === "window" || node.callee.object.name === "globalThis")) {
545
+ if (node.arguments.length === 0) return;
546
+ const firstArg = node.arguments[0];
547
+ if (isLiteral(firstArg)) return;
548
+ context.report({
549
+ node,
550
+ messageId: "evalDynamic",
551
+ data: { callee: `${node.callee.object.name}.eval()` }
552
+ });
553
+ }
554
+ },
555
+ NewExpression(node) {
556
+ if (node.callee.type === import_utils7.AST_NODE_TYPES.Identifier && node.callee.name === "Function") {
557
+ if (node.arguments.length === 0) return;
558
+ const allLiteral = node.arguments.every(isLiteral);
559
+ if (allLiteral) return;
560
+ context.report({
561
+ node,
562
+ messageId: "newFunctionDynamic"
563
+ });
564
+ }
565
+ }
566
+ };
567
+ }
568
+ });
569
+ function isLiteral(node) {
570
+ if (node.type === import_utils7.AST_NODE_TYPES.Literal) return true;
571
+ if (node.type === import_utils7.AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0) {
572
+ return true;
573
+ }
574
+ return false;
575
+ }
576
+
577
+ // src/rules/security/no-sql-string-concat.ts
578
+ var import_utils8 = require("@typescript-eslint/utils");
579
+ var createRule8 = import_utils8.ESLintUtils.RuleCreator(
580
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
581
+ );
582
+ var SQL_KEYWORDS = /\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|EXECUTE|UNION|WHERE|FROM|JOIN|INTO|VALUES|SET|TABLE|DATABASE|GRANT|REVOKE|TRUNCATE)\b/i;
583
+ var noSqlStringConcat = createRule8({
584
+ name: "no-sql-string-concat",
585
+ meta: {
586
+ type: "problem",
587
+ docs: {
588
+ description: "Disallow string concatenation or interpolation with variables in SQL query contexts. AI tools frequently generate SQL queries using template literals or string concatenation with user input, creating SQL injection vulnerabilities."
589
+ },
590
+ fixable: void 0,
591
+ schema: [],
592
+ messages: {
593
+ sqlStringConcat: 'Potential SQL injection: string concatenation or interpolation detected in a SQL query. AI tools frequently generate this pattern. Use parameterized queries (e.g., `db.query("SELECT * FROM users WHERE id = $1", [id])`) instead.'
594
+ }
595
+ },
596
+ defaultOptions: [],
597
+ create(context) {
598
+ return {
599
+ // Template literals: `SELECT * FROM users WHERE id = ${userId}`
600
+ TemplateLiteral(node) {
601
+ if (node.expressions.length === 0) return;
602
+ const staticText = node.quasis.map((q) => q.value.raw).join("");
603
+ if (SQL_KEYWORDS.test(staticText)) {
604
+ context.report({
605
+ node,
606
+ messageId: "sqlStringConcat"
607
+ });
608
+ }
609
+ },
610
+ // Binary expressions: "SELECT * FROM users WHERE id = " + userId
611
+ BinaryExpression(node) {
612
+ if (node.operator !== "+") return;
613
+ const leftStr = getStringLiteralValue(node.left);
614
+ const rightStr = getStringLiteralValue(node.right);
615
+ if (leftStr && SQL_KEYWORDS.test(leftStr) || rightStr && SQL_KEYWORDS.test(rightStr)) {
616
+ const leftIsDynamic = !isStaticString(node.left);
617
+ const rightIsDynamic = !isStaticString(node.right);
618
+ if (leftIsDynamic || rightIsDynamic) {
619
+ context.report({
620
+ node,
621
+ messageId: "sqlStringConcat"
622
+ });
623
+ }
624
+ }
625
+ }
626
+ };
627
+ }
628
+ });
629
+ function getStringLiteralValue(node) {
630
+ if (node.type === import_utils8.AST_NODE_TYPES.Literal && typeof node.value === "string") {
631
+ return node.value;
632
+ }
633
+ return null;
634
+ }
635
+ function isStaticString(node) {
636
+ if (node.type === import_utils8.AST_NODE_TYPES.Literal && typeof node.value === "string") {
637
+ return true;
638
+ }
639
+ if (node.type === import_utils8.AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0) {
640
+ return true;
641
+ }
642
+ return false;
643
+ }
644
+
645
+ // src/rules/security/require-auth-middleware.ts
646
+ var import_utils9 = require("@typescript-eslint/utils");
647
+ var createRule9 = import_utils9.ESLintUtils.RuleCreator(
648
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
649
+ );
650
+ var HTTP_METHODS = /* @__PURE__ */ new Set([
651
+ "get",
652
+ "post",
653
+ "put",
654
+ "patch",
655
+ "delete",
656
+ "all",
657
+ "options",
658
+ "head"
659
+ ]);
660
+ var DEFAULT_AUTH_MIDDLEWARE_NAMES = /* @__PURE__ */ new Set([
661
+ "authenticate",
662
+ "requireAuth",
663
+ "isAuthenticated",
664
+ "verifyToken",
665
+ "protect",
666
+ "authorized",
667
+ "authorize",
668
+ "isAdmin",
669
+ "ensureAuthenticated",
670
+ "ensureLoggedIn",
671
+ "auth",
672
+ "authMiddleware",
673
+ "requireLogin",
674
+ "checkAuth",
675
+ "validateToken",
676
+ "passport.authenticate",
677
+ "jwt",
678
+ "requireSession"
679
+ ]);
680
+ var PUBLIC_ROUTE_PATTERNS = [
681
+ /^\/?$/,
682
+ // '/' root
683
+ /^\/health/,
684
+ // health checks
685
+ /^\/ping/,
686
+ // ping
687
+ /^\/status/,
688
+ // status
689
+ /^\/api\/v\d+\/auth/,
690
+ // auth routes
691
+ /^\/auth/,
692
+ // auth routes
693
+ /^\/login/,
694
+ // login
695
+ /^\/register/,
696
+ // register
697
+ /^\/signup/,
698
+ // signup
699
+ /^\/forgot/,
700
+ // forgot password
701
+ /^\/reset/,
702
+ // reset password
703
+ /^\/webhook/,
704
+ // webhooks
705
+ /^\/public/,
706
+ // explicitly public
707
+ /^\/assets/,
708
+ // static assets
709
+ /^\/static/,
710
+ // static files
711
+ /^\/favicon/,
712
+ // favicon
713
+ /^\/robots/,
714
+ // robots.txt
715
+ /^\/sitemap/
716
+ // sitemap
717
+ ];
718
+ var requireAuthMiddleware = createRule9({
719
+ name: "require-auth-middleware",
720
+ meta: {
721
+ type: "suggestion",
722
+ docs: {
723
+ description: "Require authentication middleware on Express/Fastify route definitions. AI tools frequently generate route handlers without auth middleware, creating unprotected endpoints that expose sensitive data or operations."
724
+ },
725
+ fixable: void 0,
726
+ schema: [
727
+ {
728
+ type: "object",
729
+ properties: {
730
+ authMiddlewareNames: {
731
+ type: "array",
732
+ items: { type: "string" },
733
+ description: "Additional custom middleware names to recognize as authentication middleware."
734
+ }
735
+ },
736
+ additionalProperties: false
737
+ }
738
+ ],
739
+ messages: {
740
+ missingAuth: "Route `{{method}} {{path}}` appears to have no authentication middleware. AI tools frequently generate routes without auth checks, creating unprotected endpoints. Add authentication middleware (e.g., `protect`, `authenticate`) to the handler chain."
741
+ }
742
+ },
743
+ defaultOptions: [{}],
744
+ create(context, [options]) {
745
+ const customNames = new Set(options.authMiddlewareNames ?? []);
746
+ const allAuthNames = /* @__PURE__ */ new Set([...DEFAULT_AUTH_MIDDLEWARE_NAMES, ...customNames]);
747
+ let hasRouterUseAuth = false;
748
+ return {
749
+ CallExpression(node) {
750
+ if (isRouterUseAuth(node, allAuthNames)) {
751
+ hasRouterUseAuth = true;
752
+ return;
753
+ }
754
+ if (!isRouteDefinition(node)) return;
755
+ if (hasRouterUseAuth) return;
756
+ const callee = node.callee;
757
+ const method = callee.property.name;
758
+ const args = node.arguments;
759
+ if (args.length < 2) return;
760
+ const pathArg = args[0];
761
+ const pathStr = getPathString(pathArg);
762
+ if (pathStr && isPublicRoute(pathStr)) return;
763
+ const middlewareArgs = args.slice(1, -1);
764
+ if (middlewareArgs.length === 0 && args.length === 2) {
765
+ context.report({
766
+ node,
767
+ messageId: "missingAuth",
768
+ data: {
769
+ method: method.toUpperCase(),
770
+ path: pathStr || "<dynamic>"
771
+ }
772
+ });
773
+ return;
774
+ }
775
+ const hasAuth = middlewareArgs.some((arg) => isAuthMiddleware(arg, allAuthNames));
776
+ if (!hasAuth) {
777
+ context.report({
778
+ node,
779
+ messageId: "missingAuth",
780
+ data: {
781
+ method: method.toUpperCase(),
782
+ path: pathStr || "<dynamic>"
783
+ }
784
+ });
785
+ }
786
+ }
787
+ };
788
+ }
789
+ });
790
+ function isRouteDefinition(node) {
791
+ if (node.callee.type !== import_utils9.AST_NODE_TYPES.MemberExpression) return false;
792
+ if (node.callee.property.type !== import_utils9.AST_NODE_TYPES.Identifier) return false;
793
+ const methodName = node.callee.property.name;
794
+ if (!HTTP_METHODS.has(methodName)) return false;
795
+ const obj = node.callee.object;
796
+ if (obj.type === import_utils9.AST_NODE_TYPES.Identifier) {
797
+ const name = obj.name.toLowerCase();
798
+ return name === "router" || name === "app" || name.includes("router");
799
+ }
800
+ if (obj.type === import_utils9.AST_NODE_TYPES.CallExpression) return true;
801
+ return false;
802
+ }
803
+ function isRouterUseAuth(node, authNames) {
804
+ if (node.callee.type !== import_utils9.AST_NODE_TYPES.MemberExpression) return false;
805
+ if (node.callee.property.type !== import_utils9.AST_NODE_TYPES.Identifier) return false;
806
+ if (node.callee.property.name !== "use") return false;
807
+ return node.arguments.some((arg) => isAuthMiddleware(arg, authNames));
808
+ }
809
+ function isAuthMiddleware(node, authNames) {
810
+ if (node.type === import_utils9.AST_NODE_TYPES.Identifier) {
811
+ return authNames.has(node.name);
812
+ }
813
+ if (node.type === import_utils9.AST_NODE_TYPES.CallExpression) {
814
+ if (node.callee.type === import_utils9.AST_NODE_TYPES.Identifier) {
815
+ return authNames.has(node.callee.name);
816
+ }
817
+ if (node.callee.type === import_utils9.AST_NODE_TYPES.MemberExpression && node.callee.object.type === import_utils9.AST_NODE_TYPES.Identifier && node.callee.property.type === import_utils9.AST_NODE_TYPES.Identifier) {
818
+ const fullName = `${node.callee.object.name}.${node.callee.property.name}`;
819
+ return authNames.has(fullName) || authNames.has(node.callee.property.name);
820
+ }
821
+ }
822
+ return false;
823
+ }
824
+ function getPathString(node) {
825
+ if (node.type === import_utils9.AST_NODE_TYPES.Literal && typeof node.value === "string") {
826
+ return node.value;
827
+ }
828
+ if (node.type === import_utils9.AST_NODE_TYPES.TemplateLiteral) {
829
+ if (node.expressions.length === 0 && node.quasis.length === 1) {
830
+ return node.quasis[0].value.cooked ?? null;
831
+ }
832
+ if (node.quasis.length > 0) {
833
+ return node.quasis[0].value.cooked ?? null;
834
+ }
835
+ }
836
+ return null;
837
+ }
838
+ function isPublicRoute(pathStr) {
839
+ return PUBLIC_ROUTE_PATTERNS.some((pattern) => pattern.test(pathStr));
840
+ }
841
+
842
+ // src/rules/index.ts
843
+ var allRules = {
844
+ "no-empty-catch": noEmptyCatch,
845
+ "no-broad-exception": noBroadException,
846
+ "no-async-array-callback": noAsyncArrayCallback,
847
+ "no-floating-promise": noFloatingPromise,
848
+ "no-await-in-loop": noAwaitInLoop,
849
+ "no-hardcoded-secret": noHardcodedSecret,
850
+ "no-eval-dynamic": noEvalDynamic,
851
+ "no-sql-string-concat": noSqlStringConcat,
852
+ "require-auth-middleware": requireAuthMiddleware
853
+ };
854
+
855
+ // src/configs/recommended.ts
856
+ var recommended = {
857
+ plugins: ["ai-guard"],
858
+ rules: {
859
+ // Error Handling
860
+ "ai-guard/no-empty-catch": "error",
861
+ "ai-guard/no-broad-exception": "warn",
862
+ // Async
863
+ "ai-guard/no-async-array-callback": "error",
864
+ "ai-guard/no-floating-promise": "error",
865
+ "ai-guard/no-await-in-loop": "warn",
866
+ // Security
867
+ "ai-guard/no-hardcoded-secret": "error",
868
+ "ai-guard/no-eval-dynamic": "error",
869
+ "ai-guard/no-sql-string-concat": "error",
870
+ "ai-guard/require-auth-middleware": "warn"
871
+ }
872
+ };
873
+ var recommended_default = recommended;
874
+
875
+ // src/configs/strict.ts
876
+ var strict = {
877
+ plugins: ["ai-guard"],
878
+ rules: {
879
+ // Error Handling - all at error
880
+ "ai-guard/no-empty-catch": "error",
881
+ "ai-guard/no-broad-exception": "error",
882
+ // Async - all at error
883
+ "ai-guard/no-async-array-callback": "error",
884
+ "ai-guard/no-floating-promise": "error",
885
+ "ai-guard/no-await-in-loop": "error",
886
+ // Security - all at error
887
+ "ai-guard/no-hardcoded-secret": "error",
888
+ "ai-guard/no-eval-dynamic": "error",
889
+ "ai-guard/no-sql-string-concat": "error",
890
+ "ai-guard/require-auth-middleware": "error"
891
+ }
892
+ };
893
+ var strict_default = strict;
894
+
895
+ // src/configs/security.ts
896
+ var security = {
897
+ plugins: ["ai-guard"],
898
+ rules: {
899
+ "ai-guard/no-hardcoded-secret": "error",
900
+ "ai-guard/no-eval-dynamic": "error",
901
+ "ai-guard/no-sql-string-concat": "error",
902
+ "ai-guard/require-auth-middleware": "error"
903
+ }
904
+ };
905
+ var security_default = security;
906
+
907
+ // src/index.ts
908
+ var plugin = {
909
+ meta: {
910
+ name: "eslint-plugin-ai-guard",
911
+ version: "1.0.0"
912
+ },
913
+ rules: allRules,
914
+ configs: {
915
+ recommended: recommended_default,
916
+ strict: strict_default,
917
+ security: security_default
918
+ }
919
+ };
920
+ var index_default = plugin;
921
+ // Annotate the CommonJS export names for ESM import in node:
922
+ 0 && (module.exports = {
923
+ recommended,
924
+ rules,
925
+ security,
926
+ strict
927
+ });
928
+ //# sourceMappingURL=index.js.map