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.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: "
|
|
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
|
|
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
|
|
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(
|
|
2313
|
-
if (!
|
|
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 (
|
|
2439
|
+
if (path4[i] !== prefix[i]) {
|
|
2318
2440
|
return false;
|
|
2319
2441
|
}
|
|
2320
2442
|
}
|
|
2321
2443
|
return true;
|
|
2322
2444
|
}
|
|
2323
|
-
function isLikelyResourceIdPath(
|
|
2324
|
-
if (!
|
|
2445
|
+
function isLikelyResourceIdPath(path4) {
|
|
2446
|
+
if (!path4 || path4.length < 3) {
|
|
2325
2447
|
return false;
|
|
2326
2448
|
}
|
|
2327
|
-
if (!hasPathPrefix(
|
|
2449
|
+
if (!hasPathPrefix(path4, ["req", "params"]) && !hasPathPrefix(path4, ["req", "body"]) && !hasPathPrefix(path4, ["req", "query"])) {
|
|
2328
2450
|
return false;
|
|
2329
2451
|
}
|
|
2330
|
-
const last =
|
|
2452
|
+
const last = path4[path4.length - 1].toLowerCase();
|
|
2331
2453
|
return last === "id" || last.endsWith("id");
|
|
2332
2454
|
}
|
|
2333
|
-
function isReqUserPath(
|
|
2334
|
-
return hasPathPrefix(
|
|
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
|
|
2374
|
-
if (isLikelyResourceIdPath(
|
|
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: "
|
|
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:
|
|
2972
|
+
version: pkgVersion
|
|
2835
2973
|
},
|
|
2836
2974
|
rules: allRules,
|
|
2837
2975
|
configs: {
|