Auth Bypass in Fastapi with Cockroachdb
Auth Bypass in Fastapi with Cockroachdb — how this specific combination creates or exposes the vulnerability
Auth bypass occurs when authentication controls fail to prevent access to protected endpoints. In a Fastapi application using Cockroachdb as the backing data store, the risk often arises from how identity information is retrieved, compared, and cached between the application and the distributed SQL layer.
Because Cockroachdb is strongly consistent and supports transactional reads, developers may assume that a single SELECT within a transaction is sufficient to enforce authorization. If the Fastapi route retrieves a user record by an ID sourced directly from the request (for example, a path parameter user_id) without verifying that the authenticated subject owns that row, an IDOR or BOLA condition exists. An attacker who can manipulate the identifier can read or act on other users’ data even when session cookies or bearer tokens are validated by Fastapi middleware.
A common pattern that leads to bypass involves dynamic SQL or an ORM query built from request-supplied identifiers without scoping to the authenticated principal. For example, a route that calls SELECT * FROM users WHERE id = $1 using a client-provided ID will succeed in Cockroachdb regardless of the token’s subject if the query does not also include AND tenant_id = $2 or an equivalent ownership filter. Because Cockroachdb returns rows deterministically, the absence of this scoping means the database will return data, and Fastapi may incorrectly treat that data as safe.
Another vector specific to Fastapi with Cockroachdb is reliance on cached identity. If the application caches user claims or roles in memory or via a distributed cache and does not revalidate scope on each request, an attacker who obtains a token with broad claims may continue to access admin endpoints even after role changes are persisted in Cockroachdb. Cockroachdb’s serializable isolation will reflect updates once committed, but the application must re-fetch and re-evaluate permissions on each call; otherwise the outdated in-memory view creates an implicit bypass.
SSRF and external dependency risks can also intersect with this scenario. An attacker may trick Fastapi into making requests to internal Cockroachdb HTTP status endpoints or to other internal services if the environment exposes administrative interfaces. Proper network controls and input validation are required to ensure that database connectivity details are not inferable or manipulable via user-controlled URLs.
Cockroachdb-Specific Remediation in Fastapi — concrete code fixes
Remediation centers on scoping every database read or write to the authenticated subject and avoiding trust in client-supplied identifiers. Below are concrete, Cockroachdb-oriented examples for Fastapi that demonstrate secure patterns.
First, always include the authenticated principal in the WHERE clause. Use a dependency that resolves the subject from the token and pass it to the query. This example uses asyncpg (commonly compatible with Cockroachdb) and ensures row ownership is enforced at the SQL level:
import asyncpg
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def get_db_pool():
# In production, use a connection pool managed by your app lifespan
return asyncpg.create_pool(dsn="postgresql://user:password@host:26257/dbname?sslmode=require")
async def get_current_subject(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
# Validate token and extract subject (sub), e.g., via JWT verification
subject = validate_jwt_subject(token) # user-defined function
if not subject:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
return subject
async def fetch_user_profile(subject: str, pool: asyncpg.Pool = Depends(get_db_pool)):
async with pool.acquire() as conn:
row = await conn.fetchrow(
"SELECT id, display_name, email FROM users WHERE id = $1 AND tenant_id = $2",
subject,
subject # assuming tenant_id maps to subject for simplicity; use a proper tenant resolution in multi-tenant apps
)
if row is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
return dict(row)
For multi-tenant scenarios, resolve the tenant from a trusted source (subdomain, JWT claim, or routing header) and include it in all queries. Never allow the client to dictate the tenant or user ID used in the WHERE clause. The example below demonstrates scoping by tenant_id, with the tenant resolved independently of the user-supplied identifier:
async def fetch_team_members(team_slug: str, subject: str, pool: asyncpg.Pool = Depends(get_db_pool)):
async with pool.acquire() as conn:
rows = await conn.fetch(
"""
SELECT m.user_id, m.role, u.display_name
FROM team_members m
JOIN users u ON m.user_id = u.id
WHERE m.team_slug = $1
AND m.user_id = $2
AND m.tenant_id = (SELECT tenant_id FROM teams WHERE slug = $1)
""",
team_slug,
subject
)
if not rows:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
return [dict(r) for r in rows]
Use transactions when multiple reads or conditional writes are required, and ensure that the session’s search_path does not allow schema ambiguity that could be abused. With Cockroachdb, serializable transactions guarantee correctness, but the application must still include ownership predicates in each statement:
async def update_display_name(subject: str, new_name: str, pool: asyncpg.Pool = Depends(get_db_pool)):
async with pool.acquire() as conn:
async with conn.transaction():
result = await conn.execute(
"""
UPDATE users
SET display_name = $1
WHERE id = $2 AND tenant_id = $3
""",
new_name,
subject,
resolve_tenant(subject)
)
if result == "":
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="No rows updated")
Finally, enforce least privilege for the Cockroachdb user that Fastapi connects with. Avoid using a superuser; instead, create a role with SELECT, INSERT, UPDATE, and DELETE limited to the required tables and row-level policies. Combine this with parameterized queries to prevent injection and to keep query planning stable.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |