Skip to content

Frontend#5

Open
siwei-li wants to merge 7 commits into
masterfrom
dev
Open

Frontend#5
siwei-li wants to merge 7 commits into
masterfrom
dev

Conversation

@siwei-li
Copy link
Copy Markdown
Owner

No description provided.

Comment thread app/linguist/campaign_routes.py Fixed
result = await add_recipients_to_campaign(campaign_id, phones)

return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?success=added_{result['recipients_added']}",

Check warning

Code scanning / CodeQL

URL redirection from remote source Medium

Untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI 8 months ago

To fix the vulnerability, we should ensure that campaign_id is always strictly an integer and completely trusted before embedding it into any redirect URLs. Since FastAPI normally enforces the parameter type via the function signature (campaign_id: int), this should suffice in most cases. However, to further harden the code and handle edge cases robustly (should unexpected types slip through or should this route ever be refactored), we can explicitly validate that campaign_id is an integer before constructing the redirect URL.

The fix can be implemented by:

  • Explicitly checking and casting campaign_id to int at the top of the handler function.
  • If conversion fails, reject the request with a 400 error.
  • Only use campaign_id in the redirect URL if we are sure it's an integer.

Alternatively, and as additional hardening, consider using type checks directly in the redirect URL construction to ensure nothing untrusted is used; or, for even more safety, use string formatting that only allows digits (e.g., {int(campaign_id)}).

No additional imports are needed; the standard exception (ValueError) can be used, and FastAPI's HTTPException is already imported.

Only lines relating to redirecting using the untrusted campaign_id need changing—in this case, within the add_campaign_recipients function, lines 468–497.


Suggested changeset 1
app/linguist/campaign_routes.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/linguist/campaign_routes.py b/app/linguist/campaign_routes.py
--- a/app/linguist/campaign_routes.py
+++ b/app/linguist/campaign_routes.py
@@ -470,6 +470,11 @@
     campaign_id: int,
     phone_numbers: str = Form(...)  # Comma or newline separated
 ):
+    # Enforce campaign_id is an integer to prevent any injection
+    try:
+        safe_campaign_id = int(campaign_id)
+    except (ValueError, TypeError):
+        raise HTTPException(status_code=400, detail="Invalid campaign ID")
     """Add phone numbers to a campaign"""
     user = await auth.get_current_user(request)
     if not user:
@@ -486,13 +491,13 @@
         result = await add_recipients_to_campaign(campaign_id, phones)
 
         return RedirectResponse(
-            url=f"/linguist/campaigns/{campaign_id}/recipients?success=added_{result['recipients_added']}",
+            url=f"/linguist/campaigns/{safe_campaign_id}/recipients?success=added_{result['recipients_added']}",
             status_code=303
         )
 
     except Exception as e:
         return RedirectResponse(
-            url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",
+            url=f"/linguist/campaigns/{safe_campaign_id}/recipients?error={str(e)}",
             status_code=303
         )
 
EOF
@@ -470,6 +470,11 @@
campaign_id: int,
phone_numbers: str = Form(...) # Comma or newline separated
):
# Enforce campaign_id is an integer to prevent any injection
try:
safe_campaign_id = int(campaign_id)
except (ValueError, TypeError):
raise HTTPException(status_code=400, detail="Invalid campaign ID")
"""Add phone numbers to a campaign"""
user = await auth.get_current_user(request)
if not user:
@@ -486,13 +491,13 @@
result = await add_recipients_to_campaign(campaign_id, phones)

return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?success=added_{result['recipients_added']}",
url=f"/linguist/campaigns/{safe_campaign_id}/recipients?success=added_{result['recipients_added']}",
status_code=303
)

except Exception as e:
return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",
url=f"/linguist/campaigns/{safe_campaign_id}/recipients?error={str(e)}",
status_code=303
)

Copilot is powered by AI and may make mistakes. Always verify output.

except Exception as e:
return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",

Check warning

Code scanning / CodeQL

URL redirection from remote source Medium

Untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI 8 months ago

To properly address the vulnerability, we should ensure that campaign_id is safe before interpolating it into the redirect path. FastAPI parses campaign_id as int, but defense in depth says we should strictly check that it is, in fact, a positive integer.
We must also ensure that the redirect URL cannot be manipulated to perform remote redirects, including via encoding tricks.

General fix:

  • Validate campaign_id is of type int and within the expected range (e.g., positive IDs).
  • Alternatively, construct routes by using only validated values.
  • Additionally, ensure that no user-supplied data in query parameters (such as the error message) can inadvertently create a security risk.

Detailed fix:

  • Check that campaign_id is a positive integer (greater than zero).
  • In the exception handler, if the campaign id is not valid, redirect to a safe fallback (e.g., campaign listing page).
  • Escape or limit the error query parameter message to avoid path/query confusion (e.g., limit its length, remove special characters, or avoid reflecting details).
  • Only perform redirects to paths beginning with /linguist/campaigns/<integer>/recipients.

Required changes:

  • In the function at line 468 (add_campaign_recipients), validate campaign_id explicitly before constructing the redirect URL.
  • Sanitize error message before embedding in URL.

No new imports required for basic Python validation.

Suggested changeset 1
app/linguist/campaign_routes.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/linguist/campaign_routes.py b/app/linguist/campaign_routes.py
--- a/app/linguist/campaign_routes.py
+++ b/app/linguist/campaign_routes.py
@@ -491,8 +491,15 @@
         )
 
     except Exception as e:
+        # Validate campaign_id is a positive integer
+        safe_campaign_id = campaign_id if isinstance(campaign_id, int) and campaign_id > 0 else ""
+        # Limit error length and remove unsafe characters
+        safe_error = str(e)
+        safe_error = safe_error[:100].replace("&", "").replace("?", "")  # remove query separators
+        if not safe_campaign_id:
+            return RedirectResponse(url="/linguist/campaigns?error=invalid_campaign", status_code=303)
         return RedirectResponse(
-            url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",
+            url=f"/linguist/campaigns/{safe_campaign_id}/recipients?error={safe_error}",
             status_code=303
         )
 
EOF
@@ -491,8 +491,15 @@
)

except Exception as e:
# Validate campaign_id is a positive integer
safe_campaign_id = campaign_id if isinstance(campaign_id, int) and campaign_id > 0 else ""
# Limit error length and remove unsafe characters
safe_error = str(e)
safe_error = safe_error[:100].replace("&", "").replace("?", "") # remove query separators
if not safe_campaign_id:
return RedirectResponse(url="/linguist/campaigns?error=invalid_campaign", status_code=303)
return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",
url=f"/linguist/campaigns/{safe_campaign_id}/recipients?error={safe_error}",
status_code=303
)

Copilot is powered by AI and may make mistakes. Always verify output.
await remove_recipient(campaign_id, phone_number)

return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?success=removed",

Check warning

Code scanning / CodeQL

URL redirection from remote source Medium

Untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI 8 months ago

To fix this issue, we need to ensure that user-provided values interpolated into URLs for redirects are either strictly validated or sanitized to avoid untrusted URL redirection. In this case, since campaign_id should always be an integer, we can explicitly cast it to int at the point of formatting, guaranteeing that only a valid integer value is used. This will prevent any possibility of path manipulation or other injection attacks, even if the function signature were changed later. This edit applies to the redirect URLs constructed within the remove_campaign_recipient route (lines 515, 521).

No imports or new methods are necessary—just cast campaign_id to an integer in the string formatting for the redirect URLs.


Suggested changeset 1
app/linguist/campaign_routes.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/linguist/campaign_routes.py b/app/linguist/campaign_routes.py
--- a/app/linguist/campaign_routes.py
+++ b/app/linguist/campaign_routes.py
@@ -512,13 +512,13 @@
         await remove_recipient(campaign_id, phone_number)
 
         return RedirectResponse(
-            url=f"/linguist/campaigns/{campaign_id}/recipients?success=removed",
+            url=f"/linguist/campaigns/{int(campaign_id)}/recipients?success=removed",
             status_code=303
         )
 
     except Exception as e:
         return RedirectResponse(
-            url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",
+            url=f"/linguist/campaigns/{int(campaign_id)}/recipients?error={str(e)}",
             status_code=303
         )
 
EOF
@@ -512,13 +512,13 @@
await remove_recipient(campaign_id, phone_number)

return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?success=removed",
url=f"/linguist/campaigns/{int(campaign_id)}/recipients?success=removed",
status_code=303
)

except Exception as e:
return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",
url=f"/linguist/campaigns/{int(campaign_id)}/recipients?error={str(e)}",
status_code=303
)

Copilot is powered by AI and may make mistakes. Always verify output.

except Exception as e:
return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",

Check warning

Code scanning / CodeQL

URL redirection from remote source Medium

Untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI 8 months ago

To mitigate open redirect risk, we must ensure that the campaign_id path parameter cannot be used by a malicious user to craft a redirect URL that exits the application's domain or route space. Since this parameter is typed as int, we should enforce strict integer validation (which FastAPI normally does), and also ensure that the redirect URL being constructed is a relative path, not an absolute URL or protocol-relative (e.g., does not start with http://, //, or includes malicious characters such as backslash).

Implementation plan:

  • Explicitly validate that the campaign_id is an integer (should be handled by FastAPI, but validate defensively).
  • Before performing the redirect, construct the target URL using only sanitized integers.
  • Optionally, use URL-building utilities or escape/encode any variable used in the URL (though campaign_id as int does not need encoding).
  • As an additional safety step, check that the redirect URL is not suspicious (does not start with //, does not contain backslashes, etc.), though again the risk is low with int.

Given the risk is primarily with campaign_id, make sure it's int, and never allow any malformed values through. If API usage ever changes campaign_id to be a string or expands supported data types, additional validation will be required.

Specific changes:

  • Add type check: If campaign_id is not int, return an error or redirect to safe location.
  • Build redirect URL in a way that only accepts proper campaign_id.
  • Consider adding defensive checks, e.g.: if not isinstance(campaign_id, int): ...
  • No third-party libraries required; use builtin type checking.
Suggested changeset 1
app/linguist/campaign_routes.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/linguist/campaign_routes.py b/app/linguist/campaign_routes.py
--- a/app/linguist/campaign_routes.py
+++ b/app/linguist/campaign_routes.py
@@ -508,6 +508,11 @@
     if not user:
         return RedirectResponse(url="/linguist/login", status_code=303)
 
+    # Defensive: ensure campaign_id is strictly integer (FastAPI should enforce, but double check)
+    if not isinstance(campaign_id, int) or campaign_id < 0:
+        # Redirect to safe location if campaign_id is invalid
+        return RedirectResponse(url="/linguist/campaigns/?error=invalid_campaign_id", status_code=303)
+
     try:
         await remove_recipient(campaign_id, phone_number)
 
@@ -518,7 +523,7 @@
 
     except Exception as e:
         return RedirectResponse(
-            url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",
+            url=f"/linguist/campaigns/{campaign_id}/recipients?error=removed_failed",
             status_code=303
         )
 
EOF
@@ -508,6 +508,11 @@
if not user:
return RedirectResponse(url="/linguist/login", status_code=303)

# Defensive: ensure campaign_id is strictly integer (FastAPI should enforce, but double check)
if not isinstance(campaign_id, int) or campaign_id < 0:
# Redirect to safe location if campaign_id is invalid
return RedirectResponse(url="/linguist/campaigns/?error=invalid_campaign_id", status_code=303)

try:
await remove_recipient(campaign_id, phone_number)

@@ -518,7 +523,7 @@

except Exception as e:
return RedirectResponse(
url=f"/linguist/campaigns/{campaign_id}/recipients?error={str(e)}",
url=f"/linguist/campaigns/{campaign_id}/recipients?error=removed_failed",
status_code=303
)

Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +539 to +543
return JSONResponse({
"success": True,
"message": f"Campaign started! Sent to {result['sent']} recipients.",
"data": result
})

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 8 months ago

To fix the problem, we should ensure that internal exception details are not exposed to API clients. The best way is to mask or generalize the error information sent to the user. Specifically:

  • In start_campaign_flow (in app/services/campaign_starter.py), do not return the raw exception message in the results structure. Instead, use a generic error string such as "An internal error occurred" or "Sending failed".
  • Optionally, log the detailed exception message on the server for diagnostics.
  • Only generic, non-sensitive information should be included in API responses.

To implement this:

  • Change line(s) 151–154 in start_campaign_flow so that the 'error' field does not include str(e) but a generic message instead.
  • (Optional, if you want logging) Import the standard logging module and log the exception message and stack trace.
  • No changes are needed in app/linguist/campaign_routes.py, because after the above fix, the response will not include sensitive exception details anyway.
Suggested changeset 1
app/services/campaign_starter.py
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/services/campaign_starter.py b/app/services/campaign_starter.py
--- a/app/services/campaign_starter.py
+++ b/app/services/campaign_starter.py
@@ -148,10 +148,13 @@
 
         except Exception as e:
             failed_count += 1
+            # Log the actual error for server-side diagnostics
+            import logging
+            logging.exception(f"Error sending message to {phone}: {e}")
             results.append({
                 'phone': phone,
                 'status': 'failed',
-                'error': str(e)
+                'error': "Failed to send message due to an internal error."
             })
 
     # Update campaign status
EOF
@@ -148,10 +148,13 @@

except Exception as e:
failed_count += 1
# Log the actual error for server-side diagnostics
import logging
logging.exception(f"Error sending message to {phone}: {e}")
results.append({
'phone': phone,
'status': 'failed',
'error': str(e)
'error': "Failed to send message due to an internal error."
})

# Update campaign status
Copilot is powered by AI and may make mistakes. Always verify output.
Comment thread app/linguist/campaign_routes.py Fixed
Comment thread app/services/campaign_starter.py Fixed
siwei-li and others added 4 commits October 9, 2025 19:36
…nsitive information

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…rough an exception

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…rough an exception

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants