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.d.mts CHANGED
@@ -60,6 +60,9 @@ declare const allRules: {
60
60
  };
61
61
  readonly 'require-auth-middleware': _typescript_eslint_utils_ts_eslint.RuleModule<"missingAuth", [{
62
62
  authMiddlewareNames?: string[];
63
+ allowElectronApps?: boolean;
64
+ allowInternalScripts?: boolean;
65
+ internalPathPatterns?: string[];
63
66
  }], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
64
67
  name: string;
65
68
  };
@@ -130,6 +133,9 @@ declare const plugin: {
130
133
  };
131
134
  readonly 'require-auth-middleware': _typescript_eslint_utils_ts_eslint.RuleModule<"missingAuth", [{
132
135
  authMiddlewareNames?: string[];
136
+ allowElectronApps?: boolean;
137
+ allowInternalScripts?: boolean;
138
+ internalPathPatterns?: string[];
133
139
  }], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
134
140
  name: string;
135
141
  };
package/dist/index.d.ts CHANGED
@@ -60,6 +60,9 @@ declare const allRules: {
60
60
  };
61
61
  readonly 'require-auth-middleware': _typescript_eslint_utils_ts_eslint.RuleModule<"missingAuth", [{
62
62
  authMiddlewareNames?: string[];
63
+ allowElectronApps?: boolean;
64
+ allowInternalScripts?: boolean;
65
+ internalPathPatterns?: string[];
63
66
  }], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
64
67
  name: string;
65
68
  };
@@ -130,6 +133,9 @@ declare const plugin: {
130
133
  };
131
134
  readonly 'require-auth-middleware': _typescript_eslint_utils_ts_eslint.RuleModule<"missingAuth", [{
132
135
  authMiddlewareNames?: string[];
136
+ allowElectronApps?: boolean;
137
+ allowInternalScripts?: boolean;
138
+ internalPathPatterns?: string[];
133
139
  }], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
134
140
  name: string;
135
141
  };
package/dist/index.js CHANGED
@@ -1973,6 +1973,25 @@ var import_utils13 = require("@typescript-eslint/utils");
1973
1973
  var createRule13 = import_utils13.ESLintUtils.RuleCreator(
1974
1974
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
1975
1975
  );
1976
+ var INTERNAL_FILE_PATTERNS = [
1977
+ /[\\/]debug[\\/]/i,
1978
+ /[\\/]scripts[\\/]/i,
1979
+ /[\\/]tools[\\/]/i,
1980
+ /[\\/]migrations?[\\/]/i,
1981
+ /[\\/]seeds?[\\/]/i,
1982
+ /[\\/]fixtures[\\/]/i,
1983
+ /check[-_]server/i,
1984
+ /debug[-_]server/i,
1985
+ /diagnostics/i,
1986
+ /seed\./i,
1987
+ /migrate?\./i,
1988
+ /setup\./i,
1989
+ /scaffold\./i,
1990
+ /test[-_].*\.js$/i
1991
+ ];
1992
+ function isInternalToolingFile(filePath) {
1993
+ return INTERNAL_FILE_PATTERNS.some((p) => p.test(filePath));
1994
+ }
1976
1995
  var UNTRUSTED_IDENTIFIER_NAMES = [
1977
1996
  "input",
1978
1997
  "userInput",
@@ -2014,11 +2033,13 @@ var noUnsafeDeserialize = createRule13({
2014
2033
  fixable: void 0,
2015
2034
  schema: [],
2016
2035
  messages: {
2017
- 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."
2036
+ unsafeDeserialize: "JSON.parse() on likely untrusted input without visible schema validation. Validate or sanitize the input before parsing to prevent deserialization of unexpected data."
2018
2037
  }
2019
2038
  },
2020
2039
  defaultOptions: [],
2021
2040
  create(context) {
2041
+ const filePath = context.filename ?? context.getFilename?.() ?? "";
2042
+ if (isInternalToolingFile(filePath)) return {};
2022
2043
  return {
2023
2044
  CallExpression(node) {
2024
2045
  if (!isJsonParseCall(node)) {
@@ -2075,8 +2096,8 @@ var DEFAULT_AUTH_MIDDLEWARE_NAMES = /* @__PURE__ */ new Set([
2075
2096
  "requireSession"
2076
2097
  ]);
2077
2098
  var PUBLIC_ROUTE_PATTERNS = [
2078
- /^\/?$/,
2079
- // '/' root
2099
+ /^\/?\s*$/,
2100
+ // '/' root or empty
2080
2101
  /^\*$/,
2081
2102
  // '*' SPA fallback
2082
2103
  /^\/\*$/,
@@ -2087,12 +2108,20 @@ var PUBLIC_ROUTE_PATTERNS = [
2087
2108
  // ping
2088
2109
  /^\/status/,
2089
2110
  // status
2111
+ /^\/metrics/,
2112
+ // Prometheus metrics
2113
+ /^\/ready/,
2114
+ // readiness probe
2115
+ /^\/live/,
2116
+ // liveness probe
2090
2117
  /^\/api\/v\d+\/auth/,
2091
- // auth routes
2118
+ // versioned auth routes
2092
2119
  /^\/auth/,
2093
2120
  // auth routes
2094
2121
  /^\/login/,
2095
2122
  // login
2123
+ /^\/logout/,
2124
+ // logout
2096
2125
  /^\/register/,
2097
2126
  // register
2098
2127
  /^\/signup/,
@@ -2101,8 +2130,10 @@ var PUBLIC_ROUTE_PATTERNS = [
2101
2130
  // forgot password
2102
2131
  /^\/reset/,
2103
2132
  // reset password
2133
+ /^\/verify/,
2134
+ // email verification
2104
2135
  /^\/webhook/,
2105
- // webhooks
2136
+ // webhooks (typically verified by signature)
2106
2137
  /^\/callback/,
2107
2138
  // OAuth callback
2108
2139
  /^\/public/,
@@ -2119,15 +2150,62 @@ var PUBLIC_ROUTE_PATTERNS = [
2119
2150
  // sitemap
2120
2151
  /^\/api\/docs/,
2121
2152
  // API documentation (Swagger, etc.)
2122
- /^\/docs/
2153
+ /^\/docs/,
2123
2154
  // documentation
2155
+ /^\/swagger/,
2156
+ // Swagger UI
2157
+ /^\/graphql/,
2158
+ // GraphQL (handles own auth)
2159
+ /^\/debug/,
2160
+ // debug endpoints
2161
+ /^\/diagnostics/,
2162
+ // diagnostics endpoints
2163
+ /^\/internal/,
2164
+ // internal routes
2165
+ /^\/dev/
2166
+ // dev-only routes
2124
2167
  ];
2168
+ var ELECTRON_PATH_PATTERNS = [
2169
+ /[\\/]electron[\\/]/i,
2170
+ /[\\/]electron-main/i,
2171
+ /[\\/]electron-preload/i,
2172
+ /\bmain\.js$/,
2173
+ /\bpreload\.js$/,
2174
+ /\bbackground\.js$/,
2175
+ /\belectron\.js$/
2176
+ ];
2177
+ var INTERNAL_SCRIPT_PATTERNS = [
2178
+ /[\\/]scripts[\\/]/i,
2179
+ /[\\/]tools[\\/]/i,
2180
+ /[\\/]migrations?[\\/]/i,
2181
+ /[\\/]seeds?[\\/]/i,
2182
+ /[\\/]fixtures[\\/]/i,
2183
+ /[\\/]dev[\\/]/i,
2184
+ /[\\/]debug[\\/]/i,
2185
+ /[\\/]diagnostics[\\/]/i,
2186
+ /\bdebug[-_]server/i,
2187
+ /\bdev[-_]server/i,
2188
+ /\bcheck[-_]server/i,
2189
+ /\blocal[-_]server/i,
2190
+ /\bseed\./i,
2191
+ /\bmigrate?\./i,
2192
+ /\bsetup\./i,
2193
+ /\bscaffold\./i,
2194
+ /\binit\./i,
2195
+ /\bbootstrap\./i
2196
+ ];
2197
+ function isElectronFile(filePath) {
2198
+ return ELECTRON_PATH_PATTERNS.some((p) => p.test(filePath));
2199
+ }
2200
+ function isInternalScript(filePath) {
2201
+ return INTERNAL_SCRIPT_PATTERNS.some((p) => p.test(filePath));
2202
+ }
2125
2203
  var requireAuthMiddleware = createRule14({
2126
2204
  name: "require-auth-middleware",
2127
2205
  meta: {
2128
2206
  type: "suggestion",
2129
2207
  docs: {
2130
- 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."
2208
+ description: "Require authentication middleware on Express/Fastify route definitions. AI tools frequently generate route handlers without auth middleware, creating unprotected endpoints."
2131
2209
  },
2132
2210
  fixable: void 0,
2133
2211
  schema: [
@@ -2138,19 +2216,45 @@ var requireAuthMiddleware = createRule14({
2138
2216
  type: "array",
2139
2217
  items: { type: "string" },
2140
2218
  description: "Additional custom middleware names to recognize as authentication middleware."
2219
+ },
2220
+ allowElectronApps: {
2221
+ type: "boolean",
2222
+ description: "Suppress findings in Electron main/preload files (default: true). Electron localhost routes do not need web auth middleware."
2223
+ },
2224
+ allowInternalScripts: {
2225
+ type: "boolean",
2226
+ description: "Suppress findings in internal tooling files (migration, seed, debug, dev-server). These are never production-exposed (default: true)."
2227
+ },
2228
+ internalPathPatterns: {
2229
+ type: "array",
2230
+ items: { type: "string" },
2231
+ description: "Additional file path patterns to treat as internal (suppressed)."
2141
2232
  }
2142
2233
  },
2143
2234
  additionalProperties: false
2144
2235
  }
2145
2236
  ],
2146
2237
  messages: {
2147
- 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."
2238
+ 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."
2148
2239
  }
2149
2240
  },
2150
2241
  defaultOptions: [{}],
2151
2242
  create(context, [options]) {
2243
+ const allowElectronApps = options.allowElectronApps !== false;
2244
+ const allowInternalScripts = options.allowInternalScripts !== false;
2152
2245
  const customNames = new Set(options.authMiddlewareNames ?? []);
2153
2246
  const allAuthNames = /* @__PURE__ */ new Set([...DEFAULT_AUTH_MIDDLEWARE_NAMES, ...customNames]);
2247
+ const extraPatterns = (options.internalPathPatterns ?? []).map((p) => new RegExp(p, "i"));
2248
+ const filePath = context.filename ?? context.getFilename?.() ?? "";
2249
+ if (allowElectronApps && isElectronFile(filePath)) {
2250
+ return {};
2251
+ }
2252
+ if (allowInternalScripts && isInternalScript(filePath)) {
2253
+ return {};
2254
+ }
2255
+ if (extraPatterns.some((p) => p.test(filePath))) {
2256
+ return {};
2257
+ }
2154
2258
  let hasRouterUseAuth = false;
2155
2259
  return {
2156
2260
  CallExpression(node) {
@@ -2269,6 +2373,24 @@ var AUTHZ_HELPER_NAMES = [
2269
2373
  "canModify",
2270
2374
  "hasAccess"
2271
2375
  ];
2376
+ var ELECTRON_PATH_PATTERNS2 = [
2377
+ /[\\/]electron[\\/]/i,
2378
+ /[\\/]electron-main/i,
2379
+ /preload\.js$/,
2380
+ /background\.js$/
2381
+ ];
2382
+ var INTERNAL_SCRIPT_PATTERNS2 = [
2383
+ /[\\/]scripts[\\/]/i,
2384
+ /[\\/]migrations?[\\/]/i,
2385
+ /[\\/]seeds?[\\/]/i,
2386
+ /[\\/]debug[\\/]/i,
2387
+ /seed\./i,
2388
+ /migrate?\./i,
2389
+ /setup\./i
2390
+ ];
2391
+ function isElectronOrInternalFile(filePath) {
2392
+ return ELECTRON_PATH_PATTERNS2.some((p) => p.test(filePath)) || INTERNAL_SCRIPT_PATTERNS2.some((p) => p.test(filePath));
2393
+ }
2272
2394
  function isRouteRegistrationCall(node) {
2273
2395
  if (node.callee.type !== import_utils15.AST_NODE_TYPES.MemberExpression || node.callee.property.type !== import_utils15.AST_NODE_TYPES.Identifier) {
2274
2396
  return false;
@@ -2309,29 +2431,29 @@ function getMemberPath(node) {
2309
2431
  }
2310
2432
  return [...objectPath, node.property.name];
2311
2433
  }
2312
- function hasPathPrefix(path3, prefix) {
2313
- if (!path3 || path3.length < prefix.length) {
2434
+ function hasPathPrefix(path4, prefix) {
2435
+ if (!path4 || path4.length < prefix.length) {
2314
2436
  return false;
2315
2437
  }
2316
2438
  for (let i = 0; i < prefix.length; i += 1) {
2317
- if (path3[i] !== prefix[i]) {
2439
+ if (path4[i] !== prefix[i]) {
2318
2440
  return false;
2319
2441
  }
2320
2442
  }
2321
2443
  return true;
2322
2444
  }
2323
- function isLikelyResourceIdPath(path3) {
2324
- if (!path3 || path3.length < 3) {
2445
+ function isLikelyResourceIdPath(path4) {
2446
+ if (!path4 || path4.length < 3) {
2325
2447
  return false;
2326
2448
  }
2327
- if (!hasPathPrefix(path3, ["req", "params"]) && !hasPathPrefix(path3, ["req", "body"]) && !hasPathPrefix(path3, ["req", "query"])) {
2449
+ if (!hasPathPrefix(path4, ["req", "params"]) && !hasPathPrefix(path4, ["req", "body"]) && !hasPathPrefix(path4, ["req", "query"])) {
2328
2450
  return false;
2329
2451
  }
2330
- const last = path3[path3.length - 1].toLowerCase();
2452
+ const last = path4[path4.length - 1].toLowerCase();
2331
2453
  return last === "id" || last.endsWith("id");
2332
2454
  }
2333
- function isReqUserPath(path3) {
2334
- return hasPathPrefix(path3, ["req", "user"]);
2455
+ function isReqUserPath(path4) {
2456
+ return hasPathPrefix(path4, ["req", "user"]);
2335
2457
  }
2336
2458
  function containsAuthorizationHelper(node) {
2337
2459
  if (node.type === import_utils15.AST_NODE_TYPES.CallExpression && (node.callee.type === import_utils15.AST_NODE_TYPES.Identifier && AUTHZ_HELPER_NAMES.includes(node.callee.name) || node.callee.type === import_utils15.AST_NODE_TYPES.MemberExpression && node.callee.property.type === import_utils15.AST_NODE_TYPES.Identifier && AUTHZ_HELPER_NAMES.includes(node.callee.property.name))) {
@@ -2370,8 +2492,8 @@ function collectBodySignals(node) {
2370
2492
  }
2371
2493
  }
2372
2494
  if (current.type === import_utils15.AST_NODE_TYPES.MemberExpression) {
2373
- const path3 = getMemberPath(current);
2374
- if (isLikelyResourceIdPath(path3)) {
2495
+ const path4 = getMemberPath(current);
2496
+ if (isLikelyResourceIdPath(path4)) {
2375
2497
  hasResourceIdAccess = true;
2376
2498
  }
2377
2499
  }
@@ -2407,11 +2529,13 @@ var requireAuthzCheck = createRule15({
2407
2529
  fixable: void 0,
2408
2530
  schema: [],
2409
2531
  messages: {
2410
- 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."
2532
+ 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."
2411
2533
  }
2412
2534
  },
2413
2535
  defaultOptions: [],
2414
2536
  create(context) {
2537
+ const filePath = context.filename ?? context.getFilename?.() ?? "";
2538
+ if (isElectronOrInternalFile(filePath)) return {};
2415
2539
  return {
2416
2540
  CallExpression(node) {
2417
2541
  if (!isRouteRegistrationCall(node)) {
@@ -2828,10 +2952,24 @@ var security = {
2828
2952
  var security_default = security;
2829
2953
 
2830
2954
  // src/index.ts
2955
+ var import_module = require("module");
2956
+ var import_url = require("url");
2957
+ var import_path3 = __toESM(require("path"));
2958
+ var import_meta = {};
2959
+ var pkgVersion = "1.2.2";
2960
+ try {
2961
+ const require_ = typeof require !== "undefined" ? require : (0, import_module.createRequire)(import_meta.url);
2962
+ const isESM = typeof __dirname === "undefined";
2963
+ const dirname = isESM ? import_path3.default.dirname((0, import_url.fileURLToPath)(import_meta.url)) : __dirname;
2964
+ const pkgPath = import_path3.default.resolve(dirname, dirname.endsWith("src") ? "../package.json" : "../../package.json");
2965
+ const pkg = require_(pkgPath);
2966
+ if (pkg && pkg.version) pkgVersion = pkg.version;
2967
+ } catch {
2968
+ }
2831
2969
  var plugin = {
2832
2970
  meta: {
2833
2971
  name: "eslint-plugin-ai-guard",
2834
- version: "1.2.0"
2972
+ version: pkgVersion
2835
2973
  },
2836
2974
  rules: allRules,
2837
2975
  configs: {