failed WRONG_FIX wrong_fix · 32 tool calls · 282 s · fastapi/fastapi
♻️ Refactor logic to handle OpenAPI and Swagger UI escaping data ♻️ Refactor logic to handle OpenAPI and Swagger UI escaping data OpenAPI, do not store `root_path` in servers: the only way this could be a problem is if there was a misconfigured proxy that somehow allowed an attacker client to set `x-forwarded-*` headers and passed them along. For a proxy (or server) to do this, it normally has to be intentionally/explicitly misconfigured. But again, doesn't hurt to have it there. Escape Swagger UI configs: I wouldn't consider this really important, the Swagger UI logic takes only data from the same developer building the app, I don't see a feasible scenario where this could be a problem, but probably also doesn't hurt much to have it there. --- I received several "security reports" with this, I suspect some automated scanning tool that checks any JSON inside of HTML or similar. I don't consider these security issues, but also think it's probably fine to have these changes.
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"root_path\" fastapi/openapi.py fastapi/openapi/utils.py | head -20"
} | {"status": "ok", "stdout": "", "stderr": "grep: fastapi/openapi.py: No such file or directory\n", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "grep: fastapi/openapi.py: No such file or directory\n", "exit_code": 0} |
| 2 | run_command | {
"command": "find fastapi -name \"*openapi*\""
} | {"status": "ok", "stdout": "fastapi/openapi\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "fastapi/openapi\n", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "grep -rn \"root_path\" fastapi/openapi | head -20"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "grep -rn \"root_path\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/applications.py:300: (`root_path`) if it's different from `/`.\nfastapi/applications.py:637: \"openapi_prefix\" has been depfull{"status": "ok", "stdout": "fastapi/applications.py:300: (`root_path`) if it's different from `/`.\nfastapi/applications.py:637: \"openapi_prefix\" has been deprecated in favor of \"root_path\", which\nfastapi/applications.py:643: root_path: Annotated[\nfastapi/applications.py:658: app = FastAPI(root_path=\"/api/v1\")\nfastapi/applications.py:663: root_path_in_servers: Annotated[\nfastapi/applications.py:668: in the autogenerated OpenAPI using the `root_path`.\nfastapi/applications.py:678: app = FastAPI(root_path_in_servers=False)\nfastapi/applications.py:886: self.root_path_in_servers = root_path_in_servers\nfastapi/applications.py:934: '\"openapi_prefix\" has been deprecated in favor of \"root_path\", which '\nfastapi/applications.py:951: self.root_path = root_path or openapi_prefix\nfastapi/applications.py:1108: root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\nfastapi/applications.py:1109: if root_path not in server_urls:\nfastapi/applications.py:1110: if root_path and self.root_path_in_servers:\nfastapi/applications.py:1111: self.servers.insert(0, {\"url\": root_path})\nfastapi/applications.py:1112: server_urls.add(root_path)\nfastapi/applications.py:1119: root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\nfastapi/applications.py:1120: openapi_url = root_path + self.openapi_url\nfastapi/applications.py:1123: oauth2_redirect_url = root_path + oauth2_redirect_url\nfastapi/applications.py:1147: root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\nfastapi/applications.py:1148: openapi_url = root_path + self.openapi_url\n", "stderr": "", "exit_code": 0} |
| 5 | run_command | {
"command": "grep -rn \"swagger_ui_parameters\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/applications.py:756: swagger_ui_parameters: Annotated[\nfastapi/applications.py:891: self.swagger_ui_parameters = swagger_uifull{"status": "ok", "stdout": "fastapi/applications.py:756: swagger_ui_parameters: Annotated[\nfastapi/applications.py:891: self.swagger_ui_parameters = swagger_ui_parameters\nfastapi/applications.py:1129: swagger_ui_parameters=self.swagger_ui_parameters,\nfastapi/openapi/docs.py:109: swagger_ui_parameters: Annotated[\nfastapi/openapi/docs.py:134: current_swagger_ui_parameters = swagger_ui_default_parameters.copy()\nfastapi/openapi/docs.py:135: if swagger_ui_parameters:\nfastapi/openapi/docs.py:136: current_swagger_ui_parameters.update(swagger_ui_parameters)\nfastapi/openapi/docs.py:157: for key, value in current_swagger_ui_parameters.items():\n", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "sed -n '1100,1120p' fastapi/applications.py"
} | {"status": "ok", "stdout": " return self.openapi_schema\n\n def setup(self) -> None:\n if self.openapi_url:\n urls = (server_data.get(\"url\") for server_data ifull{"status": "ok", "stdout": " return self.openapi_schema\n\n def setup(self) -> None:\n if self.openapi_url:\n urls = (server_data.get(\"url\") for server_data in self.servers)\n server_urls = {url for url in urls if url}\n\n async def openapi(req: Request) -> JSONResponse:\n root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n if root_path not in server_urls:\n if root_path and self.root_path_in_servers:\n self.servers.insert(0, {\"url\": root_path})\n server_urls.add(root_path)\n return JSONResponse(self.openapi())\n\n self.add_route(self.openapi_url, openapi, include_in_schema=False)\n if self.openapi_url and self.docs_url:\n\n async def swagger_ui_html(req: Request) -> HTMLResponse:\n root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n openapi_url = root_path + self.openapi_url\n", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "sed -n '130,160p' fastapi/openapi/docs.py"
} | {"status": "ok", "stdout": " Read more about it in the\n [FastAPI docs for Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/)\n anfull{"status": "ok", "stdout": " Read more about it in the\n [FastAPI docs for Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/)\n and the [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/).\n \"\"\"\n current_swagger_ui_parameters = swagger_ui_default_parameters.copy()\n if swagger_ui_parameters:\n current_swagger_ui_parameters.update(swagger_ui_parameters)\n\n html = f\"\"\"\n <!DOCTYPE html>\n <html>\n <head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <link type=\"text/css\" rel=\"stylesheet\" href=\"{swagger_css_url}\">\n <link rel=\"shortcut icon\" href=\"{swagger_favicon_url}\">\n <title>{title}</title>\n </head>\n <body>\n <div id=\"swagger-ui\">\n </div>\n <script src=\"{swagger_js_url}\"></script>\n <!-- `SwaggerUIBundle` is now available on the page -->\n <script>\n const ui = SwaggerUIBundle({{\n url: '{openapi_url}',\n \"\"\"\n\n for key, value in current_swagger_ui_parameters.items():\n html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\\n\"\n\n if oauth2_redirect_url:\n", "stderr": "", "exit_code": 0} |
| 8 | run_command | {
"command": "sed -n '1100,1115p' fastapi/applications.py"
} | {"status": "ok", "stdout": " return self.openapi_schema\n\n def setup(self) -> None:\n if self.openapi_url:\n urls = (server_data.get(\"url\") for server_data ifull{"status": "ok", "stdout": " return self.openapi_schema\n\n def setup(self) -> None:\n if self.openapi_url:\n urls = (server_data.get(\"url\") for server_data in self.servers)\n server_urls = {url for url in urls if url}\n\n async def openapi(req: Request) -> JSONResponse:\n root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n if root_path not in server_urls:\n if root_path and self.root_path_in_servers:\n self.servers.insert(0, {\"url\": root_path})\n server_urls.add(root_path)\n return JSONResponse(self.openapi())\n\n self.add_route(self.openapi_url, openapi, include_in_schema=False)\n", "stderr": "", "exit_code": 0} |
| 9 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/applications.py\")\ns = p.read_text()\nold = \"\"\" if root_path not in server_urls:\n if root_path and self.root_path_in_servers:\n self.servers.insert(0, {\"url\": root_path})\n server_urls.add(root_path)\"\"\"\nnew = \"\"\" if root_path not in server_urls:\n pass\"\"\"\n# Wait, the problem says \"OpenAPI, do not store root_path in servers\".\n# The current code inserts it into self.servers.\n# If I just remove it, it won't be stored.\n# But wait, if I just remove the block, I should also remove the if root_path not in server_urls.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/applications.py\")\ns = p.read_text()\nold = \"\"\" if root_path not in server_urls:\n if root_path and self.root_path_in_servers:\n self.servers.insert(0, {\"url\": root_path})\n server_urls.add(root_path)\"\"\"\nnew = \"\"\" if root_path not in server_urls:\n pass\"\"\"\n# Actually, the request is \"OpenAPI, do not store root_path in servers\".\n# The code is:\n# if root_path not in server_urls:\n# if root_path and self.root_path_in_servers:\n# self.servers.insert(0, {\"url\": root_path})\n# server_urls.add(root_path)\n#\n# If I remove this, root_path will not be added to self.servers.\n# Let's just remove the whole block.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/applications.py\")\ns = p.read_text()\nold = \"\"\" if root_path not in server_urls:\n if root_path and self.root_path_in_servers:\n self.servers.insert(0, {\"url\": root_path})\n server_urls.add(root_path)\"\"\"\nnew = \"\"\n# I need to be careful about indentation and the surrounding code.\n# Let's use a more precise replacement.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/applications.py\")\ns = p.read_text()\nold = \"\"\" if root_path not in server_urls:\n if root_path and self.root_path_in_servers:\n self.servers.insert(0, {\"url\": root_path})\n server_urls.add(root_path)\"\"\"\n# I will replace it with nothing, but I must ensure the indentation of the next line is correct.\n# The next line is \" return JSONResponse(self.openapi())\"\n# Let's see the context again.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "sed -n '1105,1112p' fastapi/applications.py"
} | {"status": "ok", "stdout": " server_urls = {url for url in urls if url}\n\n async def openapi(req: Request) -> JSONResponse:\n root_path = req.scope.get(\"root_full{"status": "ok", "stdout": " server_urls = {url for url in urls if url}\n\n async def openapi(req: Request) -> JSONResponse:\n root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n if root_path not in server_urls:\n if root_path and self.root_path_in_servers:\n self.servers.insert(0, {\"url\": root_path})\n server_urls.add(root_path)\n", "stderr": "", "exit_code": 0} |
| 14 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/applications.py\")\ns = p.read_text()\nold = \"\"\" if root_path not in server_urls:\n if root_path and self.root_path_in_servers:\n self.servers.insert(0, {\"url\": root_path})\n server_urls.add(root_path)\"\"\"\nnew = \"\"\n# I will replace it with an empty string.\n# Wait, if I replace it with an empty string, the indentation of the next line \" return JSONResponse(self.openapi())\" will be fine.\n# But I should probably remove the empty line if it's created.\n# Let's just replace the whole block including the if.\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 15 | run_command | {
"command": "sed -n '1105,1115p' fastapi/applications.py"
} | {"status": "ok", "stdout": " server_urls = {url for url in urls if url}\n\n async def openapi(req: Request) -> JSONResponse:\n root_path = req.scope.get(\"root_full{"status": "ok", "stdout": " server_urls = {url for url in urls if url}\n\n async def openapi(req: Request) -> JSONResponse:\n root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n\n return JSONResponse(self.openapi())\n\n self.add_route(self.openapi_url, openapi, include_in_schema=False)\n if self.openapi_url and self.docs_url:\n\n async def swagger_ui_html(req: Request) -> HTMLResponse:\n", "stderr": "", "exit_code": 0} |
| 16 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/applications.py\")\ns = p.read_text()\n# Remove the extra newline created by the replacement\n# The current state is:\n# root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n#\n# return JSONResponse(self.openapi())\n#\n# I want:\n# root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n# return JSONResponse(self.openapi())\n#\n# But wait, root_path is now unused in that function.\n# If root_path is unused, I should remove it too.\n#\n# Let's check if root_path is used anywhere else in that function.\n# No, it was only used in the if block I removed.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 17 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/applications.py\")\ns = p.read_text()\nold = \"\"\" async def openapi(req: Request) -> JSONResponse:\n root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n\n return JSONResponse(self.openapi())\"\"\"\nnew = \"\"\" async def openapi(req: Request) -> JSONResponse:\n return JSONResponse(self.openapi())\"\"\"\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 18 | run_command | {
"command": "sed -n '1105,1115p' fastapi/applications.py"
} | {"status": "ok", "stdout": " server_urls = {url for url in urls if url}\n\n async def openapi(req: Request) -> JSONResponse:\n return JSONResponse(self.openapi(full{"status": "ok", "stdout": " server_urls = {url for url in urls if url}\n\n async def openapi(req: Request) -> JSONResponse:\n return JSONResponse(self.openapi())\n\n self.add_route(self.openapi_url, openapi, include_in_schema=False)\n if self.openapi_url and self.docs_url:\n\n async def swagger_ui_html(req: Request) -> HTMLResponse:\n root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n openapi_url = root_path + self.openapi_url\n", "stderr": "", "exit_code": 0} |
| 19 | run_command | {
"command": "sed -n '150,165p' fastapi/openapi/docs.py"
} | {"status": "ok", "stdout": " <script src=\"{swagger_js_url}\"></script>\n <!-- `SwaggerUIBundle` is now available on the page -->\n <script>\n const ui = Swaggefull{"status": "ok", "stdout": " <script src=\"{swagger_js_url}\"></script>\n <!-- `SwaggerUIBundle` is now available on the page -->\n <script>\n const ui = SwaggerUIBundle({{\n url: '{openapi_url}',\n \"\"\"\n\n for key, value in current_swagger_ui_parameters.items():\n html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\\n\"\n\n if oauth2_redirect_url:\n html += f\"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',\"\n\n html += \"\"\"\n presets: [\n SwaggerUIBundle.presets.apis,\n", "stderr": "", "exit_code": 0} |
| 20 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/openapi/docs.py\")\ns = p.read_text()\nold = \"\"\" for key, value in current_swagger_ui_parameters.items():\n html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\"»\n# Wait, the quote is wrong.\nPY\n"
} | {"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 4\n old = \"\"\" for key, value in current_swagger_ui_parameters.itefull{"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 4\n old = \"\"\" for key, value in current_swagger_ui_parameters.items():\n ^\nSyntaxError: unterminated triple-quoted string literal (detected at line 6)\n", "details": {"stdout": "", "stderr": " File \"<stdin>\", line 4\n old = \"\"\" for key, value in current_swagger_ui_parameters.items():\n ^\nSyntaxError: unterminated triple-quoted string literal (detected at line 6)\n", "exit_code": 1}} |
| 21 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/openapi/docs.py\")\ns = p.read_text()\nold = \"\"\" for key, value in current_swagger_ui_parameters.items():\n html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\"\"\"\nnew = \"\"\" for key, value in current_swagger_ui_parameters.items():\n html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\"»\n# Still wrong. I should use a different way to escape the f-string.\nPY\n"
} | {"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 6\n new = \"\"\" for key, value in current_swagger_ui_parameters.itefull{"status": "error", "error_type": "CommandError", "error_message": " File \"<stdin>\", line 6\n new = \"\"\" for key, value in current_swagger_ui_parameters.items():\n ^\nSyntaxError: unterminated triple-quoted string literal (detected at line 8)\n", "details": {"stdout": "", "stderr": " File \"<stdin>\", line 6\n new = \"\"\" for key, value in current_swagger_ui_parameters.items():\n ^\nSyntaxError: unterminated triple-quoted string literal (detected at line 8)\n", "exit_code": 1}} |
| 22 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/openapi/docs.py\")\ns = p.read_text()\n# I want to change:\n# for key, value in current_swagger_ui_parameters.items():\n# html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n# to something that escapes the data.\n# Actually, json.dumps already escapes the data for JSON.\n# But this is being inserted into a JavaScript object literal inside an HTML script tag.\n# The issue is about \"escaping data\".\n# If json.dumps is used, it should be safe from most XSS if it's just a JS object.\n# However, if the value contains `</script>`, it could close the script tag.\n#\n# Wait, the problem says \"Escape Swagger UI configs\".\n# Let's see how it's currently done:\n# html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n#\n# If I use json.dumps, it produces a JSON string.\n# If I want to be extra safe, I should make sure that it doesn't contain `</script>`.\n#\n# But wait, maybe the \"escaping\" they mean is just using json.dumps?\n# It already uses json.dumps.\n#\n# Let's re-read: \"Escape Swagger UI configs: I wouldn't consider this really important... but probably also doesn't hurt much to have it there.\"\n#\n# If I look at the current code:\n# for key, value in current_swagger_ui_parameters.items():\n# html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n#\n# This is already using json.dumps.\n# Maybe they want to use a more robust way to embed JSON in HTML?\n#\n# Actually, if I look at the code again:\n# html = f\"\"\"\n# ...\n# <script>\n# const ui = SwaggerUIBundle({{\n# url: '{openapi_url}',\n# \"\"\"\n#\n# Then the loop:\n# for key, value in current_swagger_ui_parameters.items():\n# html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n#\n# The `openapi_url` is NOT json.dumps-ed. It's just `{openapi_url}`.\n# That could be a problem.\n#\n# Let's check if openapi_url is escaped.\n# openapi_url = root_path + self.openapi_url\n#\n# If I change `{openapi_url}` to `{json.dumps(openapi_url)}`, it would be safer.\n# But wait, the current code is:\n# url: '{openapi_url}',\n#\n# If I change it to:\n# url: {json.dumps(openapi_url)},\n# it would be safer.\n#\n# Let's check the loop again.\n# html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n#\n# This part seems okay because it uses json.dumps.\n#\n# Wait, I see something.\n# The `openapi_url` is inside single quotes: `url: '{openapi_url}',`\n# If `openapi_url` contains a single quote, it will break the JS.\n#\n# If I change it to `url: {json.dumps(openapi_url)},`, it will be a proper JS string.\n#\n# Let's see where `openapi_url` is defined in `fastapi/openapi/docs.py`.\n# It's passed as an argument to `get_swagger_ui_html`.\n#\n# Let's look at `fastapi/openapi/docs.py` again.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 23 | run_command | {
"command": "sed -n '140,155p' fastapi/openapi/docs.py"
} | {"status": "ok", "stdout": " <html>\n <head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <link type=\"text/css\" rel=\"stylesfull{"status": "ok", "stdout": " <html>\n <head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <link type=\"text/css\" rel=\"stylesheet\" href=\"{swagger_css_url}\">\n <link rel=\"shortcut icon\" href=\"{swagger_favicon_url}\">\n <title>{title}</title>\n </head>\n <body>\n <div id=\"swagger-ui\">\n </div>\n <script src=\"{swagger_js_url}\"></script>\n <!-- `SwaggerUIBundle` is now available on the page -->\n <script>\n const ui = SwaggerUIBundle({{\n url: '{openapi_url}',\n \"\"\"\n", "stderr": "", "exit_code": 0} |
| 24 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/openapi/docs.py\")\ns = p.read_text()\nold = \" url: '{openapi_url}',\"\nnew = \" url: {json.dumps(openapi_url)},\"\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 25 | run_command | {
"command": "sed -n '150,155p' fastapi/openapi/docs.py"
} | {"status": "ok", "stdout": " <script src=\"{swagger_js_url}\"></script>\n <!-- `SwaggerUIBundle` is now available on the page -->\n <script>\n const ui = Swaggefull{"status": "ok", "stdout": " <script src=\"{swagger_js_url}\"></script>\n <!-- `SwaggerUIBundle` is now available on the page -->\n <script>\n const ui = SwaggerUIBundle({{\n url: {json.dumps(openapi_url)},\n \"\"\"\n", "stderr": "", "exit_code": 0} |
| 26 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/openapi/docs.py\")\ns = p.read_text()\n# Wait, I just realized that the f-string in `get_swagger_ui_html` is:\n# html = f\"\"\"\n# ...\n# <script>\n# const ui = SwaggerUIBundle({{\n# url: {json.dumps(openapi_url)},\n# \"\"\"\n#\n# But `json` is not imported in `get_swagger_ui_html`'s scope if it's not imported in the module.\n# Let's check imports in `fastapi/openapi/docs.py`.\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 27 | run_command | {
"command": "head -n 30 fastapi/openapi/docs.py"
} | {"status": "ok", "stdout": "import json\nfrom typing import Annotated, Any\n\nfrom annotated_doc import Doc\nfrom fastapi.encoders import jsonable_encoder\nfromfull{"status": "ok", "stdout": "import json\nfrom typing import Annotated, Any\n\nfrom annotated_doc import Doc\nfrom fastapi.encoders import jsonable_encoder\nfrom starlette.responses import HTMLResponse\n\nswagger_ui_default_parameters: Annotated[\n dict[str, Any],\n Doc(\n \"\"\"\n Default configurations for Swagger UI.\n\n You can use it as a template to add any other configurations needed.\n \"\"\"\n ),\n] = {\n \"dom_id\": \"#swagger-ui\",\n \"layout\": \"BaseLayout\",\n \"deepLinking\": True,\n \"showExtensions\": True,\n \"showCommonExtensions\": True,\n}\n\n\ndef get_swagger_ui_html(\n *,\n openapi_url: Annotated[\n str,\n Doc(\n", "stderr": "", "exit_code": 0} |
| 28 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/openapi/docs.py\")\ns = p.read_text()\n# The f-string is:\n# html = f\"\"\"\n# ...\n# <script>\n# const ui = SwaggerUIBundle({{\n# url: {json.dumps(openapi_url)},\n# \"\"\"\n#\n# But wait, this is an f-string. {json.dumps(openapi_url)} will be evaluated at runtime.\n# This is correct.\n#\n# However, I should check if there are other things that need escaping.\n# The loop:\n# for key, value in current_swagger_ui_parameters.items():\n# html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n#\n# This is also using json.dumps.\n#\n# What about `oauth2_redirect_url`?\n# if oauth2_redirect_url:\n# html += f\"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',\"\n#\n# This is also not using json.dumps.\n# Let's change it to:\n# html += f\"oauth2RedirectUrl: window.location.origin + {json.dumps(oauth2_redirect_url)},\"\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 29 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/openapi/docs.py\")\ns = p.read_text()\nold = \" html += f\\\"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',\"\nnew = \" html += f\\\"oauth2RedirectUrl: window.location.origin + {json.dumps(oauth2_redirect_url)},\"\nt = s.replace(old, new)\np.write_text(t)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 30 | run_command | {
"command": "sed -n '160,170p' fastapi/openapi/docs.py"
} | {"status": "ok", "stdout": " if oauth2_redirect_url:\n html += f\"oauth2RedirectUrl: window.location.origin + {json.dumps(oauth2_redirect_url)},\"\n\n html += \full{"status": "ok", "stdout": " if oauth2_redirect_url:\n html += f\"oauth2RedirectUrl: window.location.origin + {json.dumps(oauth2_redirect_url)},\"\n\n html += \"\"\"\n presets: [\n SwaggerUIBundle.presets.apis,\n SwaggerUIBundle.SwaggerUIStandalonePreset\n ],\n })\"\"\"\n\n if init_oauth:\n", "stderr": "", "exit_code": 0} |
| 31 | run_command | {
"command": "git diff"
} | {"status": "ok", "stdout": "diff --git a/fastapi/applications.py b/fastapi/applications.py\nindex ed05a1ff..82dbfb69 100644\n--- a/fastapi/applications.py\n+++ full{"status": "ok", "stdout": "diff --git a/fastapi/applications.py b/fastapi/applications.py\nindex ed05a1ff..82dbfb69 100644\n--- a/fastapi/applications.py\n+++ b/fastapi/applications.py\n@@ -1105,11 +1105,6 @@ class FastAPI(Starlette):\n server_urls = {url for url in urls if url}\n \n async def openapi(req: Request) -> JSONResponse:\n- root_path = req.scope.get(\"root_path\", \"\").rstrip(\"/\")\n- if root_path not in server_urls:\n- if root_path and self.root_path_in_servers:\n- self.servers.insert(0, {\"url\": root_path})\n- server_urls.add(root_path)\n return JSONResponse(self.openapi())\n \n self.add_route(self.openapi_url, openapi, include_in_schema=False)\ndiff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py\nindex b845f87c..92acff08 100644\n--- a/fastapi/openapi/docs.py\n+++ b/fastapi/openapi/docs.py\n@@ -151,14 +151,14 @@ def get_swagger_ui_html(\n <!-- `SwaggerUIBundle` is now available on the page -->\n <script>\n const ui = SwaggerUIBundle({{\n- url: '{openapi_url}',\n+ url: {json.dumps(openapi_url)},\n \"\"\"\n \n for key, value in current_swagger_ui_parameters.items():\n html += f\"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\\n\"\n \n if oauth2_redirect_url:\n- html += f\"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',\"\n+ html += f\"oauth2RedirectUrl: window.location.origin + {json.dumps(oauth2_redirect_url)},\"\n \n html += \"\"\"\n presets: [\n", "stderr": "", "exit_code": 0} |
| 32 | run_command | {
"command": "submit_patch"
} | {"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/basfull{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}} |
| 33 | submit_patch | {} | {"status": "ok", "patch_size": 1557, "files_changed": 2}full{"status": "ok", "patch_size": 1557, "files_changed": 2} |
diff --git a/fastapi/applications.py b/fastapi/applications.py
index ed05a1ff..82dbfb69 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -1105,11 +1105,6 @@ class FastAPI(Starlette):
server_urls = {url for url in urls if url}
async def openapi(req: Request) -> JSONResponse:
- root_path = req.scope.get("root_path", "").rstrip("/")
- if root_path not in server_urls:
- if root_path and self.root_path_in_servers:
- self.servers.insert(0, {"url": root_path})
- server_urls.add(root_path)
return JSONResponse(self.openapi())
self.add_route(self.openapi_url, openapi, include_in_schema=False)
diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py
index b845f87c..92acff08 100644
--- a/fastapi/openapi/docs.py
+++ b/fastapi/openapi/docs.py
@@ -151,14 +151,14 @@ def get_swagger_ui_html(
<!-- `SwaggerUIBundle` is now available on the page -->
<script>
const ui = SwaggerUIBundle({{
- url: '{openapi_url}',
+ url: {json.dumps(openapi_url)},
"""
for key, value in current_swagger_ui_parameters.items():
html += f"{json.dumps(key)}: {json.dumps(jsonable_encoder(value))},\n"
if oauth2_redirect_url:
- html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
+ html += f"oauth2RedirectUrl: window.location.origin + {json.dumps(oauth2_redirect_url)},"
html += """
presets: [
F
=================================== FAILURES ===================================
_______________ test_root_path_does_not_persist_across_requests ________________
def test_root_path_does_not_persist_across_requests():
app = FastAPI()
@app.get("/")
def read_root(): # pragma: no cover
return {"ok": True}
# Attacker request with a spoofed root_path
attacker_client = TestClient(app, root_path="/evil-api")
response1 = attacker_client.get("/openapi.json")
data1 = response1.json()
> assert any(s.get("url") == "/evil-api" for s in data1.get("servers", []))
E assert False
E + where False = any(<generator object test_root_path_does_not_persist_across_requests.<locals>.<genexpr> at 0x105a99560>)
tests/test_openapi_cache_root_path.py:16: AssertionError
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-py3-none-any/starlette/testclient.py:45
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.52.1-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
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 1 warning in 0.43s