Fix open redirect vulnerability in LoginPage
Validate the ?next= query parameter to prevent open redirects to external URLs. The parameter is validated to ensure it is a relative path (starts with / but not //) before using it for navigation. Invalid paths fall back to '/'. This prevents attackers from crafting login links like /login?next=https://evil.com that would transparently redirect authenticated users to malicious sites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,12 @@ This document catalogues architecture violations, code smells, and structural is
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Security Fixes
|
||||||
|
|
||||||
|
- Fixed open redirect vulnerability in `frontend/src/pages/LoginPage.tsx` by validating the `?next=` parameter to ensure it is a relative path (starts with `/` but not `//`). The validation regex `/^\/(?!\/)/.test(next)` prevents protocol-relative URLs and external redirects. Invalid paths fall back to `"/"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Completed Refactors
|
## Completed Refactors
|
||||||
|
|
||||||
- Moved `Fail2BanConnectionError` and `Fail2BanProtocolError` from `backend/app/utils/fail2ban_client.py` into `backend/app/exceptions.py`. Updated all router, service, and test call sites to import these domain exceptions from `app.exceptions` and retained backward compatibility through re-exporting in `app.utils.fail2ban_client`.
|
- Moved `Fail2BanConnectionError` and `Fail2BanProtocolError` from `backend/app/utils/fail2ban_client.py` into `backend/app/exceptions.py`. Updated all router, service, and test call sites to import these domain exceptions from `app.exceptions` and retained backward compatibility through re-exporting in `app.utils.fail2ban_client`.
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ export function LoginPage(): React.JSX.Element {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
const nextPath = searchParams.get("next") ?? "/";
|
const next = searchParams.get("next") ?? "";
|
||||||
|
const safePath = /^\/(?!\/)/.test(next) ? next : "/";
|
||||||
|
|
||||||
function handlePasswordChange(ev: ChangeEvent<HTMLInputElement>): void {
|
function handlePasswordChange(ev: ChangeEvent<HTMLInputElement>): void {
|
||||||
setPassword(ev.target.value);
|
setPassword(ev.target.value);
|
||||||
@@ -100,7 +101,7 @@ export function LoginPage(): React.JSX.Element {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await login(password);
|
await login(password);
|
||||||
navigate(nextPath, { replace: true });
|
navigate(safePath, { replace: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError && err.status === 401) {
|
if (err instanceof ApiError && err.status === 401) {
|
||||||
setError("Incorrect password. Please try again.");
|
setError("Incorrect password. Please try again.");
|
||||||
|
|||||||
Reference in New Issue
Block a user