A test suite that only verifies authorized access never tests the actual security boundary — it confirms the happy path works but leaves the failure path untested. CWE-284 (Improper Access Control) vulnerabilities frequently survive code review and ship to production because no test ever checked that unauthorized access was rejected. OWASP A01 (Broken Access Control) is the top web application risk; the absence of negative authorization tests means any refactor can silently remove a permission check with no failing test to signal the regression. NIST 800-53 CA-8 (Penetration Testing) requires verifying that security controls reject unauthorized access — automated negative tests are the continuous equivalent.
Low because the pattern describes test absence rather than a direct vulnerability, but authorization regressions that only tests would catch are consistently discovered in production instead.
Add tests that deliberately simulate unauthorized access and assert rejection. One negative test per critical authorization boundary is the minimum — the goal is catching regressions when authorization logic is refactored.
// __tests__/api/posts.test.ts
describe('POST /api/posts/:id - authorization', () => {
it('returns 404 when user tries to update a post they do not own', async () => {
const { userA, userB } = await createTestUsers();
const postByUserB = await createPost(userB);
const response = await fetch(`/api/posts/${postByUserB.id}`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${userA.token}` },
body: JSON.stringify({ title: 'Hijacked' }),
});
expect(response.status).toBe(404); // or 403
});
});
If the test file __tests__/api/ does not yet exist, create it alongside the route handler file.
ID: saas-authorization.api-auth.authorization-tested-negative
Severity: low
What to look for: Count all relevant instances and enumerate each. Examine test files (*.test.ts, *.spec.ts, __tests__/). Look for tests that intentionally simulate an unauthorized access scenario — a test where user A tries to access user B's resource and the test asserts a 403 or 404 response. Search for test descriptions containing words like "reject", "unauthorized", "forbidden", "cannot access", "403", "should not".
Pass criteria: The test suite contains at least some tests that explicitly verify unauthorized access is denied — testing the "sad path" of authorization, not just the happy path. At least 1 implementation must be verified.
Fail criteria: Tests only verify that authorized users can access their resources (happy path) with no tests checking that unauthorized users are rejected (sad path). Authorization logic that is never tested for failures can silently degrade.
Skip (N/A) when: No test files found in the codebase — the project has no automated tests.
Detail on fail: "No negative authorization tests found. Test suite only verifies authorized access, not that unauthorized access is denied." (Note if tests exist at all or if testing is absent entirely.)
Remediation: Add tests that deliberately attempt unauthorized access and assert rejection. These are often called "negative tests" or "security tests."
// __tests__/api/posts.test.ts
describe('POST /api/posts/:id - authorization', () => {
it('returns 404 when user tries to update a post they do not own', async () => {
const { userA, userB } = await createTestUsers();
const postByUserB = await createPost(userB);
const response = await fetch(`/api/posts/${postByUserB.id}`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${userA.token}` },
body: JSON.stringify({ title: 'Hijacked' }),
});
expect(response.status).toBe(404); // or 403
});
});
One negative test per critical authorization boundary is the minimum. The goal is to catch regressions when authorization logic is refactored.