From security-guardian
OWASP A05 - Broken Access Control Detection. Use this skill when implementing authorization, checking permissions, or auditing who can access what resources. Activate when: authorization, permissions, access control, RBAC, ABAC, admin access, privilege escalation, IDOR, direct object reference, role check, can user access.
npx claudepluginhub latestaiagents/agent-skills --plugin security-guardianThis skill uses the workspace's default tool permissions.
**Detect and fix broken access control vulnerabilities including IDOR, privilege escalation, and missing authorization checks.**
Flags broken access control vulnerabilities including missing ownership checks, IDOR, role enforcement gaps, and insecure middleware. Suggests fixes with gates, middleware, and 404 responses.
Audits IAM policies, RBAC, ACLs, file permissions, and API authorization for vulnerabilities, privilege escalation paths, and least privilege violations.
Tests web apps for broken access control (OWASP A01) including privilege escalation, missing checks, and IDOR using Burp Authorize, ffuf, curl across user roles. For pentests and audits.
Share bugs, ideas, or general feedback.
Detect and fix broken access control vulnerabilities including IDOR, privilege escalation, and missing authorization checks.
| Vulnerability | Risk | Example |
|---|---|---|
| IDOR | HIGH | /api/users/123 accessible by any user |
| Missing Auth Check | CRITICAL | Admin endpoints without verification |
| Privilege Escalation | CRITICAL | User can elevate to admin |
| Path Traversal | HIGH | ../../admin/config |
| Forced Browsing | MEDIUM | Guessing admin URLs |
| Metadata Manipulation | HIGH | Changing userId in JWT |
// VULNERABLE - No ownership check
app.get('/api/documents/:id', async (req, res) => {
const doc = await Document.findById(req.params.id);
res.json(doc); // Anyone can access any document!
});
// VULNERABLE - No role check on admin route
app.delete('/api/users/:id', async (req, res) => {
await User.findByIdAndDelete(req.params.id);
res.json({ success: true }); // Any user can delete anyone!
});
// VULNERABLE - Client-side only checks
// Frontend hides admin button, but API has no check
if (user.role === 'admin') {
showAdminButton();
}
// VULNERABLE - Sequential IDs exposed
app.get('/api/invoices/:id', (req, res) => {
// User can increment ID to see other invoices
const invoice = await Invoice.findById(req.params.id);
res.json(invoice);
});
// VULNERABLE - User ID from request body
app.post('/api/profile/update', (req, res) => {
// Attacker can change userId to modify others
await User.findByIdAndUpdate(req.body.userId, req.body.data);
});
// VULNERABLE - Role from user input
app.post('/api/users', (req, res) => {
const user = new User({
email: req.body.email,
role: req.body.role // Attacker sets role: 'admin'
});
});
// VULNERABLE - Mass assignment
app.put('/api/profile', (req, res) => {
// Attacker adds { isAdmin: true } to request
await User.findByIdAndUpdate(userId, req.body);
});
// Authorization middleware factory
function authorize(...allowedRoles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!allowedRoles.includes(req.user.role)) {
// Log authorization failure
logger.warn('Authorization failed', {
userId: req.user.id,
requiredRoles: allowedRoles,
userRole: req.user.role,
path: req.path
});
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Usage
app.delete('/api/users/:id',
authenticate,
authorize('admin'),
deleteUserHandler
);
app.get('/api/reports',
authenticate,
authorize('admin', 'manager'),
getReportsHandler
);
// Ownership check middleware
function verifyOwnership(resourceModel, paramName = 'id') {
return async (req, res, next) => {
const resourceId = req.params[paramName];
const resource = await resourceModel.findById(resourceId);
if (!resource) {
return res.status(404).json({ error: 'Resource not found' });
}
// Check ownership (adjust field name as needed)
const isOwner = resource.userId?.toString() === req.user.id;
const isAdmin = req.user.role === 'admin';
if (!isOwner && !isAdmin) {
logger.warn('Ownership check failed', {
userId: req.user.id,
resourceId,
resourceOwner: resource.userId
});
return res.status(403).json({ error: 'Access denied' });
}
req.resource = resource;
next();
};
}
// Usage
app.get('/api/documents/:id',
authenticate,
verifyOwnership(Document),
(req, res) => res.json(req.resource)
);
app.put('/api/documents/:id',
authenticate,
verifyOwnership(Document),
updateDocumentHandler
);
const { Ability, AbilityBuilder } = require('@casl/ability');
function defineAbilitiesFor(user) {
const { can, cannot, build } = new AbilityBuilder(Ability);
if (user.role === 'admin') {
can('manage', 'all'); // Admin can do everything
} else if (user.role === 'manager') {
can('read', 'all');
can('update', 'Document', { departmentId: user.departmentId });
can('create', 'Document');
cannot('delete', 'Document');
} else {
// Regular user
can('read', 'Document', { userId: user.id });
can('update', 'Document', { userId: user.id });
can('create', 'Document');
can('read', 'Profile', { userId: user.id });
can('update', 'Profile', { userId: user.id });
}
return build();
}
// Middleware
function checkAbility(action, subject) {
return (req, res, next) => {
const ability = defineAbilitiesFor(req.user);
if (ability.can(action, subject)) {
next();
} else {
res.status(403).json({ error: 'Forbidden' });
}
};
}
// Usage
app.delete('/api/documents/:id',
authenticate,
checkAbility('delete', 'Document'),
deleteHandler
);
// Whitelist allowed fields
const ALLOWED_PROFILE_FIELDS = ['name', 'email', 'avatar', 'bio'];
const ALLOWED_ADMIN_FIELDS = [...ALLOWED_PROFILE_FIELDS, 'role', 'isActive'];
function filterFields(data, allowedFields) {
return Object.keys(data)
.filter(key => allowedFields.includes(key))
.reduce((obj, key) => {
obj[key] = data[key];
return obj;
}, {});
}
app.put('/api/profile', authenticate, async (req, res) => {
const allowedFields = req.user.role === 'admin'
? ALLOWED_ADMIN_FIELDS
: ALLOWED_PROFILE_FIELDS;
const safeData = filterFields(req.body, allowedFields);
await User.findByIdAndUpdate(req.user.id, safeData);
res.json({ success: true });
});
const { v4: uuidv4 } = require('uuid');
// Mongoose schema with UUID
const documentSchema = new mongoose.Schema({
_id: {
type: String,
default: uuidv4
},
title: String,
userId: String
});
// Or use mongoose-uuid
const mongoose = require('mongoose');
require('mongoose-uuid')(mongoose);
const accessMatrix = {
'GET /api/users': ['admin'],
'GET /api/users/:id': ['admin', 'self'],
'PUT /api/users/:id': ['admin', 'self'],
'DELETE /api/users/:id': ['admin'],
'GET /api/documents': ['admin', 'user'],
'GET /api/documents/:id': ['admin', 'owner'],
'POST /api/documents': ['admin', 'user'],
'PUT /api/documents/:id': ['admin', 'owner'],
'DELETE /api/documents/:id': ['admin', 'owner'],
'GET /api/admin/*': ['admin'],
'POST /api/admin/*': ['admin']
};
function checkAccess(req, resourceOwnerId = null) {
const route = `${req.method} ${req.route.path}`;
const allowedRoles = accessMatrix[route] || [];
if (allowedRoles.includes(req.user.role)) {
return true;
}
if (allowedRoles.includes('self') && req.params.id === req.user.id) {
return true;
}
if (allowedRoles.includes('owner') && resourceOwnerId === req.user.id) {
return true;
}
return false;
}
# Test IDOR - Try accessing other users' resources
curl -H "Authorization: Bearer $USER_A_TOKEN" \
http://api.com/users/USER_B_ID
# Test privilege escalation
curl -X PUT http://api.com/profile \
-H "Authorization: Bearer $USER_TOKEN" \
-d '{"role": "admin"}'
# Test missing auth
curl http://api.com/admin/users
# Test forced browsing
curl http://api.com/admin
curl http://api.com/backup
curl http://api.com/config
# Automated testing
# Use Burp Suite's Authorize extension
# OWASP ZAP's Access Control Testing