eslint-plugin-ai-guard 1.2.0 → 1.2.2

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.mjs CHANGED
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/rules/reliability/no-empty-catch.ts
2
9
  import { ESLintUtils } from "@typescript-eslint/utils";
3
10
  var createRule = ESLintUtils.RuleCreator(
@@ -1933,6 +1940,25 @@ import { ESLintUtils as ESLintUtils13, AST_NODE_TYPES as AST_NODE_TYPES12 } from
1933
1940
  var createRule13 = ESLintUtils13.RuleCreator(
1934
1941
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
1935
1942
  );
1943
+ var INTERNAL_FILE_PATTERNS = [
1944
+ /[\\/]debug[\\/]/i,
1945
+ /[\\/]scripts[\\/]/i,
1946
+ /[\\/]tools[\\/]/i,
1947
+ /[\\/]migrations?[\\/]/i,
1948
+ /[\\/]seeds?[\\/]/i,
1949
+ /[\\/]fixtures[\\/]/i,
1950
+ /check[-_]server/i,
1951
+ /debug[-_]server/i,
1952
+ /diagnostics/i,
1953
+ /seed\./i,
1954
+ /migrate?\./i,
1955
+ /setup\./i,
1956
+ /scaffold\./i,
1957
+ /test[-_].*\.js$/i
1958
+ ];
1959
+ function isInternalToolingFile(filePath) {
1960
+ return INTERNAL_FILE_PATTERNS.some((p) => p.test(filePath));
1961
+ }
1936
1962
  var UNTRUSTED_IDENTIFIER_NAMES = [
1937
1963
  "input",
1938
1964
  "userInput",
@@ -1974,11 +2000,13 @@ var noUnsafeDeserialize = createRule13({
1974
2000
  fixable: void 0,
1975
2001
  schema: [],
1976
2002
  messages: {
1977
- unsafeDeserialize: "Potential unsafe deserialization: JSON.parse() is used on likely untrusted input without visible schema validation. AI tools frequently generate this shortcut. Validate input before parsing."
2003
+ unsafeDeserialize: "JSON.parse() on likely untrusted input without visible schema validation. Validate or sanitize the input before parsing to prevent deserialization of unexpected data."
1978
2004
  }
1979
2005
  },
1980
2006
  defaultOptions: [],
1981
2007
  create(context) {
2008
+ const filePath = context.filename ?? context.getFilename?.() ?? "";
2009
+ if (isInternalToolingFile(filePath)) return {};
1982
2010
  return {
1983
2011
  CallExpression(node) {
1984
2012
  if (!isJsonParseCall(node)) {
@@ -2035,8 +2063,8 @@ var DEFAULT_AUTH_MIDDLEWARE_NAMES = /* @__PURE__ */ new Set([
2035
2063
  "requireSession"
2036
2064
  ]);
2037
2065
  var PUBLIC_ROUTE_PATTERNS = [
2038
- /^\/?$/,
2039
- // '/' root
2066
+ /^\/?\s*$/,
2067
+ // '/' root or empty
2040
2068
  /^\*$/,
2041
2069
  // '*' SPA fallback
2042
2070
  /^\/\*$/,
@@ -2047,12 +2075,20 @@ var PUBLIC_ROUTE_PATTERNS = [
2047
2075
  // ping
2048
2076
  /^\/status/,
2049
2077
  // status
2078
+ /^\/metrics/,
2079
+ // Prometheus metrics
2080
+ /^\/ready/,
2081
+ // readiness probe
2082
+ /^\/live/,
2083
+ // liveness probe
2050
2084
  /^\/api\/v\d+\/auth/,
2051
- // auth routes
2085
+ // versioned auth routes
2052
2086
  /^\/auth/,
2053
2087
  // auth routes
2054
2088
  /^\/login/,
2055
2089
  // login
2090
+ /^\/logout/,
2091
+ // logout
2056
2092
  /^\/register/,
2057
2093
  // register
2058
2094
  /^\/signup/,
@@ -2061,8 +2097,10 @@ var PUBLIC_ROUTE_PATTERNS = [
2061
2097
  // forgot password
2062
2098
  /^\/reset/,
2063
2099
  // reset password
2100
+ /^\/verify/,
2101
+ // email verification
2064
2102
  /^\/webhook/,
2065
- // webhooks
2103
+ // webhooks (typically verified by signature)
2066
2104
  /^\/callback/,
2067
2105
  // OAuth callback
2068
2106
  /^\/public/,
@@ -2079,15 +2117,62 @@ var PUBLIC_ROUTE_PATTERNS = [
2079
2117
  // sitemap
2080
2118
  /^\/api\/docs/,
2081
2119
  // API documentation (Swagger, etc.)
2082
- /^\/docs/
2120
+ /^\/docs/,
2083
2121
  // documentation
2122
+ /^\/swagger/,
2123
+ // Swagger UI
2124
+ /^\/graphql/,
2125
+ // GraphQL (handles own auth)
2126
+ /^\/debug/,
2127
+ // debug endpoints
2128
+ /^\/diagnostics/,
2129
+ // diagnostics endpoints
2130
+ /^\/internal/,
2131
+ // internal routes
2132
+ /^\/dev/
2133
+ // dev-only routes
2134
+ ];
2135
+ var ELECTRON_PATH_PATTERNS = [
2136
+ /[\\/]electron[\\/]/i,
2137
+ /[\\/]electron-main/i,
2138
+ /[\\/]electron-preload/i,
2139
+ /\bmain\.js$/,
2140
+ /\bpreload\.js$/,
2141
+ /\bbackground\.js$/,
2142
+ /\belectron\.js$/
2143
+ ];
2144
+ var INTERNAL_SCRIPT_PATTERNS = [
2145
+ /[\\/]scripts[\\/]/i,
2146
+ /[\\/]tools[\\/]/i,
2147
+ /[\\/]migrations?[\\/]/i,
2148
+ /[\\/]seeds?[\\/]/i,
2149
+ /[\\/]fixtures[\\/]/i,
2150
+ /[\\/]dev[\\/]/i,
2151
+ /[\\/]debug[\\/]/i,
2152
+ /[\\/]diagnostics[\\/]/i,
2153
+ /\bdebug[-_]server/i,
2154
+ /\bdev[-_]server/i,
2155
+ /\bcheck[-_]server/i,
2156
+ /\blocal[-_]server/i,
2157
+ /\bseed\./i,
2158
+ /\bmigrate?\./i,
2159
+ /\bsetup\./i,
2160
+ /\bscaffold\./i,
2161
+ /\binit\./i,
2162
+ /\bbootstrap\./i
2084
2163
  ];
2164
+ function isElectronFile(filePath) {
2165
+ return ELECTRON_PATH_PATTERNS.some((p) => p.test(filePath));
2166
+ }
2167
+ function isInternalScript(filePath) {
2168
+ return INTERNAL_SCRIPT_PATTERNS.some((p) => p.test(filePath));
2169
+ }
2085
2170
  var requireAuthMiddleware = createRule14({
2086
2171
  name: "require-auth-middleware",
2087
2172
  meta: {
2088
2173
  type: "suggestion",
2089
2174
  docs: {
2090
- 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."
2175
+ description: "Require authentication middleware on Express/Fastify route definitions. AI tools frequently generate route handlers without auth middleware, creating unprotected endpoints."
2091
2176
  },
2092
2177
  fixable: void 0,
2093
2178
  schema: [
@@ -2098,19 +2183,45 @@ var requireAuthMiddleware = createRule14({
2098
2183
  type: "array",
2099
2184
  items: { type: "string" },
2100
2185
  description: "Additional custom middleware names to recognize as authentication middleware."
2186
+ },
2187
+ allowElectronApps: {
2188
+ type: "boolean",
2189
+ description: "Suppress findings in Electron main/preload files (default: true). Electron localhost routes do not need web auth middleware."
2190
+ },
2191
+ allowInternalScripts: {
2192
+ type: "boolean",
2193
+ description: "Suppress findings in internal tooling files (migration, seed, debug, dev-server). These are never production-exposed (default: true)."
2194
+ },
2195
+ internalPathPatterns: {
2196
+ type: "array",
2197
+ items: { type: "string" },
2198
+ description: "Additional file path patterns to treat as internal (suppressed)."
2101
2199
  }
2102
2200
  },
2103
2201
  additionalProperties: false
2104
2202
  }
2105
2203
  ],
2106
2204
  messages: {
2107
- 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."
2205
+ missingAuth: "Route `{{method}} {{path}}` appears to lack authentication middleware. Verify that auth is applied at the router level, or add middleware (e.g., `protect`, `authenticate`) to the handler chain."
2108
2206
  }
2109
2207
  },
2110
2208
  defaultOptions: [{}],
2111
2209
  create(context, [options]) {
2210
+ const allowElectronApps = options.allowElectronApps !== false;
2211
+ const allowInternalScripts = options.allowInternalScripts !== false;
2112
2212
  const customNames = new Set(options.authMiddlewareNames ?? []);
2113
2213
  const allAuthNames = /* @__PURE__ */ new Set([...DEFAULT_AUTH_MIDDLEWARE_NAMES, ...customNames]);
2214
+ const extraPatterns = (options.internalPathPatterns ?? []).map((p) => new RegExp(p, "i"));
2215
+ const filePath = context.filename ?? context.getFilename?.() ?? "";
2216
+ if (allowElectronApps && isElectronFile(filePath)) {
2217
+ return {};
2218
+ }
2219
+ if (allowInternalScripts && isInternalScript(filePath)) {
2220
+ return {};
2221
+ }
2222
+ if (extraPatterns.some((p) => p.test(filePath))) {
2223
+ return {};
2224
+ }
2114
2225
  let hasRouterUseAuth = false;
2115
2226
  return {
2116
2227
  CallExpression(node) {
@@ -2229,6 +2340,24 @@ var AUTHZ_HELPER_NAMES = [
2229
2340
  "canModify",
2230
2341
  "hasAccess"
2231
2342
  ];
2343
+ var ELECTRON_PATH_PATTERNS2 = [
2344
+ /[\\/]electron[\\/]/i,
2345
+ /[\\/]electron-main/i,
2346
+ /preload\.js$/,
2347
+ /background\.js$/
2348
+ ];
2349
+ var INTERNAL_SCRIPT_PATTERNS2 = [
2350
+ /[\\/]scripts[\\/]/i,
2351
+ /[\\/]migrations?[\\/]/i,
2352
+ /[\\/]seeds?[\\/]/i,
2353
+ /[\\/]debug[\\/]/i,
2354
+ /seed\./i,
2355
+ /migrate?\./i,
2356
+ /setup\./i
2357
+ ];
2358
+ function isElectronOrInternalFile(filePath) {
2359
+ return ELECTRON_PATH_PATTERNS2.some((p) => p.test(filePath)) || INTERNAL_SCRIPT_PATTERNS2.some((p) => p.test(filePath));
2360
+ }
2232
2361
  function isRouteRegistrationCall(node) {
2233
2362
  if (node.callee.type !== AST_NODE_TYPES14.MemberExpression || node.callee.property.type !== AST_NODE_TYPES14.Identifier) {
2234
2363
  return false;
@@ -2269,29 +2398,29 @@ function getMemberPath(node) {
2269
2398
  }
2270
2399
  return [...objectPath, node.property.name];
2271
2400
  }
2272
- function hasPathPrefix(path3, prefix) {
2273
- if (!path3 || path3.length < prefix.length) {
2401
+ function hasPathPrefix(path4, prefix) {
2402
+ if (!path4 || path4.length < prefix.length) {
2274
2403
  return false;
2275
2404
  }
2276
2405
  for (let i = 0; i < prefix.length; i += 1) {
2277
- if (path3[i] !== prefix[i]) {
2406
+ if (path4[i] !== prefix[i]) {
2278
2407
  return false;
2279
2408
  }
2280
2409
  }
2281
2410
  return true;
2282
2411
  }
2283
- function isLikelyResourceIdPath(path3) {
2284
- if (!path3 || path3.length < 3) {
2412
+ function isLikelyResourceIdPath(path4) {
2413
+ if (!path4 || path4.length < 3) {
2285
2414
  return false;
2286
2415
  }
2287
- if (!hasPathPrefix(path3, ["req", "params"]) && !hasPathPrefix(path3, ["req", "body"]) && !hasPathPrefix(path3, ["req", "query"])) {
2416
+ if (!hasPathPrefix(path4, ["req", "params"]) && !hasPathPrefix(path4, ["req", "body"]) && !hasPathPrefix(path4, ["req", "query"])) {
2288
2417
  return false;
2289
2418
  }
2290
- const last = path3[path3.length - 1].toLowerCase();
2419
+ const last = path4[path4.length - 1].toLowerCase();
2291
2420
  return last === "id" || last.endsWith("id");
2292
2421
  }
2293
- function isReqUserPath(path3) {
2294
- return hasPathPrefix(path3, ["req", "user"]);
2422
+ function isReqUserPath(path4) {
2423
+ return hasPathPrefix(path4, ["req", "user"]);
2295
2424
  }
2296
2425
  function containsAuthorizationHelper(node) {
2297
2426
  if (node.type === AST_NODE_TYPES14.CallExpression && (node.callee.type === AST_NODE_TYPES14.Identifier && AUTHZ_HELPER_NAMES.includes(node.callee.name) || node.callee.type === AST_NODE_TYPES14.MemberExpression && node.callee.property.type === AST_NODE_TYPES14.Identifier && AUTHZ_HELPER_NAMES.includes(node.callee.property.name))) {
@@ -2330,8 +2459,8 @@ function collectBodySignals(node) {
2330
2459
  }
2331
2460
  }
2332
2461
  if (current.type === AST_NODE_TYPES14.MemberExpression) {
2333
- const path3 = getMemberPath(current);
2334
- if (isLikelyResourceIdPath(path3)) {
2462
+ const path4 = getMemberPath(current);
2463
+ if (isLikelyResourceIdPath(path4)) {
2335
2464
  hasResourceIdAccess = true;
2336
2465
  }
2337
2466
  }
@@ -2367,11 +2496,13 @@ var requireAuthzCheck = createRule15({
2367
2496
  fixable: void 0,
2368
2497
  schema: [],
2369
2498
  messages: {
2370
- missingAuthz: "Potential missing authorization check. This handler uses resource identifiers (like req.params.id) but no visible ownership/authorization guard was found. AI tools frequently miss authz after auth."
2499
+ missingAuthz: "Handler accesses resource identifiers (e.g., req.params.id) without a visible authorization check. Verify ownership or permission is enforced before the resource is returned or modified."
2371
2500
  }
2372
2501
  },
2373
2502
  defaultOptions: [],
2374
2503
  create(context) {
2504
+ const filePath = context.filename ?? context.getFilename?.() ?? "";
2505
+ if (isElectronOrInternalFile(filePath)) return {};
2375
2506
  return {
2376
2507
  CallExpression(node) {
2377
2508
  if (!isRouteRegistrationCall(node)) {
@@ -2788,10 +2919,23 @@ var security = {
2788
2919
  var security_default = security;
2789
2920
 
2790
2921
  // src/index.ts
2922
+ import { createRequire } from "module";
2923
+ import { fileURLToPath } from "url";
2924
+ import path3 from "path";
2925
+ var pkgVersion = "1.2.2";
2926
+ try {
2927
+ const require_ = typeof __require !== "undefined" ? __require : createRequire(import.meta.url);
2928
+ const isESM = typeof __dirname === "undefined";
2929
+ const dirname = isESM ? path3.dirname(fileURLToPath(import.meta.url)) : __dirname;
2930
+ const pkgPath = path3.resolve(dirname, dirname.endsWith("src") ? "../package.json" : "../../package.json");
2931
+ const pkg = require_(pkgPath);
2932
+ if (pkg && pkg.version) pkgVersion = pkg.version;
2933
+ } catch {
2934
+ }
2791
2935
  var plugin = {
2792
2936
  meta: {
2793
2937
  name: "eslint-plugin-ai-guard",
2794
- version: "1.2.0"
2938
+ version: pkgVersion
2795
2939
  },
2796
2940
  rules: allRules,
2797
2941
  configs: {