HIGH broken access controlfastapidynamodb

Broken Access Control in Fastapi with Dynamodb

Broken Access Control in Fastapi with Dynamodb — how this specific combination creates or exposes the vulnerability

Broken Access Control occurs when authorization checks are missing or incorrectly enforced, allowing an authenticated user to access or modify resources that should be restricted. In a Fastapi application backed by DynamoDB, this often arises from trusting client-supplied identifiers (such as a resource ID or path parameter) without verifying that the authenticated subject owns or is permitted to operate on that resource. Because DynamoDB is a low-level, schema-flexible store, it does not enforce ownership or permissions by itself; it is the application layer in Fastapi that must implement and enforce these checks consistently.

Consider an endpoint designed to retrieve a user profile by ID. A vulnerable Fastapi route might read the ID directly from the request and query DynamoDB without confirming the ID belongs to the requesting user:

from fastapi import Fastapi, Depends, HTTPException
import boto3
from pydantic import BaseModel

app = Fastapi()
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('users')

class UserProfile(BaseModel):
    user_id: str
    email: str
    display_name: str

@app.get('/profiles/{user_id}', response_model=UserProfile)
def get_profile(user_id: str, token_user_id: str = Depends(get_current_user_id)):
    response = table.get_item(Key={'user_id': user_id})
    item = response.get('Item')
    if not item:
        raise HTTPException(status_code=404, detail='Not found')
    # Missing: ensure token_user_id == user_id
    return UserProfile(**item)

In this example, an attacker can change the user_id path parameter to access any profile in the table, provided they know or can guess valid IDs. This is a classic BOLA (Broken Object Level Authorization) and IDOR pattern. DynamoDB does not raise an error for unauthorized reads; it simply returns the item if the key exists, so the responsibility to enforce ownership lies entirely with Fastapi code.

The same class of issue extends to write operations. An endpoint that updates or deletes a record based on a client-supplied key without validating that the record belongs to the caller can lead to privilege escalation or unintended data modification. For example, a Fastapi route that deletes a DynamoDB item by ID supplied in the body or URL, without checking that the item’s owner_id matches the authenticated subject, exposes a BFLA (Business Logic Flaw) and privilege escalation risk.

Additional risk patterns include missing authorization on related operations (e.g., listing resources the user shouldn’t see) and inconsistent enforcement where some endpoints check ownership and others do not. Because DynamoDB often stores denormalized or shared data models, it is easy to omit necessary checks when designing table structures, especially when composite keys are used. Without rigorous mapping between authentication subject and DynamoDB partition/sort keys in Fastapi, authorization gaps become likely.

Dynamodb-Specific Remediation in Fastapi — concrete code fixes

Remediation centers on enforcing ownership and authorization on every request that interacts with DynamoDB, using the authenticated subject to constrain queries and updates. Below are concrete, secure patterns for Fastapi with DynamoDB resource and document models.

Secure read with ownership check

Always include the authenticated user identifier in the key condition and validate that the returned item matches the subject:

from fastapi import Fastapi, Depends, HTTPException
import boto3
from pydantic import BaseModel

app = Fastapi()
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('user_profiles')

class UserProfile(BaseModel):
    user_id: str
    email: str
    display_name: str

def get_current_user_id() -> str:
    # implementation that extracts and validates identity
    return 'user-uuid-123'

@app.get('/profiles/me', response_model=UserProfile)
def get_my_profile(token_user_id: str = Depends(get_current_user_id)):
    response = table.get_item(Key={'user_id': token_user_id})
    item = response.get('Item')
    if not item:
        raise HTTPException(status_code=404, detail='Not found')
    return UserProfile(**item)

By using a scoped endpoint like /profiles/me and deriving the key from the authenticated subject, you avoid IDOR entirely. For multi-tenant or organization-based models, include the organization or tenant ID in both the key and the authentication context, and assert that they match before querying.

Secure write with ownership and existence checks

For updates and deletes, re-fetch the item and confirm ownership as part of a conditional write. Conditional writes in DynamoDB help prevent race conditions where permissions change between read and write:

from fastapi import Fastapi, Depends, HTTPException
import boto3
from boto3.dynamodb.conditions import Attr

app = Fastapi()
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('user_profiles')

class UpdateProfile(BaseModel):
    display_name: str

def get_current_user_id() -> str:
    return 'user-uuid-123'

@app.put('/profiles/me')
def update_my_profile(payload: UpdateProfile, token_user_id: str = Depends(get_current_user_id)):
    # Conditional update ensures item still belongs to the user at write time
    response = table.update_item(
        Key={'user_id': token_user_id},
        UpdateExpression='SET display_name = :val',
        ConditionExpression=Attr('user_id').eq(token_user_id),
        ExpressionAttributeValues={':val': payload.display_name},
        ReturnValues='UPDATED_NEW'
    )
    return {'message': 'updated', 'updated': response.get('Attributes')}

The ConditionExpression causes DynamoDB to reject the update if the item no longer has the expected owner, providing an additional safety net. For deletes, use the same pattern with table.delete_item(Key=..., ConditionExpression=...).

Organizational models and composite keys

When data is shared within teams or organizations, include a partition key that incorporates the organization or tenant ID, and enforce that the authenticated subject belongs to that tenant. For example, use a composite key like PK = ORG#org_id#USER#user_id and SK for sort attributes. In Fastapi, derive both parts from the token and validate that the requested resource’s key components match the subject’s organization membership before querying.

import boto3
from fastapi import Depends, HTTPException

dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('org_items')

def get_current_user() -> dict:
    # returns {'org_id': 'org-123', 'user_id': 'user-abc'}
    return {'org_id': 'org-123', 'user_id': 'user-abc'}

@app.get('/org/items/{item_id}')
def get_org_item(item_id: str, user: dict = Depends(get_current_user)):
    pk = f"ORG#{user['org_id']}#USER#{user['user_id']}"
    response = table.get_item(Key={'pk': pk, 'sk': item_id})
    item = response.get('Item')
    if not item:
        raise HTTPException(status_code=404, detail='Not found or access denied')
    return item

With this pattern, even if item_id is guessed, access is denied unless the item’s partition key matches the authenticated user’s organizational context. This aligns the DynamoDB data model with authorization boundaries enforced in Fastapi.

Frequently Asked Questions

Does DynamoDB prevent Broken Access Control by itself?
No. DynamoDB is a schema and storage engine; it does not enforce ownership or permissions. Authorization must be implemented and enforced by the application layer in Fastapi, using authenticated subject checks and conditional writes.
How can I test if my Fastapi + DynamoDB endpoints are vulnerable to IDOR?
Use an authenticated request to access or modify resources with another user's identifier (e.g., change user_id in the path or body). If the endpoint returns or applies changes without verifying ownership, it is vulnerable. Automated scans like middleBrick can help identify these patterns by correlating spec-defined endpoints with runtime authorization checks.