September 5, 2026
URL Contexts: href, src, and the javascript: Bridge
Hello, I am Nitin.

By Nitin yadav
6 min read
Day five. Today is the context where you do not break out of anything.
In every context so far, the job was escaping โ get out of the string, get out of the attribute, get out of the element. URL context is different. Your input is already exactly where it needs to be. The application has handed you an attribute whose entire purpose is "the browser will go here or fetch this." You are not fighting the parser. You are just choosing a scheme it will execute.
<a href="nitn4041x">Continue</a><a href="nitn4041x">Continue</a>Becomes:
<a href="javascript:alert(document.domain)">Continue</a><a href="javascript:alert(document.domain)">Continue</a>No quotes. No angle brackets. Nothing encoded. Every character in that payload is one an HTML encoder considers harmless.
That is why this context survives on applications that have otherwise locked XSS down completely.
Which attributes are URL contexts
Know the full list, because people test href and stop.
Navigation and links
a hrefโ executes the javascript scheme on clickarea hrefโ same, on image mapsform actionandbutton formactionโ executes on submitbase hrefโ does not execute directly, but redirects every relative URL on the page, which is a CSP bypass primitive we cover on day 21
Embedding
iframe srcโ the javascript scheme executes in some contextsiframe srcdocโ a whole HTML document, entity-decodedembed srcandobject dataโ a base64 data URL is a classic CSP bypass when object-src is not locked downscript srcโ if you control this, you control everything
Resources
img srcโ does not execute the javascript scheme, but a broken src fires the error handlerlink hrefโ stylesheet injectionsvg use hrefand the xlink variant โ has had executable behaviour depending on browser version and reference type
Client-side redirect sinks โ not attributes, but the same problem
location = X,location.href = X,location.assign(X),location.replace(X)window.open(X)
That last group matters enormously and is the single most reliable place to find this bug in a modern SPA. A server-issued 302 will not execute the javascript scheme โ the browser refuses. But a client-side redirect written in JavaScript will happily navigate to it, because as far as the browser is concerned that is the page choosing to run its own code.
So the first question on any redirect-shaped parameter is: is the redirect server-side or client-side? Check for a 3xx with a Location header. If there is no 3xx, the redirect is happening in JavaScript, and the javascript scheme is on the table.
Bypassing scheme filters
Almost every application that reflects user input into href has something blocking the javascript scheme. Almost none of them do it correctly, because they string-match instead of parsing.
The browser's URL parser is far more permissive than developers expect. Here is what it tolerates before the scheme.
Case variation
Schemes are case-insensitive:
JaVaScRiPt:alert(document.domain)JaVaScRiPt:alert(document.domain)Still works against naive blocklists in 2026. Try it first because it costs nothing.
Whitespace and control characters inside the scheme
The URL parser strips tab, line feed, and carriage return from URLs โ including from the middle of the scheme name.
java%09script:alert(document.domain)
java%0ascript:alert(document.domain)
java%0dscript:alert(document.domain)java%09script:alert(document.domain)
java%0ascript:alert(document.domain)
java%0dscript:alert(document.domain)A filter doing a startsWith check or a regex on the literal string sees a scheme with a tab in the middle and passes it. The browser strips the tab and executes. This is the highest-value single trick in this whole post.
Leading whitespace and null bytes
%20javascript:alert(document.domain)
%09javascript:alert(document.domain)
%00javascript:alert(document.domain)%20javascript:alert(document.domain)
%09javascript:alert(document.domain)
%00javascript:alert(document.domain)Leading whitespace is stripped by the URL parser. Null bytes have historically been stripped by some parsers and some server-side filters, creating a mismatch.
HTML entity encoding
Remember the two-parser rule. Inside an HTML attribute, entities are decoded by the HTML parser before the URL is ever resolved:
javascript:alert(document.domain)
javascript:alert(document.domain)
javascript:alert(document.domain)javascript:alert(document.domain)
javascript:alert(document.domain)
javascript:alert(document.domain)Padded zeros and a missing trailing semicolon both work in most parsers. A server-side filter reading the raw parameter finds no match.
Combining
The reason WAF bypass writeups look like line noise is that these stack. My advice: do not start there. Start with one technique at a time so you learn which rule you defeated. That knowledge is what you put in the report, and it is what makes the fix correct rather than another blocklist entry.
The payload body
Once the scheme is through, the filter may still block the function name or parentheses. Everything from day three applies โ backticks, bracket property access, string reconstruction. And one specific to URL context: the payload after the scheme is URL-decoded, so you get a whole extra encoding layer for free. Double-encode if the value passes through two decode steps, which happens more often than you would think in redirect chains.
Hunting URL contexts specifically
Find redirect-shaped parameters at scale
The parameter names are boringly consistent across the entire internet:
url, uri, u, redirect, redirect_url, redirect_uri, redirectUrl, next,
return, returnUrl, returnTo, return_to, continue, dest, destination,
target, goto, go, out, link, to, view, page, path, file, image_url,
callback, forward, checkout_url, r, rurl, login_url, logout_urlurl, uri, u, redirect, redirect_url, redirect_uri, redirectUrl, next,
return, returnUrl, returnTo, return_to, continue, dest, destination,
target, goto, go, out, link, to, view, page, path, file, image_url,
callback, forward, checkout_url, r, rurl, login_url, logout_urlMine your archive dump for these:
gau target.com | grep -Ei "(url|uri|redirect|next|return|continue|dest|target|goto|link|to|out|callback)=" | sort -u > redirect-params.txtgau target.com | grep -Ei "(url|uri|redirect|next|return|continue|dest|target|goto|link|to|out|callback)=" | sort -u > redirect-params.txtThen fire a marker and see which ones actually navigate:
cat redirect-params.txt | qsreplace "<https://nitn4041x.example>" | httpx -silent -location -mc 200,301,302,303,307,308cat redirect-params.txt | qsreplace "<https://nitn4041x.example>" | httpx -silent -location -mc 200,301,302,303,307,308Grep the JavaScript for client-side redirect sinks
This is where the money is, because client-side redirects execute the javascript scheme and server-side ones do not.
grep -nE "location\s*(\.href|\.assign|\.replace)?\s*=|window\.open\(" all-js.txtgrep -nE "location\s*(\.href|\.assign|\.replace)?\s*=|window\.open\(" all-js.txtThen trace backwards: what feeds that assignment? If it is the query string, the fragment, a value from the app's router, or something read from a cross-window message, you have a candidate.
Check the code that builds links
grep -nE "\.href\s*=|setAttribute\(\s*['\"]href|\.src\s*=" all-js.txtgrep -nE "\.href\s*=|setAttribute\(\s*['\"]href|\.src\s*=" all-js.txtAn SPA that reads a next parameter and writes it into a Continue button's href is the exact pattern this post is about, and it is extremely common in login and checkout flows.
Do not forget markdown and rich text
If the application renders markdown, link syntax pointing at the javascript scheme is a URL context with a completely different filter in front of it. Many markdown renderers have their own scheme allowlist, and many of those allowlists are string-matched and therefore vulnerable to everything above. Day 18 is entirely about markdown renderers.
The interaction problem, and how to solve it
The javascript scheme in an href requires a click. That is a real severity reduction and triage will apply it.
Three ways to remove or reduce the requirement:
Find the same sink in a client-side redirect instead. A location assignment fires on page load. No click. This is why the JS grep above matters more than the anchor-tag hunt.
Find it in formaction on an autofocused submit button. Combined with an auto-submit, this can fire without a deliberate click.
Find it in iframe srcdoc or object data. These load automatically.
If none of those apply and you genuinely have a click-required finding, still report it โ but be honest about the interaction requirement and argue severity based on where the link appears. A malicious scheme in the Continue button of a login flow is a link users are trained to click. That argument is legitimate and triage teams respond to it.
Impact ladder
- Informational โ protocol-relative open redirect only, no script execution. Report it as an open redirect. Many programs consider it low or out of scope on its own; it becomes valuable as a chain component.
- Low โ executable scheme in a link requiring a deliberate, unusual click.
- Medium โ executable scheme in a prominent action link (Continue, Return to site, Back to dashboard) in a flow users complete routinely.
- High โ executable scheme reaching a client-side redirect sink, firing on page load with no interaction, on an authenticated origin.
- High to critical โ script src or base href control. If you can point the page at your own script, you have full origin control and you have also just bypassed most CSP configurations. The base-uri directive is not covered by default-src, which is one of the most common CSP mistakes in existence.
For a client-side redirect sink, a screenshot showing the URL bar containing your payload and the alert showing the target's origin is the complete evidence package.
If the program's scope excludes open redirects, that exclusion usually does not extend to XSS achieved through a redirect parameter โ but check, and say clearly in the title that you are reporting script execution, not a redirect.
Conclusion โ steal this checklist
- URL context needs no breakout. You are already in the right place; you only need a scheme.
- Map the full attribute list: href, formaction, action, src, srcdoc, data, base href, and the SVG use reference.
- Server-side 3xx redirects will not execute the javascript scheme. Client-side ones will. Determine which you have before anything else.
- Grep the JS bundle for location assignment, assign, replace, and window.open. That is the highest-value hunt in this post.
- Scheme bypasses in order: case variation, then embedded tab, newline, or carriage return, then leading whitespace, then HTML entities. One at a time so you learn which rule broke.
- A tab inside the scheme name defeats string-matching filters that use startsWith or a literal regex.
- HTML entities decode before URL resolution inside attributes, so an entity-prefixed scheme is a different string to every server-side filter.
- The payload after the scheme gets an extra URL-decode layer. Use it, and test double encoding on multi-hop redirects.
- Mine redirect-shaped parameter names from archives; there are about thirty and they are the same everywhere.
- Markdown link syntax is a URL context with its own separate, usually weaker, filter.
- Removing the click requirement raises severity more than any payload cleverness. Hunt the client-side sink, not the anchor tag.
- base href and script src control are the top of this ladder and they carry CSP implications.
Tomorrow: stored XSS โ how to map every place your content is actually rendered, including the places the UI never shows you.
If you Love reading my blogs. Check my Youtube Channel too.