From harness-claude
Prevents OWASP IDOR vulnerabilities by enforcing object-level authorization in API endpoints via user-scoped database queries, authorization services, and resource guards.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Enforce object-level authorization so users can only access resources they own or are permitted to access
Flags broken access control vulnerabilities including missing ownership checks, IDOR, role enforcement gaps, and insecure middleware. Suggests fixes with gates, middleware, and 404 responses.
Guides IDOR pentesting in web APIs with discovery patterns, exploitation techniques like parameter tampering and UUID guessing, checklists, and real-world cases.
Identifies and exploits IDOR vulnerabilities in web apps through parameter manipulation, ID enumeration, Burp Suite interception, and multi-account testing. For authorized security assessments.
Share bugs, ideas, or general feedback.
Enforce object-level authorization so users can only access resources they own or are permitted to access
The most reliable pattern: include userId in every database query, not just in authorization checks after the fact.
// BAD — fetches by ID only, attacker can enumerate any order
app.get('/orders/:id', authenticate, async (req, res) => {
const order = await db.order.findUnique({ where: { id: req.params.id } });
if (!order) return res.status(404).json({ error: 'Not found' });
res.json(order); // returns order even if it belongs to another user
});
// GOOD — scope the query to the current user
app.get('/orders/:id', authenticate, async (req, res) => {
const order = await db.order.findFirst({
where: {
id: req.params.id,
userId: req.user.id, // IDOR prevention at query level
},
});
if (!order) return res.status(404).json({ error: 'Not found' }); // same error for not found and unauthorized
res.json(order);
});
Centralize ownership checks in a dedicated service to avoid scattered, inconsistent checks:
// authorization.service.ts
@Injectable()
export class AuthorizationService {
constructor(private db: PrismaService) {}
async assertOwnsOrder(userId: string, orderId: string): Promise<Order> {
const order = await this.db.order.findFirst({
where: { id: orderId, userId },
});
if (!order) throw new ForbiddenException('Access denied');
return order;
}
async assertCanAccessDocument(userId: string, docId: string): Promise<Document> {
const doc = await this.db.document.findFirst({
where: {
id: docId,
OR: [
{ ownerId: userId },
{ sharedWith: { some: { userId } } },
],
},
});
if (!doc) throw new ForbiddenException('Access denied');
return doc;
}
}
// order.controller.ts
@Get(':id')
async getOrder(@Param('id') id: string, @CurrentUser() user: User) {
return this.authz.assertOwnsOrder(user.id, id);
}
// resource-owner.guard.ts
@Injectable()
export class ResourceOwnerGuard implements CanActivate {
constructor(
private reflector: Reflector,
private authz: AuthorizationService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const userId = req.user.id;
const resourceId = req.params.id;
const resourceType = this.reflector.get<string>('resourceType', context.getHandler());
await this.authz.assertOwns(userId, resourceType, resourceId);
return true;
}
}
// Usage
@Get(':id')
@SetMetadata('resourceType', 'order')
@UseGuards(ResourceOwnerGuard)
getOrder(@Param('id') id: string) { ... }
Use UUIDs for public-facing resource identifiers. Sequential IDs make enumeration trivial.
// PREDICTABLE — easy to enumerate
// GET /invoices/1, /invoices/2, /invoices/3 ...
// BETTER — UUID makes guessing computationally infeasible
// GET /invoices/550e8400-e29b-41d4-a716-446655440000
// Prisma schema
model Order {
id String @id @default(uuid())
userId String
// ...
}
For extra security with internal sequential IDs, map them to tokens:
// Return opaque tokens instead of raw IDs
function encodeResourceId(internalId: number, secret: string): string {
// Use a deterministic encryption (not hash) so you can decode
return Buffer.from(`${internalId}:${secret}`).toString('base64url');
}
IDOR (also called BOLA — Broken Object Level Authorization) is the #1 API security issue per OWASP API Security Top 10. Authentication tells you WHO the user is; authorization tells you WHAT they can access.
Common IDOR mistakes:
userId in request body instead of the JWT/sessionReturn 404 not 403 for unauthorized resources — returning 403 confirms the resource exists, enabling enumeration.
Audit checklist:
findById / findUnique call — does it include userId or equivalent scope?https://owasp.org/www-project-top-ten/