From b04cd7414eef18bbf6f8e83248d4311ebb1562a2 Mon Sep 17 00:00:00 2001 From: Ritikyadav2004 Date: Fri, 31 Jul 2026 00:22:35 +0530 Subject: [PATCH 1/6] test(frontend): validate SignIn return-route handling #2178 --- frontend/src/pages/SignIn.tsx | 23 +++++++++++++- frontend/testing/unit/pages/SignIn.test.tsx | 34 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/SignIn.tsx b/frontend/src/pages/SignIn.tsx index 8e1957b20..9705fdfc3 100644 --- a/frontend/src/pages/SignIn.tsx +++ b/frontend/src/pages/SignIn.tsx @@ -13,11 +13,32 @@ import { routes } from '../routes' * POST /api/v1/auth/session). On success we mark the session authenticated and * return the operator to the route they were sent here from (or the dashboard). */ +function isSafeInternalPath(path: string): boolean { + if (!path.startsWith('/')) { + return false + } + // Prevent protocol-relative URLs like //evil.com + if (path.startsWith('//')) { + return false + } + // Prevent backslash protocol-relative bypasses or Windows share paths like /\evil.com + if (path.startsWith('/\\')) { + return false + } + // Ensure no protocols/schemes (e.g. http://, https://, javascript:) are in the pathname + if (/^[a-z0-9+.-]+:/i.test(path.trim())) { + return false + } + return true +} + export default function SignIn() { const { isAuthenticated, loading, markAuthenticated } = useAuth() const navigate = useNavigate() const location = useLocation() as { state?: { from?: { pathname?: string } } } - const from = location.state?.from?.pathname || routes.dashboard + + const rawFrom = location.state?.from?.pathname + const from = rawFrom && isSafeInternalPath(rawFrom) ? rawFrom : routes.dashboard // If a valid session already exists, don't show the key prompt. useEffect(() => { diff --git a/frontend/testing/unit/pages/SignIn.test.tsx b/frontend/testing/unit/pages/SignIn.test.tsx index 475344d28..7fa138d3f 100644 --- a/frontend/testing/unit/pages/SignIn.test.tsx +++ b/frontend/testing/unit/pages/SignIn.test.tsx @@ -64,6 +64,40 @@ describe('SignIn page (issue #795)', () => { await waitFor(() => expect(screen.getByText('FINDINGS PAGE')).toBeInTheDocument()) }) + it('rejects external HTTP/HTTPS URLs and redirects to the dashboard instead', async () => { + vi.mocked(authenticateWithApiKey).mockResolvedValue(undefined) + renderSignIn({ pathname: '/signin', state: { from: { pathname: 'https://evil.com/findings' } } }) + await screen.findByLabelText(/Backend API Key/i) + + await userEvent.type(screen.getByLabelText(/Backend API Key/i), 'operator-key-123') + await userEvent.click(screen.getByText(/Save and connect/i)) + + await waitFor(() => expect(screen.getByText('DASHBOARD HOME')).toBeInTheDocument()) + expect(screen.queryByText('FINDINGS PAGE')).not.toBeInTheDocument() + }) + + it('rejects protocol-relative URLs (starts with //) and redirects to the dashboard', async () => { + vi.mocked(authenticateWithApiKey).mockResolvedValue(undefined) + renderSignIn({ pathname: '/signin', state: { from: { pathname: '//evil.com/findings' } } }) + await screen.findByLabelText(/Backend API Key/i) + + await userEvent.type(screen.getByLabelText(/Backend API Key/i), 'operator-key-123') + await userEvent.click(screen.getByText(/Save and connect/i)) + + await waitFor(() => expect(screen.getByText('DASHBOARD HOME')).toBeInTheDocument()) + }) + + it('rejects javascript: schemes and redirects to the dashboard', async () => { + vi.mocked(authenticateWithApiKey).mockResolvedValue(undefined) + renderSignIn({ pathname: '/signin', state: { from: { pathname: 'javascript:alert(1)' } } }) + await screen.findByLabelText(/Backend API Key/i) + + await userEvent.type(screen.getByLabelText(/Backend API Key/i), 'operator-key-123') + await userEvent.click(screen.getByText(/Save and connect/i)) + + await waitFor(() => expect(screen.getByText('DASHBOARD HOME')).toBeInTheDocument()) + }) + it('shows the backend error and does not redirect when the key is rejected', async () => { vi.mocked(authenticateWithApiKey).mockRejectedValue(new Error('Invalid API key')) renderSignIn() From 1eec56689b5446f65556e98b4b8be1b1ffffba44 Mon Sep 17 00:00:00 2001 From: Ritikyadav2004 Date: Fri, 31 Jul 2026 00:42:20 +0530 Subject: [PATCH 2/6] style: remove trailing whitespace in SignIn.tsx --- frontend/src/pages/SignIn.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/SignIn.tsx b/frontend/src/pages/SignIn.tsx index 9705fdfc3..5900ce68f 100644 --- a/frontend/src/pages/SignIn.tsx +++ b/frontend/src/pages/SignIn.tsx @@ -36,7 +36,7 @@ export default function SignIn() { const { isAuthenticated, loading, markAuthenticated } = useAuth() const navigate = useNavigate() const location = useLocation() as { state?: { from?: { pathname?: string } } } - + const rawFrom = location.state?.from?.pathname const from = rawFrom && isSafeInternalPath(rawFrom) ? rawFrom : routes.dashboard From b8d4326ee571a09e183a108c3c150d9052f41ead Mon Sep 17 00:00:00 2001 From: Ritikyadav2004 Date: Sun, 16 Aug 2026 16:55:01 +0530 Subject: [PATCH 3/6] fix(theme): prevent invalid rgb wrapping of CSS variables in tailwind config --- frontend/tailwind.config.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 7b5a82afb..bd218ac6d 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -8,25 +8,25 @@ export default { theme: { extend: { colors: { - 'primary': 'var(--bg-primary)', - 'secondary': 'var(--bg-secondary)', - 'bg-primary': 'var(--bg-primary)', - 'bg-secondary': 'var(--bg-secondary)', - 'bg-tertiary': 'var(--bg-tertiary)', - 'bg-elevated': 'var(--bg-elevated)', - 'primary-text': 'var(--text-primary)', - 'secondary-text': 'var(--text-secondary)', - 'muted': 'var(--text-muted)', - 'charcoal-dark': 'var(--bg-primary)', + 'primary': 'color-mix(in srgb, var(--bg-primary) 100%, transparent)', + 'secondary': 'color-mix(in srgb, var(--bg-secondary) 100%, transparent)', + 'bg-primary': 'color-mix(in srgb, var(--bg-primary) 100%, transparent)', + 'bg-secondary': 'color-mix(in srgb, var(--bg-secondary) 100%, transparent)', + 'bg-tertiary': 'color-mix(in srgb, var(--bg-tertiary) 100%, transparent)', + 'bg-elevated': 'color-mix(in srgb, var(--bg-elevated) 100%, transparent)', + 'primary-text': 'color-mix(in srgb, var(--text-primary) 100%, transparent)', + 'secondary-text': 'color-mix(in srgb, var(--text-secondary) 100%, transparent)', + 'muted': 'color-mix(in srgb, var(--text-muted) 100%, transparent)', + 'charcoal-dark': 'color-mix(in srgb, var(--bg-primary) 100%, transparent)', charcoal: { - light: 'var(--bg-tertiary)', - DEFAULT: 'var(--bg-secondary)', - dark: 'var(--bg-primary)', /* mapped for backward compatibility */ + light: 'color-mix(in srgb, var(--bg-tertiary) 100%, transparent)', + DEFAULT: 'color-mix(in srgb, var(--bg-secondary) 100%, transparent)', + dark: 'color-mix(in srgb, var(--bg-primary) 100%, transparent)', /* mapped for backward compatibility */ }, silver: { - bright: 'var(--text-primary)', - DEFAULT: 'var(--text-secondary)', - dark: 'var(--text-muted)', + bright: 'color-mix(in srgb, var(--text-primary) 100%, transparent)', + DEFAULT: 'color-mix(in srgb, var(--text-secondary) 100%, transparent)', + dark: 'color-mix(in srgb, var(--text-muted) 100%, transparent)', }, rag: { red: '#ef4444', @@ -37,7 +37,7 @@ export default { 'blue-bright': '#3b82f6', }, accent: { - silver: 'var(--accent-silver)' + silver: 'color-mix(in srgb, var(--accent-silver) 100%, transparent)' } }, fontFamily: { From ca567bfe3db9d59f760062c29eaebbf2a406d91c Mon Sep 17 00:00:00 2001 From: Ritikyadav2004 Date: Sun, 16 Aug 2026 17:19:54 +0530 Subject: [PATCH 4/6] fix(deps): override nanoid to fix vulnerability and update tailwind color-mix configuration --- frontend/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 8d0870432..b7b50c54b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -54,6 +54,7 @@ "esbuild": "^0.28.1", "react-router": "^6.30.4", "dompurify": "^3.4.10", - "@babel/core": "^7.29.7" + "@babel/core": "^7.29.7", + "nanoid": "^3.3.8" } } From 8624843b491a0c7028319910eca03e8d362cad5a Mon Sep 17 00:00:00 2001 From: Ritikyadav2004 Date: Sun, 16 Aug 2026 17:26:56 +0530 Subject: [PATCH 5/6] fix(deps): merge undici override to resolve conflict with main --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3e237fb2e..e57d7c05a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4536,13 +4536,13 @@ } }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", + "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=22.19.0" } }, "node_modules/undici-types": { diff --git a/frontend/package.json b/frontend/package.json index b7b50c54b..db0b6cf49 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,6 +55,7 @@ "react-router": "^6.30.4", "dompurify": "^3.4.10", "@babel/core": "^7.29.7", - "nanoid": "^3.3.8" + "nanoid": "^3.3.8", + "undici": "^8.10.0" } } From 6b0fbae47db302f1bc1f98baa8d1f16a8d8a29c6 Mon Sep 17 00:00:00 2001 From: Ritikyadav2004 Date: Sun, 16 Aug 2026 17:43:24 +0530 Subject: [PATCH 6/6] fix(deps): bump nanoid override to ^3.3.9 to resolve security vulnerability --- frontend/package-lock.json | 31 +------------------------------ frontend/package.json | 2 +- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f6976bd86..f584f895a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -115,7 +115,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -464,7 +463,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -488,7 +486,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -498,7 +495,6 @@ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -1524,7 +1520,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -1752,7 +1747,6 @@ "integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.21.0" } @@ -1783,7 +1777,6 @@ "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1795,7 +1788,6 @@ "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/react": "*" } @@ -2132,7 +2124,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -3272,7 +3263,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -3289,7 +3279,6 @@ "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.1.0", "data-urls": "^5.0.0", @@ -3763,7 +3752,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", @@ -3973,7 +3961,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3986,7 +3973,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4016,7 +4002,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -4147,8 +4132,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -4649,7 +4633,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4752,16 +4735,6 @@ "node": ">=14.17" } }, - "node_modules/undici": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", - "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22.19.0" - } - }, "node_modules/undici-types": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.21.0.tgz", @@ -4853,7 +4826,6 @@ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -4947,7 +4919,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/frontend/package.json b/frontend/package.json index 5c662a5a1..026ed35f8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,7 +55,7 @@ "react-router": "^6.30.4", "dompurify": "^3.4.10", "@babel/core": "^7.29.7", - "nanoid": "^3.3.8", + "nanoid": "^3.3.9", "undici": "^8.10.0" } }