Skip to content

Instantly share code, notes, and snippets.

@bhouston
Created September 17, 2025 01:38
Show Gist options
  • Save bhouston/5836b7925186c7c9904e3fedd9fc5df3 to your computer and use it in GitHub Desktop.
Save bhouston/5836b7925186c7c9904e3fedd9fc5df3 to your computer and use it in GitHub Desktop.
ESLint rule to catch the use of Prisma's "include" queries
/**
* ESLint rule to prevent the use of Prisma's `include` queries
* This rule helps prevent accidental exposure of sensitive data by encouraging
* explicit field selection using `select` instead of `include`
*/
export default {
meta: {
type: 'problem',
docs: {
description:
'Disallow Prisma include queries to prevent accidental data exposure',
category: 'Security',
recommended: true,
},
fixable: null,
schema: [],
messages: {
noPrismaInclude:
'Prisma `include` queries are not allowed. Use `select` instead to explicitly choose which fields to return. This prevents accidental exposure of sensitive data.',
},
},
create(context) {
// Helper function to check if a node is within a Prisma query
function isWithinPrismaQuery(node) {
let current = node;
while (current) {
if (
current.type === 'CallExpression' &&
current.callee &&
current.callee.type === 'MemberExpression' &&
current.callee.property &&
current.callee.property.type === 'Identifier'
) {
const methodName = current.callee.property.name;
const prismaMethods = [
'findMany',
'findUnique',
'findFirst',
'create',
'update',
'upsert',
'createMany',
'updateMany',
'delete',
'deleteMany',
];
if (prismaMethods.includes(methodName)) {
// Check if the callee object is likely a Prisma client
const calleeObject = current.callee.object;
if (
calleeObject &&
calleeObject.type === 'MemberExpression' &&
calleeObject.property &&
calleeObject.property.type === 'Identifier'
) {
const modelName = calleeObject.property.name;
// Common Prisma model names (you can extend this list)
const commonModels = [
'user',
'org',
'organization',
'project',
'asset',
'member',
'memberInvite',
'assetReport',
'userNotification',
'accessKey',
'session',
'refreshToken',
];
if (commonModels.includes(modelName.toLowerCase())) {
return true;
}
}
}
}
current = current.parent;
}
return false;
}
// Helper function to recursively find all 'include' properties in an object
function findIncludeProperties(node, includeProperties = []) {
if (node.type === 'ObjectExpression') {
node.properties.forEach((prop) => {
if (
prop.type === 'Property' &&
prop.key.type === 'Identifier' &&
prop.key.name === 'include'
) {
includeProperties.push(prop);
}
// Recursively check nested objects (like in select statements)
if (prop.value && prop.value.type === 'ObjectExpression') {
findIncludeProperties(prop.value, includeProperties);
}
});
}
return includeProperties;
}
return {
// Match object expressions that contain an 'include' property
ObjectExpression(node) {
// Find all include properties (including nested ones)
const includeProperties = findIncludeProperties(node);
includeProperties.forEach((includeProperty) => {
// Check if this is within a Prisma query
if (isWithinPrismaQuery(node)) {
context.report({
node: includeProperty,
messageId: 'noPrismaInclude',
});
}
});
},
};
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment