-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Web UI #634
base: main
Are you sure you want to change the base?
Conversation
Web UI fetch yaml
# Conflicts: # cli/medperf/web_ui/yaml_fetch/routes.py
MLCommons CLA bot: |
full_path = os.path.join(BASE_DIR, path) | ||
os.path.join(BASE_DIR, path) | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI 22 days ago
To fix the problem, we need to ensure that the full_path
is normalized before performing any checks. This will remove any ".." segments and ensure that the path is correctly contained within the BASE_DIR
. We will use os.path.normpath
to normalize the path and then check if the normalized path starts with the BASE_DIR
.
-
Copy modified lines R19-R26
@@ -18,11 +18,10 @@ | ||
): | ||
full_path = os.path.join(BASE_DIR, path) | ||
os.path.join(BASE_DIR, path) | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): | ||
raise HTTPException(status_code=404, detail="Directory not found") | ||
|
||
# Ensure path is within the base directory | ||
if not os.path.commonpath([BASE_DIR, full_path]).startswith(BASE_DIR): | ||
raise HTTPException(status_code=403, detail="Access denied") | ||
full_path = os.path.normpath(os.path.join(BASE_DIR, path)) | ||
|
||
# Ensure path is within the base directory | ||
if not full_path.startswith(BASE_DIR): | ||
raise HTTPException(status_code=403, detail="Access denied") | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): | ||
raise HTTPException(status_code=404, detail="Directory not found") | ||
|
full_path = os.path.join(BASE_DIR, path) | ||
os.path.join(BASE_DIR, path) | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI 22 days ago
To fix the problem, we need to ensure that the full_path
is normalized before checking if it is within the BASE_DIR
. This can be done using os.path.normpath
to remove any ".." segments that could allow directory traversal. After normalizing the path, we should check if it starts with the BASE_DIR
.
- Normalize the
full_path
usingos.path.normpath
. - Check if the normalized
full_path
starts withBASE_DIR
to ensure it is within the base directory. - Update the existing checks to use the normalized path.
-
Copy modified lines R19-R25
@@ -18,11 +18,9 @@ | ||
): | ||
full_path = os.path.join(BASE_DIR, path) | ||
os.path.join(BASE_DIR, path) | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): | ||
raise HTTPException(status_code=404, detail="Directory not found") | ||
|
||
# Ensure path is within the base directory | ||
if not os.path.commonpath([BASE_DIR, full_path]).startswith(BASE_DIR): | ||
raise HTTPException(status_code=403, detail="Access denied") | ||
full_path = os.path.normpath(os.path.join(BASE_DIR, path)) | ||
|
||
if not full_path.startswith(BASE_DIR): | ||
raise HTTPException(status_code=403, detail="Access denied") | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): | ||
raise HTTPException(status_code=404, detail="Directory not found") | ||
|
|
||
# List directories inside the path | ||
folders = [] | ||
for item in os.listdir(full_path): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI 22 days ago
To fix the problem, we need to ensure that the full_path
is properly validated and sanitized before it is used in any file system operations. The best way to do this is to normalize the path using os.path.normpath
and then check that the normalized path starts with the BASE_DIR
. This will prevent any path traversal attacks and ensure that the path is within the intended directory.
-
Copy modified lines R19-R26
@@ -18,11 +18,10 @@ | ||
): | ||
full_path = os.path.join(BASE_DIR, path) | ||
os.path.join(BASE_DIR, path) | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): | ||
raise HTTPException(status_code=404, detail="Directory not found") | ||
|
||
# Ensure path is within the base directory | ||
if not os.path.commonpath([BASE_DIR, full_path]).startswith(BASE_DIR): | ||
raise HTTPException(status_code=403, detail="Access denied") | ||
full_path = os.path.normpath(os.path.join(BASE_DIR, path)) | ||
|
||
# Ensure path is within the base directory | ||
if not full_path.startswith(BASE_DIR): | ||
raise HTTPException(status_code=403, detail="Access denied") | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): | ||
raise HTTPException(status_code=404, detail="Directory not found") | ||
|
folders = [] | ||
for item in os.listdir(full_path): | ||
item_path = os.path.join(full_path, item) | ||
if os.path.isdir(item_path): |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI 22 days ago
To fix the problem, we need to ensure that the full_path
is normalized before checking if it is within the BASE_DIR
. This can be done using os.path.normpath
to remove any ".." segments and ensure the path is safe. Additionally, we should ensure that the item_path
is also validated properly.
- Normalize the
full_path
usingos.path.normpath
. - Check if the normalized
full_path
starts withBASE_DIR
. - Ensure that
item_path
is also validated properly.
-
Copy modified lines R19-R33
@@ -18,18 +18,17 @@ | ||
): | ||
full_path = os.path.join(BASE_DIR, path) | ||
os.path.join(BASE_DIR, path) | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): | ||
raise HTTPException(status_code=404, detail="Directory not found") | ||
|
||
# Ensure path is within the base directory | ||
if not os.path.commonpath([BASE_DIR, full_path]).startswith(BASE_DIR): | ||
raise HTTPException(status_code=403, detail="Access denied") | ||
|
||
# List directories inside the path | ||
folders = [] | ||
for item in os.listdir(full_path): | ||
item_path = os.path.join(full_path, item) | ||
if os.path.isdir(item_path): | ||
folders.append({"name": item, "path": os.path.relpath(item_path, BASE_DIR)}) | ||
full_path = os.path.normpath(os.path.join(BASE_DIR, path)) | ||
|
||
if not os.path.exists(full_path) or not os.path.isdir(full_path): | ||
raise HTTPException(status_code=404, detail="Directory not found") | ||
|
||
# Ensure path is within the base directory | ||
if not full_path.startswith(BASE_DIR): | ||
raise HTTPException(status_code=403, detail="Access denied") | ||
|
||
# List directories inside the path | ||
folders = [] | ||
for item in os.listdir(full_path): | ||
item_path = os.path.normpath(os.path.join(full_path, item)) | ||
if item_path.startswith(full_path) and os.path.isdir(item_path): | ||
folders.append({"name": item, "path": os.path.relpath(item_path, BASE_DIR)}) | ||
|
# Check if user is already authenticated | ||
if token == security_token: | ||
# User is already authenticated, redirect to original URL | ||
return RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI 22 days ago
To fix the problem, we need to validate the redirect_url
parameter to ensure it does not contain an explicit host name, which could lead to open redirect vulnerabilities. We can use the urlparse
function from the Python standard library to parse the URL and check that the netloc
attribute is empty. Additionally, we should replace backslashes with forward slashes to handle cases where browsers accept backslashes as equivalent to forward slashes.
We will make changes to the login_form
and login
functions to include this validation.
-
Copy modified lines R1-R3 -
Copy modified lines R15-R24 -
Copy modified lines R37-R45
@@ -1,3 +1,4 @@ | ||
from fastapi import Request, Form, APIRouter, status, Security | ||
from fastapi.responses import HTMLResponse, RedirectResponse | ||
from fastapi import Request, Form, APIRouter, status, Security | ||
from fastapi.responses import HTMLResponse, RedirectResponse | ||
from urllib.parse import urlparse | ||
|
||
@@ -13,9 +14,12 @@ | ||
request: Request, | ||
redirect_url: str = "/", | ||
token: str = Security(api_key_cookie) | ||
): | ||
# Check if user is already authenticated | ||
if token == security_token: | ||
# User is already authenticated, redirect to original URL | ||
return RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
redirect_url: str = "/", | ||
token: str = Security(api_key_cookie) | ||
): | ||
# Validate redirect_url | ||
redirect_url = redirect_url.replace('\\', '/') | ||
if not urlparse(redirect_url).netloc and not urlparse(redirect_url).scheme: | ||
# Check if user is already authenticated | ||
if token == security_token: | ||
# User is already authenticated, redirect to original URL | ||
return RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
else: | ||
@@ -32,8 +36,11 @@ | ||
token: str = Form(...), | ||
redirect_url: str = Form("/"), | ||
): | ||
if token == security_token: | ||
response = RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
response.set_cookie(key=AUTH_COOKIE_NAME, value=token) | ||
return response | ||
redirect_url: str = Form("/"), | ||
): | ||
# Validate redirect_url | ||
redirect_url = redirect_url.replace('\\', '/') | ||
if not urlparse(redirect_url).netloc and not urlparse(redirect_url).scheme: | ||
if token == security_token: | ||
response = RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
response.set_cookie(key=AUTH_COOKIE_NAME, value=token) | ||
return response | ||
else: |
redirect_url: str = Form("/"), | ||
): | ||
if token == security_token: | ||
response = RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) |
Check warning
Code scanning / CodeQL
URL redirection from remote source Medium
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI 22 days ago
To fix the problem, we need to validate the redirect_url
parameter to ensure it does not contain an explicit host name, which could lead to an open redirect vulnerability. We can use the urlparse
function from the Python standard library to parse the URL and check that the netloc
attribute is empty. Additionally, we should replace backslashes with forward slashes to handle browser quirks.
- Import the
urlparse
function from theurllib.parse
module. - Replace backslashes in the
redirect_url
with forward slashes. - Check that the
netloc
attribute of the parsed URL is empty. - If the
redirect_url
is not valid, redirect to a default safe URL (e.g., "/").
-
Copy modified lines R1-R51
@@ -1,49 +1,51 @@ | ||
from fastapi import Request, Form, APIRouter, status, Security | ||
from fastapi.responses import HTMLResponse, RedirectResponse | ||
|
||
from medperf.web_ui.auth import security_token, AUTH_COOKIE_NAME | ||
from medperf.web_ui.common import templates, api_key_cookie | ||
|
||
router = APIRouter() | ||
|
||
|
||
# Login page GET endpoint | ||
@router.get("/login", response_class=HTMLResponse) | ||
async def login_form( | ||
request: Request, | ||
redirect_url: str = "/", | ||
token: str = Security(api_key_cookie) | ||
): | ||
# Check if user is already authenticated | ||
if token == security_token: | ||
# User is already authenticated, redirect to original URL | ||
return RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
else: | ||
# User is not authenticated, show login form | ||
return templates.TemplateResponse( | ||
"login.html", {"request": request, "redirect_url": redirect_url} | ||
) | ||
|
||
|
||
# Login page POST endpoint | ||
@router.post("/login") | ||
async def login( | ||
request: Request, | ||
token: str = Form(...), | ||
redirect_url: str = Form("/"), | ||
): | ||
if token == security_token: | ||
response = RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
response.set_cookie(key=AUTH_COOKIE_NAME, value=token) | ||
return response | ||
else: | ||
return templates.TemplateResponse( | ||
"login.html", | ||
{ | ||
"request": request, | ||
"redirect_url": redirect_url, | ||
"error": "Invalid token", | ||
}, | ||
) | ||
|
||
|
||
from fastapi import Request, Form, APIRouter, status, Security | ||
from fastapi.responses import HTMLResponse, RedirectResponse | ||
from urllib.parse import urlparse | ||
from medperf.web_ui.auth import security_token, AUTH_COOKIE_NAME | ||
from medperf.web_ui.common import templates, api_key_cookie | ||
|
||
router = APIRouter() | ||
|
||
|
||
# Login page GET endpoint | ||
@router.get("/login", response_class=HTMLResponse) | ||
async def login_form( | ||
request: Request, | ||
redirect_url: str = "/", | ||
token: str = Security(api_key_cookie) | ||
): | ||
# Check if user is already authenticated | ||
if token == security_token: | ||
# User is already authenticated, redirect to original URL | ||
return RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
else: | ||
# User is not authenticated, show login form | ||
return templates.TemplateResponse( | ||
"login.html", {"request": request, "redirect_url": redirect_url} | ||
) | ||
|
||
|
||
# Login page POST endpoint | ||
@router.post("/login") | ||
async def login( | ||
request: Request, | ||
token: str = Form(...), | ||
redirect_url: str = Form("/"), | ||
): | ||
redirect_url = redirect_url.replace('\\', '/') | ||
if not urlparse(redirect_url).netloc: | ||
if token == security_token: | ||
response = RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
response.set_cookie(key=AUTH_COOKIE_NAME, value=token) | ||
return response | ||
else: | ||
return templates.TemplateResponse( | ||
"login.html", | ||
{ | ||
"request": request, | ||
"redirect_url": redirect_url, | ||
"error": "Invalid token", | ||
}, | ||
) | ||
else: | ||
return RedirectResponse(url="/", status_code=status.HTTP_302_FOUND) |
): | ||
if token == security_token: | ||
response = RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
response.set_cookie(key=AUTH_COOKIE_NAME, value=token) |
Check warning
Code scanning / CodeQL
Failure to use secure cookies Medium
): | ||
if token == security_token: | ||
response = RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND) | ||
response.set_cookie(key=AUTH_COOKIE_NAME, value=token) |
Check warning
Code scanning / CodeQL
Construction of a cookie using user-supplied input Medium
Comments/discussions to check:
To be merged before merging to main: #618, which depends on #615