Conversation
| function writeToPage(query: string) { | ||
| const params = new URLSearchParams(window.location.search); | ||
| const value = params.get(query); | ||
| document.write('<p>' + value + '</p>'); // VULNERABILITY: URL param written directly to DOM |
Check failure
Code scanning / CodeQL
Client-side cross-site scripting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, to fix client-side XSS from URL parameters, avoid writing untrusted data as HTML. Prefer setting textContent or innerText so the browser treats the data as plain text, or apply proper HTML encoding before insertion. When using document.write, the safest fix is usually to stop using it and instead create DOM nodes and set their text content.
For this specific code, the best fix without changing visible functionality is to: (1) stop using document.write, (2) create a <p> element, and (3) assign value to textContent so that any HTML markup is escaped. To retain behavior, append this <p> to document.body. This ensures that any user-controlled content from window.location.search is rendered as text rather than executable HTML. No external libraries or new imports are needed; we only adjust the implementation of writeToPage in security-test-samples.ts.
Specifically: in security-test-samples.ts, lines 16–20 for writeToPage, replace the document.write('<p>' + value + '</p>'); line with code that creates a p element, sets p.textContent = value ?? '', and appends it to document.body. This preserves the intended display of the parameter value inside a paragraph while eliminating the XSS risk.
| @@ -16,7 +16,9 @@ | ||
| function writeToPage(query: string) { | ||
| const params = new URLSearchParams(window.location.search); | ||
| const value = params.get(query); | ||
| document.write('<p>' + value + '</p>'); // VULNERABILITY: URL param written directly to DOM | ||
| const p = document.createElement('p'); | ||
| p.textContent = value ?? ''; | ||
| document.body.appendChild(p); | ||
| } | ||
|
|
||
| // 3. eval() with user-controlled input |
| function redirectTo(dest: string) { | ||
| const params = new URLSearchParams(window.location.search); | ||
| const target = params.get(dest); | ||
| window.location.href = target!; // VULNERABILITY: unvalidated redirect |
Check failure
Code scanning / CodeQL
Client-side cross-site scripting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
In general, to fix this kind of issue you must not directly assign user-controlled strings to navigation or HTML sinks. For redirects, a common pattern is to restrict destinations to same-origin paths or to a small whitelist of allowed relative URLs, and ignore or normalize everything else. That removes the ability for an attacker to force navigation to arbitrary domains and closes the injection path.
Here, the simplest, least invasive hardening is to make redirectTo accept only in-site relative paths. We can do this by: (1) reading the parameter as today; (2) ensuring it is non-null; (3) ensuring it starts with / and does not contain a scheme (: before any /) or protocol-relative prefix (//); and (4) assigning it only if it passes these checks. If it fails, we can either do nothing (stay on the same page) or navigate to a safe default path such as /. This preserves the function’s purpose (“redirect somewhere based on a parameter”) while preventing redirection to attacker-chosen external sites.
Concretely, in security-test-samples.ts at lines 31–35, we will:
- Introduce a small helper
isSafeRelativePath(target: string | null): target is stringinsideredirectTo(or inline logic) to validate the path. - Change
window.location.href = target!;to only execute whentargetpasses validation. - Optionally fall back to a safe default (e.g.,
/) if the parameter is missing or invalid.
No new imports are required; we use only basic string operations.
| @@ -31,7 +31,31 @@ | ||
| function redirectTo(dest: string) { | ||
| const params = new URLSearchParams(window.location.search); | ||
| const target = params.get(dest); | ||
| window.location.href = target!; // VULNERABILITY: unvalidated redirect | ||
|
|
||
| // Only allow same-origin relative paths to avoid open redirect / XSS | ||
| const isSafeRelativePath = (value: string | null): value is string => { | ||
| if (!value) { | ||
| return false; | ||
| } | ||
| // Disallow protocol-relative URLs and absolute URLs with a scheme. | ||
| if (value.startsWith('//')) { | ||
| return false; | ||
| } | ||
| const firstSlash = value.indexOf('/'); | ||
| const firstColon = value.indexOf(':'); | ||
| if (firstColon !== -1 && (firstSlash === -1 || firstColon < firstSlash)) { | ||
| return false; | ||
| } | ||
| // Require a leading slash so we only navigate within this origin. | ||
| return value.startsWith('/'); | ||
| }; | ||
|
|
||
| if (isSafeRelativePath(target)) { | ||
| window.location.href = target; | ||
| } else { | ||
| // Fallback to a safe default (stay on page or navigate to home). | ||
| // window.location.href = '/'; | ||
| } | ||
| } | ||
|
|
||
| // 5. postMessage without origin check |
| window.addEventListener('message', (event) => { | ||
| // VULNERABILITY: no check on event.origin | ||
| const payload = JSON.parse(event.data); | ||
| document.getElementById('output')!.innerHTML = payload.html; |
Check failure
Code scanning / CodeQL
Client-side cross-site scripting High
| function redirectTo(dest: string) { | ||
| const params = new URLSearchParams(window.location.search); | ||
| const target = params.get(dest); | ||
| window.location.href = target!; // VULNERABILITY: unvalidated redirect |
Check warning
Code scanning / CodeQL
Client-side URL redirect Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 3 days ago
To fix client-side open redirects, user-controlled values must not be used directly as redirect URLs. Instead, the code should map user input to a restricted set of safe destinations (for example, specific paths within the same origin), or at minimum enforce same-origin and basic URL validation before redirecting.
In this snippet, the simplest safe change that preserves functionality is:
- Interpret the query parameter as an identifier (a key), not a full URL.
- Maintain a small client-side whitelist mapping from allowed keys to safe, same-origin paths.
- Look up the key from
URLSearchParamsin this map; if it is not present, either do nothing or redirect to a neutral default (such as'/'). - Never redirect to arbitrary absolute URLs or to user-provided strings that are not in the map.
Concretely, in security-test-samples.ts, lines 31–35 define redirectTo. We will:
- Add a constant
ALLOWED_REDIRECTSmap just aboveredirectTo. - Change
redirectToso it:- Reads the parameter value as a key (
key). - Finds the mapped path in
ALLOWED_REDIRECTS. - If found, assigns that path to
window.location.href. - If not found or missing, optionally redirects to
'/'or returns without redirect; to avoid surprising behavior and still “redirect somewhere,” we’ll use'/'as a safe default.
- Reads the parameter value as a key (
No new imports or external libraries are needed; the change is purely within this file and function.
| @@ -28,10 +28,17 @@ | ||
|
|
||
| // 4. Open redirect via user-controlled URL | ||
| // CodeQL rule: js/open-redirect | ||
| const ALLOWED_REDIRECTS: Record<string, string> = { | ||
| home: '/', | ||
| dashboard: '/dashboard', | ||
| settings: '/settings' | ||
| }; | ||
|
|
||
| function redirectTo(dest: string) { | ||
| const params = new URLSearchParams(window.location.search); | ||
| const target = params.get(dest); | ||
| window.location.href = target!; // VULNERABILITY: unvalidated redirect | ||
| const key = params.get(dest); | ||
| const safeTarget = (key && ALLOWED_REDIRECTS[key]) ? ALLOWED_REDIRECTS[key] : '/'; | ||
| window.location.href = safeTarget; | ||
| } | ||
|
|
||
| // 5. postMessage without origin check |
| // 5. postMessage without origin check | ||
| // CodeQL rule: js/postmessage-star-origin (or missing origin validation) | ||
| function listenForMessages() { | ||
| window.addEventListener('message', (event) => { |
Check warning
Code scanning / CodeQL
Missing origin verification in `postMessage` handler Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, the fix is to validate event.origin (and optionally event.source) in the message handler against an allowlist of trusted origins before processing event.data. If the origin is not trusted, the handler should ignore the message (or log it) and never parse or use its payload.
For this specific code, the minimal, non‑breaking change is to introduce a list of allowed origins inside listenForMessages and wrap the existing logic (JSON parsing and DOM update) in a conditional that checks event.origin against that list. We do not change what happens for trusted messages; we only prevent untrusted origins from reaching the vulnerable DOM update. Because we must not assume external configuration, we can define a simple inline array of trusted origins (e.g., ['https://www.example.com']) as a placeholder; in a real app this would be environment/config driven. The change is confined to the listenForMessages function in security-test-samples.ts; no new imports or external libraries are needed.
Concretely:
- Edit the arrow function passed to
window.addEventListener('message', ...)(lines 40–44). - Inside the listener, add a
const trustedOrigins = [...]and anif (!trustedOrigins.includes(event.origin)) { return; }. - Optionally add a
try/catcharoundJSON.parseso malformed or malicious data doesn't throw, but that’s not required by the rule; the key fix is the origin check. - Keep the existing behavior (parse JSON, set
innerHTML) for messages from trusted origins only.
| @@ -38,7 +38,11 @@ | ||
| // CodeQL rule: js/postmessage-star-origin (or missing origin validation) | ||
| function listenForMessages() { | ||
| window.addEventListener('message', (event) => { | ||
| // VULNERABILITY: no check on event.origin | ||
| const trustedOrigins = ['https://www.example.com']; | ||
| if (!trustedOrigins.includes(event.origin)) { | ||
| // Ignore messages from untrusted origins | ||
| return; | ||
| } | ||
| const payload = JSON.parse(event.data); | ||
| document.getElementById('output')!.innerHTML = payload.html; | ||
| }); |
No description provided.