February 2, 2026 • 2 min read • Fateh Mohammed
Access Control Bugs in SaaS Applications
Broken access control is the number one web application risk. Here is how it manifests in multi-tenant SaaS products and how to catch it early.
OWASP ranks broken access control as the number one web application security risk. In multi-tenant SaaS applications, the consequences are amplified because a single authorization bug can expose one customer's data to another.
Common access control failures
Missing tenant isolation
// Vulnerable: no tenant check
app.get('/api/documents/:id', async (req, res) => {
const doc = await db.documents.findById(req.params.id)
res.json(doc)
})
// Fixed: scoped to authenticated tenant
app.get('/api/documents/:id', async (req, res) => {
const doc = await db.documents.findOne({
id: req.params.id,
tenantId: req.auth.tenantId,
})
if (!doc) return res.status(404).end()
res.json(doc)
})
Every data query must include a tenant scope. Relying on frontend routing to prevent cross-tenant access is not sufficient.
Insecure direct object references (IDOR)
Sequential or predictable IDs make it trivial to enumerate resources. Even with authentication, a user can access resources belonging to other users within the same tenant if authorization is only checked at the tenant level.
Missing role checks on sensitive operations
New endpoints are the most common source of access control gaps. A developer adds an admin endpoint and forgets the role middleware. The endpoint works, passes code review, and ships without authorization.
Detection at the PR level
Automated scanners can detect:
- New route handlers without authentication middleware.
- Database queries on user-scoped tables missing a tenant or user filter.
- Admin-only operations without role verification.
These patterns are identifiable in pull request diffs and are high-confidence signals for real vulnerabilities.
Build access control into the architecture
The most reliable defense is making insecure code harder to write than secure code. Use middleware that injects tenant scope by default. Make unscoped queries require explicit opt-in. Design the framework so the secure path is the default path.