resolved RESOLVED PASS ยท 13 tool calls ยท 118 s ยท fastapi/fastapi
๐ Strip whitespaces from `Authorization` header credentials
## Description
Fixes whitespace handling in authorization header parameter extraction to comply with [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750#section-2.1).
## Problem
The current implementation preserves leading/trailing whitespace in the extracted credential parameter. While RFC 6750 allows one or more spaces (`1*SP`) between "Bearer" and the token, the token definition (`b64token`) doesn't include whitespace characters, meaning spaces should be treated as separators only.
This causes the extracted credentials to include extraneous whitespace, which can lead to authentication failures.
## Solution
Added `.strip()` to the parameter extraction to remove leading and trailing whitespace:
```python
return scheme, param.strip()
```
## Reproduction
### Before the fix:
```python
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
app = FastAPI()
security = HTTPBearer()
@app.get("/users/me")
def read_current_user(
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]
):
return {"scheme": credentials.scheme, "credentials": credentials.credentials}
```
**Test case 1: Single space (correct)**
```bash
curl -X 'GET' \
'http://127.0.0.1:8000/users/me' \
-H 'Authorization: Bearer xxx'
```
Response: `{"scheme":"Bearer","credentials":"xxx"}` โ
**Test case 2: Multiple spaces**
```bash
curl -X 'GET' \
'http://127.0.0.1:8000/users/me' \
-H 'Authorization: Bearer xxx'
```
Response: `{"scheme":"Bearer","credentials":" xxx"}` โ
**Test case 3: Trailing whitespace**
```bash
curl -X 'GET' \
'http://127.0.0.1:8000/users/me' \
-H 'Authorization: Bearer xxx '
```
Response: `{"scheme":"Bearer","credentials":"xxx "}` โ
### After the fix:
All three test cases now return: `{"scheme":"Bearer","credentials":"xxx"}` โ
## Changes
- Modified `get_authorization_scheme_param()` in `fastapi/security/utils.py` to strip whitespace from the parameter
- Added test cases to verify the fix handles multiple spaces and trailing whitespace correctly
## Checklist
- [x] Tests added to cover the bug fix
- [x] Existing tests pass| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"get_authorization_scheme_param\" fastapi/security/utils.py"
} | {"status": "ok", "stdout": "4:def get_authorization_scheme_param(\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "4:def get_authorization_scheme_param(\n", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "sed -n '1,30p' fastapi/security/utils.py"
} | {"status": "ok", "stdout": "from typing import Optional\n\n\ndef get_authorization_scheme_param(\n authorization_header_value: Optional[str],\n) -> tuple[str, sfull{"status": "ok", "stdout": "from typing import Optional\n\n\ndef get_authorization_scheme_param(\n authorization_header_value: Optional[str],\n) -> tuple[str, str]:\n if not authorization_header_value:\n return \"\", \"\"\n scheme, _, param = authorization_header_value.partition(\" \")\n return scheme, param\n", "stderr": "", "exit_code": 0} |
| 3 | write_file | {
"content": "from fastapi.security.utils import get_authorization_scheme_param\n\ndef test_get_authorization_scheme_param_whitespace():\n # Test case 1: Single space (correct)\n assert get_authorization_scheme_param(\"Bearer xxx\") == (\"Bearer\", \"xxx\")\n \n # Test case 2: Multiple spaces\n assert get_authorization_scheme_param(\"Bearer xxx\") == (\"Bearer\", \"xxx\")\n \n # Test case 3: Trailing whitespace\n assert get_authorization_scheme_param(\"Bearer xxx \") == (\"Bearer\", \"xxx\")\n \n # Test case 4: Leading whitespace in param\n assert get_authorization_scheme_param(\"Bearer xxx\") == (\"Bearer\", \"xxx\")\n \n # Test case 5: No param\n assert get_authorization_scheme_param(\"Bearer\") == (\"Bearer\", \"\")\n \n # Test case 6: Empty header\n assert get_authorization_scheme_param(None) == (\"\", \"\")\n assert get_authorization_scheme_param(\"\") == (\"\", \"\")\n\nif __name__ == \"__main__\":\n test_get_authorization_scheme_param_whitespace()\n print(\"All tests passed!\")\n",
"filepath": "/tmp/test_auth_whitespace.py"
} | {"status": "error", "error_type": "FileWriteError", "error_message": "Path traversal detected: '/tmp/test_auth_whitespace.py' escapes workspace root."}full{"status": "error", "error_type": "FileWriteError", "error_message": "Path traversal detected: '/tmp/test_auth_whitespace.py' escapes workspace root."} |
| 4 | write_file | {
"content": "from fastapi.security.utils import get_authorization_scheme_param\n\ndef test_get_authorization_scheme_param_whitespace():\n # Test case 1: Single space (correct)\n assert get_authorization_scheme_param(\"Bearer xxx\") == (\"Bearer\", \"xxx\")\n \n # Test case 2: Multiple spaces\n assert get_authorization_scheme_param(\"Bearer xxx\") == (\"Bearer\", \"xxx\")\n \n # Test case 3: Trailing whitespace\n assert get_authorization_scheme_param(\"Bearer xxx \") == (\"Bearer\", \"xxx\")\n \n # Test case 4: Leading whitespace in param\n assert get_authorization_scheme_param(\"Bearer xxx\") == (\"Bearer\", \"xxx\")\n \n # Test case 5: No param\n assert get_authorization_scheme_param(\"Bearer\") == (\"Bearer\", \"\")\n \n # Test case 6: Empty header\n assert get_authorization_scheme_param(None) == (\"\", \"\")\n assert get_authorization_scheme_param(\"\") == (\"\", \"\")\n\nif __name__ == \"__main__\":\n test_get_authorization_scheme_param_whitespace()\n print(\"All tests passed!\")\n",
"filepath": "test_auth_whitespace.py"
} | {"status": "ok", "filepath": "test_auth_whitespace.py", "size": 981}full{"status": "ok", "filepath": "test_auth_whitespace.py", "size": 981} |
| 5 | run_command | {
"command": "python test_auth_whitespace.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"/workspace/test_auth_whitespace.py\", line 24, ifull{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"/workspace/test_auth_whitespace.py\", line 24, in <module>\n test_get_authorization_scheme_param_whitespace()\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^\n File \"/workspace/test_auth_whitespace.py\", line 8, in test_get_authorization_scheme_param_whitespace\n assert get_authorization_scheme_param(\"Bearer xxx\") == (\"Bearer\", \"xxx\")\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError\n", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n File \"/workspace/test_auth_whitespace.py\", line 24, in <module>\n test_get_authorization_scheme_param_whitespace()\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^\n File \"/workspace/test_auth_whitespace.py\", line 8, in test_get_authorization_scheme_param_whitespace\n assert get_authorization_scheme_param(\"Bearer xxx\") == (\"Bearer\", \"xxx\")\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError\n", "exit_code": 1}} |
| 6 | edit_file | {
"filepath": "fastapi/security/utils.py",
"new_string": " scheme, _, param = authorization_header_value.partition(\" \")\n return scheme, param.strip()",
"old_string": " scheme, _, param = authorization_header_value.partition(\" \")\n return scheme, param"
} | {"status": "ok", "filepath": "fastapi/security/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/security/utils.py\n+++ b/fastapi/securitfull{"status": "ok", "filepath": "fastapi/security/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/security/utils.py\n+++ b/fastapi/security/utils.py\n@@ -7,4 +7,4 @@\n if not authorization_header_value:\n return \"\", \"\"\n scheme, _, param = authorization_header_value.partition(\" \")\n- return scheme, param\n+ return scheme, param.strip()\n", "is_truncated": false} |
| 7 | run_command | {
"command": "python test_auth_whitespace.py"
} | {"status": "ok", "stdout": "All tests passed!\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "All tests passed!\n", "stderr": "", "exit_code": 0} |
| 8 | run_command | {
"command": "pytest tests/security/utils.py -q"
} | {"status": "error", "error_type": "CommandError", "error_message": "ERROR: file or directory not found: tests/security/utils.py\n\n", "details": {"stdout": "\nnfull{"status": "error", "error_type": "CommandError", "error_message": "ERROR: file or directory not found: tests/security/utils.py\n\n", "details": {"stdout": "\nno tests ran in 0.11s\n", "stderr": "ERROR: file or directory not found: tests/security/utils.py\n\n", "exit_code": 4}} |
| 9 | run_command | {
"command": "find tests -name \"*security*\""
} | {"status": "ok", "stdout": "tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py\ntests/test_security_oauth2.py\ntests/test_security_scfull{"status": "ok", "stdout": "tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py\ntests/test_security_oauth2.py\ntests/test_security_scopes.py\ntests/test_security_http_digest_optional.py\ntests/test_top_level_security_scheme_in_openapi.py\ntests/test_security_api_key_cookie_optional.py\ntests/test_security_oauth2_authorization_code_bearer_description.py\ntests/test_security_oauth2_optional_description.py\ntests/test_webhooks_security.py\ntests/test_security_http_basic_optional.py\ntests/test_security_api_key_query.py\ntests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py\ntests/test_security_scopes_sub_dependency.py\ntests/test_security_openid_connect_description.py\ntests/test_security_api_key_header_optional.py\ntests/test_dependency_security_overrides.py\ntests/test_security_http_digest.py\ntests/test_security_openid_connect.py\ntests/test_security_http_base.py\ntests/test_security_http_bearer_description.py\ntests/test_security_api_key_header_description.py\ntests/test_security_api_key_cookie.py\ntests/test_security_http_basic_realm.py\ntests/test_security_http_bearer_optional.py\ntests/test_security_api_key_header.py\ntests/test_security_http_digest_description.py\ntests/test_security_http_bearer.py\ntests/test_security_oauth2_password_bearer_optional.py\ntests/test_security_api_key_query_description.py\ntests/test_security_scopes_dont_propagate.py\ntests/test_security_oauth2_authorization_code_bearer.py\ntests/test_security_oauth2_password_bearer_optional_description.py\ntests/test_security_api_key_cookie_description.py\ntests/test_security_openid_connect_optional.py\ntests/test_security_api_key_query_optional.py\ntests/test_tutorial/test_security\ntests/test_security_http_base_description.py\ntests/test_security_http_basic_realm_description.py\ntests/test_security_http_base_optional.py\ntests/test_security_oauth2_optional.py\n", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "pytest tests/test_security_http_bearer.py -q"
} | {"status": "ok", "stdout": ".... [100%]\n=============================== warnings summary ===============================\n../../../../../../kaggle/tmp/envs/ovefull{"status": "ok", "stdout": ".... [100%]\n=============================== warnings summary ===============================\n../../../../../../kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45\n /kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.\n _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n4 passed, 1 warning in 1.57s\n", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "rm test_auth_whitespace.py"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "cd /workspace && git status --short && git diff --stat"
} | {"status": "ok", "stdout": " M fastapi/security/utils.py\n fastapi/security/utils.py | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n", "stderr": "", "efull{"status": "ok", "stdout": " M fastapi/security/utils.py\n fastapi/security/utils.py | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "python -c \"import fastapi.security.utils\""
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 14 | submit_patch | {} | {"status": "ok", "patch_size": 404, "files_changed": 1}full{"status": "ok", "patch_size": 404, "files_changed": 1} |
diff --git a/fastapi/security/utils.py b/fastapi/security/utils.py
index 002e68b4..fd349aec 100644
--- a/fastapi/security/utils.py
+++ b/fastapi/security/utils.py
@@ -7,4 +7,4 @@ def get_authorization_scheme_param(
if not authorization_header_value:
return "", ""
scheme, _, param = authorization_header_value.partition(" ")
- return scheme, param
+ return scheme, param.strip()
......... [100%]
=============================== warnings summary ===============================
../../../../../../kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
/kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
_PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
9 passed, 1 warning in 0.66s