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/cli/index.js +1676 -333
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +159 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +165 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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: "
|
|
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
|
|
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
|
|
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(
|
|
2273
|
-
if (!
|
|
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 (
|
|
2406
|
+
if (path4[i] !== prefix[i]) {
|
|
2278
2407
|
return false;
|
|
2279
2408
|
}
|
|
2280
2409
|
}
|
|
2281
2410
|
return true;
|
|
2282
2411
|
}
|
|
2283
|
-
function isLikelyResourceIdPath(
|
|
2284
|
-
if (!
|
|
2412
|
+
function isLikelyResourceIdPath(path4) {
|
|
2413
|
+
if (!path4 || path4.length < 3) {
|
|
2285
2414
|
return false;
|
|
2286
2415
|
}
|
|
2287
|
-
if (!hasPathPrefix(
|
|
2416
|
+
if (!hasPathPrefix(path4, ["req", "params"]) && !hasPathPrefix(path4, ["req", "body"]) && !hasPathPrefix(path4, ["req", "query"])) {
|
|
2288
2417
|
return false;
|
|
2289
2418
|
}
|
|
2290
|
-
const last =
|
|
2419
|
+
const last = path4[path4.length - 1].toLowerCase();
|
|
2291
2420
|
return last === "id" || last.endsWith("id");
|
|
2292
2421
|
}
|
|
2293
|
-
function isReqUserPath(
|
|
2294
|
-
return hasPathPrefix(
|
|
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
|
|
2334
|
-
if (isLikelyResourceIdPath(
|
|
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: "
|
|
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:
|
|
2938
|
+
version: pkgVersion
|
|
2795
2939
|
},
|
|
2796
2940
|
rules: allRules,
|
|
2797
2941
|
configs: {
|