VYPR
Medium severity5.8NVD Advisory· Published Jul 15, 2024· Updated Apr 15, 2026

CVE-2024-40627

CVE-2024-40627

Description

Fastapi OPA is an opensource fastapi middleware which includes auth flow. HTTP OPTIONS requests are always allowed by OpaMiddleware, even when they lack authentication, and are passed through directly to the application. OpaMiddleware allows all HTTP OPTIONS requests without evaluating it against any policy. If an application provides different responses to HTTP OPTIONS requests based on an entity existing (such as to indicate whether an entity is writable on a system level), an unauthenticated attacker could discover which entities exist within an application. This issue has been addressed in release version 2.0.1. All users are advised to upgrade. There are no known workarounds for this vulnerability.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
fastapi-opaPyPI
< 2.0.12.0.1

Patches

2
9588109ff651

Merge pull request #73 from busykoala/options_auth

https://github.com/busykoala/fastapi-opaMatthias OsswaldJul 15, 2024via ghsa
6 files changed · +1031 346
  • CHANGELOG.md+8 0 modified
    @@ -1,5 +1,13 @@
     # Change Log
     
    +## [2.0.1] - 2024-07-15
    +- Security Improvement: Added authentication and authorization checks for HTTP
    +  OPTIONS requests in OpaMiddleware. This ensures that OPTIONS requests are
    +  subjected to the same security policies as other HTTP methods, preventing
    +  potential information leaks.
    +  [See advisory for more details](https://github.com/advisories/GHSA-5f5c-8rvc-j8wf)
    +- Update dependencies due to multiple vulnerabilities.
    +
     ## [2.0.0] - 2024-02-07
     - Drop Python 3.7 support due to FastAPI update
     - Update dependencies due to vulnerabilities:
    
  • fastapi_opa/opa/opa_middleware.py+2 9 modified
    @@ -18,12 +18,7 @@
     from fastapi_opa.auth.exceptions import AuthenticationException
     from fastapi_opa.opa.opa_config import OPAConfig
     
    -try:
    -    Pattern = re.Pattern
    -except AttributeError:
    -    # Python3.6 does not contain re.Pattern
    -    Pattern = None
    -
    +Pattern = re.Pattern
     logger = logging.getLogger(__name__)
     
     
    @@ -76,15 +71,13 @@ async def __call__(
             own_receive = OwnReceive(receive)
             request = Request(scope, own_receive, send)
     
    -        if request.method == "OPTIONS":
    -            return await self.app(scope, receive, send)
    -
             # allow openapi endpoints without authentication
             if should_skip_endpoint(request.url.path, self.skip_endpoints):
                 return await self.app(scope, receive, send)
     
             # authenticate user or get redirect to identity provider
             successful = False
    +        user_info_or_auth_redirect = None
             for auth in self.config.authentication:
                 try:
                     user_info_or_auth_redirect = auth.authenticate(
    
  • poetry.lock+931 336 modified
  • pyproject.toml+1 1 modified
    @@ -1,6 +1,6 @@
     [tool.poetry]
     name = "fastapi-opa"
    -version = "2.0.0"
    +version = "2.0.1"
     description = "Fastapi OPA middleware incl. auth flow."
     authors = ["Matthias Osswald <info@busykoala.io>"]
     license = "GPL-3.0-or-later"
    
  • tests/conftest.py+38 0 modified
    @@ -3,6 +3,8 @@
     import nest_asyncio
     import pytest
     from fastapi import FastAPI
    +from fastapi import HTTPException
    +from fastapi import Response
     from fastapi.testclient import TestClient
     
     from fastapi_opa import OPAConfig
    @@ -15,6 +17,12 @@
     
     nest_asyncio.apply()
     
    +# Sample data for the test
    +WRITABLE_ITEMS = {
    +    1: True,
    +    2: False,
    +}
    +
     
     @pytest.fixture
     def client():
    @@ -29,6 +37,21 @@ def client():
         async def root() -> Dict:
             return {"msg": "success"}
     
    +    @app.get("/items/{item_id}")
    +    async def read_item(item_id: int):
    +        if item_id not in WRITABLE_ITEMS:
    +            raise HTTPException(status_code=404)
    +        return {"item_id": item_id}
    +
    +    @app.options("/items/{item_id}")
    +    async def read_item_options(response: Response, item_id: int) -> Dict:
    +        if item_id not in WRITABLE_ITEMS:
    +            raise HTTPException(status_code=404)
    +        response.headers["Allow"] = "OPTIONS, GET" + (
    +            ", POST" if WRITABLE_ITEMS[item_id] else ""
    +        )
    +        return {}
    +
         yield TestClient(app)
     
     
    @@ -76,6 +99,21 @@ def client_multiple_authentications(api_key_auth):
         async def root() -> Dict:
             return {"msg": "success"}
     
    +    @app.get("/items/{item_id}")
    +    async def read_item(item_id: int):
    +        if item_id not in WRITABLE_ITEMS:
    +            raise HTTPException(status_code=404)
    +        return {"item_id": item_id}
    +
    +    @app.options("/items/{item_id}")
    +    async def read_item_options(response: Response, item_id: int) -> Dict:
    +        if item_id not in WRITABLE_ITEMS:
    +            raise HTTPException(status_code=404)
    +        response.headers["Allow"] = "OPTIONS, GET" + (
    +            ", POST" if WRITABLE_ITEMS[item_id] else ""
    +        )
    +        return {}
    +
         yield TestClient(app)
     
     
    
  • tests/test_option_requests.py+51 0 added
    @@ -0,0 +1,51 @@
    +from unittest.mock import patch
    +
    +import pytest
    +from fastapi.testclient import TestClient
    +
    +
    +@pytest.fixture
    +def mock_opa_response():
    +    with patch("fastapi_opa.opa.opa_middleware.requests.post") as mock_post:
    +        mock_post.return_value.status_code = 200
    +        mock_post.return_value.json.return_value = {"result": {"allow": True}}
    +        yield mock_post
    +
    +
    +def test_options_request_with_auth(
    +    client_multiple_authentications, api_key_auth, mock_opa_response
    +):
    +    client: TestClient = client_multiple_authentications
    +
    +    # Test OPTIONS request for an existing item with authentication
    +    response = client.options(
    +        "/items/1",
    +        headers={api_key_auth["header_key"]: api_key_auth["api_key"]},
    +    )
    +    assert response.status_code == 200
    +    assert response.headers["Allow"] == "OPTIONS, GET, POST"
    +    assert response.json() == {}
    +
    +    # Test OPTIONS request for a non-existing item with authentication
    +    response = client.options(
    +        "/items/3",
    +        headers={api_key_auth["header_key"]: api_key_auth["api_key"]},
    +    )
    +    assert response.status_code == 404
    +    assert response.json() == {"detail": "Not Found"}
    +
    +
    +def test_options_request_without_auth(
    +    client_multiple_authentications, mock_opa_response
    +):
    +    client: TestClient = client_multiple_authentications
    +
    +    # Test OPTIONS request for an existing item without authentication
    +    response = client.options("/items/1")
    +    assert response.status_code == 401
    +    assert response.json() == {"message": "Unauthorized"}
    +
    +    # Test OPTIONS request for a non-existing item without authentication
    +    response = client.options("/items/3")
    +    assert response.status_code == 401
    +    assert response.json() == {"message": "Unauthorized"}
    
9458845a6f6f

Authenticate option requests.

https://github.com/busykoala/fastapi-opaMatthias OsswaldJul 15, 2024via ghsa
4 files changed · +99 9
  • CHANGELOG.md+8 0 modified
    @@ -1,5 +1,13 @@
     # Change Log
     
    +## [2.0.1] - 2024-07-15
    +- Security Improvement: Added authentication and authorization checks for HTTP
    +  OPTIONS requests in OpaMiddleware. This ensures that OPTIONS requests are
    +  subjected to the same security policies as other HTTP methods, preventing
    +  potential information leaks.
    +  [See advisory for more details](https://github.com/advisories/GHSA-5f5c-8rvc-j8wf)
    +- Update dependencies due to multiple vulnerabilities.
    +
     ## [2.0.0] - 2024-02-07
     - Drop Python 3.7 support due to FastAPI update
     - Update dependencies due to vulnerabilities:
    
  • fastapi_opa/opa/opa_middleware.py+2 9 modified
    @@ -18,12 +18,7 @@
     from fastapi_opa.auth.exceptions import AuthenticationException
     from fastapi_opa.opa.opa_config import OPAConfig
     
    -try:
    -    Pattern = re.Pattern
    -except AttributeError:
    -    # Python3.6 does not contain re.Pattern
    -    Pattern = None
    -
    +Pattern = re.Pattern
     logger = logging.getLogger(__name__)
     
     
    @@ -76,15 +71,13 @@ async def __call__(
             own_receive = OwnReceive(receive)
             request = Request(scope, own_receive, send)
     
    -        if request.method == "OPTIONS":
    -            return await self.app(scope, receive, send)
    -
             # allow openapi endpoints without authentication
             if should_skip_endpoint(request.url.path, self.skip_endpoints):
                 return await self.app(scope, receive, send)
     
             # authenticate user or get redirect to identity provider
             successful = False
    +        user_info_or_auth_redirect = None
             for auth in self.config.authentication:
                 try:
                     user_info_or_auth_redirect = auth.authenticate(
    
  • tests/conftest.py+38 0 modified
    @@ -3,6 +3,8 @@
     import nest_asyncio
     import pytest
     from fastapi import FastAPI
    +from fastapi import HTTPException
    +from fastapi import Response
     from fastapi.testclient import TestClient
     
     from fastapi_opa import OPAConfig
    @@ -15,6 +17,12 @@
     
     nest_asyncio.apply()
     
    +# Sample data for the test
    +WRITABLE_ITEMS = {
    +    1: True,
    +    2: False,
    +}
    +
     
     @pytest.fixture
     def client():
    @@ -29,6 +37,21 @@ def client():
         async def root() -> Dict:
             return {"msg": "success"}
     
    +    @app.get("/items/{item_id}")
    +    async def read_item(item_id: int):
    +        if item_id not in WRITABLE_ITEMS:
    +            raise HTTPException(status_code=404)
    +        return {"item_id": item_id}
    +
    +    @app.options("/items/{item_id}")
    +    async def read_item_options(response: Response, item_id: int) -> Dict:
    +        if item_id not in WRITABLE_ITEMS:
    +            raise HTTPException(status_code=404)
    +        response.headers["Allow"] = "OPTIONS, GET" + (
    +            ", POST" if WRITABLE_ITEMS[item_id] else ""
    +        )
    +        return {}
    +
         yield TestClient(app)
     
     
    @@ -76,6 +99,21 @@ def client_multiple_authentications(api_key_auth):
         async def root() -> Dict:
             return {"msg": "success"}
     
    +    @app.get("/items/{item_id}")
    +    async def read_item(item_id: int):
    +        if item_id not in WRITABLE_ITEMS:
    +            raise HTTPException(status_code=404)
    +        return {"item_id": item_id}
    +
    +    @app.options("/items/{item_id}")
    +    async def read_item_options(response: Response, item_id: int) -> Dict:
    +        if item_id not in WRITABLE_ITEMS:
    +            raise HTTPException(status_code=404)
    +        response.headers["Allow"] = "OPTIONS, GET" + (
    +            ", POST" if WRITABLE_ITEMS[item_id] else ""
    +        )
    +        return {}
    +
         yield TestClient(app)
     
     
    
  • tests/test_option_requests.py+51 0 added
    @@ -0,0 +1,51 @@
    +from unittest.mock import patch
    +
    +import pytest
    +from fastapi.testclient import TestClient
    +
    +
    +@pytest.fixture
    +def mock_opa_response():
    +    with patch("fastapi_opa.opa.opa_middleware.requests.post") as mock_post:
    +        mock_post.return_value.status_code = 200
    +        mock_post.return_value.json.return_value = {"result": {"allow": True}}
    +        yield mock_post
    +
    +
    +def test_options_request_with_auth(
    +    client_multiple_authentications, api_key_auth, mock_opa_response
    +):
    +    client: TestClient = client_multiple_authentications
    +
    +    # Test OPTIONS request for an existing item with authentication
    +    response = client.options(
    +        "/items/1",
    +        headers={api_key_auth["header_key"]: api_key_auth["api_key"]},
    +    )
    +    assert response.status_code == 200
    +    assert response.headers["Allow"] == "OPTIONS, GET, POST"
    +    assert response.json() == {}
    +
    +    # Test OPTIONS request for a non-existing item with authentication
    +    response = client.options(
    +        "/items/3",
    +        headers={api_key_auth["header_key"]: api_key_auth["api_key"]},
    +    )
    +    assert response.status_code == 404
    +    assert response.json() == {"detail": "Not Found"}
    +
    +
    +def test_options_request_without_auth(
    +    client_multiple_authentications, mock_opa_response
    +):
    +    client: TestClient = client_multiple_authentications
    +
    +    # Test OPTIONS request for an existing item without authentication
    +    response = client.options("/items/1")
    +    assert response.status_code == 401
    +    assert response.json() == {"message": "Unauthorized"}
    +
    +    # Test OPTIONS request for a non-existing item without authentication
    +    response = client.options("/items/3")
    +    assert response.status_code == 401
    +    assert response.json() == {"message": "Unauthorized"}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.