VYPR
High severity7.5NVD Advisory· Published Jun 2, 2026

CVE-2026-3514

CVE-2026-3514

Description

Prefect versions prior to 3.6.19 allow authentication bypass via specially crafted resource names ending in 'health' or 'ready'.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Prefect versions prior to 3.6.19 allow authentication bypass via specially crafted resource names ending in 'health' or 'ready'.

Vulnerability

An authentication bypass vulnerability exists in Prefect versions prior to 3.6.19. The authentication middleware improperly exempts any URL path ending with 'health' or 'ready' from authentication checks. This allows attackers to access resources by naming them with these suffixes, bypassing intended security controls [1].

Exploitation

An attacker can exploit this vulnerability by creating resources, such as Prefect Variables, Flows, Work Pools, Work Queues, or Deployments, with names that end in 'health' or 'ready'. By then accessing these resources via their corresponding API endpoints, the attacker can bypass authentication and gain unauthorized access [1, 2].

Impact

Successful exploitation allows an attacker to access sensitive information stored within Prefect Variables, including API keys and database credentials, without proper authentication. This can lead to further compromise of the system and its associated data [2].

Mitigation

The vulnerability is fixed in Prefect version 3.6.19. Users are advised to upgrade to this version or a later release. No workarounds are available for earlier versions. This vulnerability has not been listed on the Known Exploited Vulnerabilities (KEV) catalog as of the current date [1, 2].

AI Insight generated on Jun 2, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

1
e21617125335

Fix auth bypass via endswith() health check path exemption (#21063)

https://github.com/prefecthq/prefectChris PickettMar 10, 2026via nvd-ref
2 files changed · +68 5
  • src/prefect/server/api/server.py+9 5 modified
    @@ -417,16 +417,20 @@ async def server_version() -> str:  # type: ignore[reportUnusedFunction]
         auth_string = prefect.settings.PREFECT_SERVER_API_AUTH_STRING.value()
     
         if auth_string is not None:
    +        health_check_paths = {health_check_path, "/ready"}
     
             @api_app.middleware("http")
             async def token_validation(request: Request, call_next: Any):  # type: ignore[reportUnusedFunction]
                 header_token = request.headers.get("Authorization")
     
    -            # used for probes in k8s and such
    -            if (
    -                request.url.path.endswith(("health", "ready"))
    -                and request.method.upper() == "GET"
    -            ):
    +            # Allow unauthenticated health/ready probes (e.g. k8s).
    +            # Use scope["path"] (not request.url.path) because url.path
    +            # can be spoofed via Host header manipulation. Use exact path
    +            # matching (not suffix matching) to prevent auth bypass via
    +            # crafted paths like /variables/name/system-health.
    +            scope = request.scope
    +            app_path = scope["path"].removeprefix(scope.get("root_path", ""))
    +            if app_path in health_check_paths and request.method.upper() == "GET":
                     return await call_next(request)
                 try:
                     if header_token is None:
    
  • tests/server/test_app.py+59 0 modified
    @@ -1,3 +1,6 @@
    +import base64
    +from collections.abc import Generator
    +
     import pytest
     from fastapi.testclient import TestClient
     
    @@ -48,3 +51,59 @@ def test_app_add_csrf_middleware_when_enabled(enabled: bool):
                 if "CsrfMiddleware" in str(middleware)
             ]
             assert len(matching) == (1 if enabled else 0)
    +
    +
    +class TestAuthMiddleware:
    +    AUTH_STRING = "admin:test"
    +    VALID_AUTH_HEADER = "Basic " + base64.b64encode(b"admin:test").decode()
    +
    +    @pytest.fixture()
    +    def anonymous_client(self) -> Generator[TestClient, None, None]:
    +        with temporary_settings({PREFECT_SERVER_API_AUTH_STRING: self.AUTH_STRING}):
    +            app = create_app(ignore_cache=True)
    +            yield TestClient(app)
    +
    +    def test_health_bypasses_auth(self, anonymous_client: TestClient):
    +        response = anonymous_client.get("/api/health")
    +        assert response.status_code == 200
    +
    +    def test_ready_bypasses_auth(self, anonymous_client: TestClient):
    +        response = anonymous_client.get("/api/ready")
    +        assert response.status_code == 200
    +
    +    def test_other_routes_require_auth(self, anonymous_client: TestClient):
    +        response = anonymous_client.get("/api/version")
    +        assert response.status_code == 401
    +
    +    def test_valid_auth_allows_access(self, anonymous_client: TestClient):
    +        response = anonymous_client.get(
    +            "/api/version",
    +            headers={"Authorization": self.VALID_AUTH_HEADER},
    +        )
    +        assert response.status_code == 200
    +
    +    def test_path_ending_in_health_does_not_bypass_auth(
    +        self, anonymous_client: TestClient
    +    ):
    +        """Regression: suffix matching allowed bypass via resource names like
    +        /api/variables/name/system-health"""
    +        response = anonymous_client.get("/api/variables/name/system-health")
    +        assert response.status_code == 401
    +
    +    def test_path_ending_in_ready_does_not_bypass_auth(
    +        self, anonymous_client: TestClient
    +    ):
    +        response = anonymous_client.get("/api/variables/name/system-ready")
    +        assert response.status_code == 401
    +
    +    def test_host_header_manipulation_does_not_bypass_auth(
    +        self, anonymous_client: TestClient
    +    ):
    +        """Regression: Host header like 'localhost/health?' causes
    +        request.url.path to evaluate to '/health' while the real route
    +        is still served."""
    +        response = anonymous_client.get(
    +            "/api/version",
    +            headers={"Host": "localhost/health?"},
    +        )
    +        assert response.status_code == 401
    

Vulnerability mechanics

Root cause

"The authentication middleware improperly exempted any URL path ending with 'health' or 'ready' from authentication checks."

Attack vector

An attacker can bypass authentication by crafting resource names that end with 'health' or 'ready'. This allows them to access sensitive endpoints without providing valid credentials. The vulnerability affects various resource types including variables, flows, work pools, work queues, and deployments. Exploiting this can lead to unauthorized access to sensitive information such as API keys and database credentials stored in Prefect Variables [ref_id=1].

Affected code

The vulnerability existed in the authentication middleware logic within `src/prefect/server/api/server.py`. The `token_validation` middleware previously used `request.url.path.endswith(("health", "ready"))` to exempt GET requests for health probes. The patch modifies this to check for exact path matches against a set of health check paths.

What the fix does

The patch modifies the authentication middleware to use exact path matching instead of suffix matching for health check exemptions. It now checks if the application path is exactly '/health' or '/ready' using `scope["path"]` which cannot be spoofed via Host header manipulation. This prevents attackers from creating resources with names like '/api/variables/name/system-health' to bypass authentication [ref_id=1][patch_id=4466694].

Preconditions

  • inputThe attacker must be able to make unauthenticated HTTP requests to the Prefect server.

Generated on Jun 2, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.