Safeguard
Application Security•

ASVS 5.0 V1.2.2: Build URLs Safely and Block Bad Schemes

ASVS V1.2.2 requires URL encoding of untrusted values and an allowlist of protocols. See code to block javascript: and data: links and how to test it.

Safeguard Security Team
3 min read

ASVS 5.0 V1.2.2 (Level 1) requires two things when you build URLs from untrusted data: encode each value for the URL part it goes into (URL encoding or base64url for path and query parameters), and allow only safe protocols, so javascript: and data: never end up in a link or redirect.

Why it matters

A value that contains &, # or / can add parameters, truncate a URL or move a path. Without encoding, a search term like a&admin=true quietly becomes two parameters. And if a user supplied URL is rendered as an href, then javascript:alert(document.cookie) runs script on click. Redirect and profile website fields are the usual victims.

How to implement it

Encode each component separately, with a library, never by string concatenation of raw values.

const url = new URL('https://api.example.com/search');
url.searchParams.set('q', userQuery);          // encodes for you
url.pathname = '/users/' + encodeURIComponent(userId);
from urllib.parse import quote, urlencode

path = "/users/" + quote(user_id, safe="")
query = urlencode({"q": user_query})

For URLs supplied by users, parse them and check the scheme against an allowlist:

function safeHttpUrl(input) {
  try {
    const u = new URL(input);
    return (u.protocol === 'https:' || u.protocol === 'http:') ? u.toString() : null;
  } catch {
    return null;
  }
}

Render the link only when the function returns a value. Checking that a string starts with http is not enough, since httpx: or whitespace and control character tricks can slip past naive checks, while the URL parser normalizes them.

How to test it

Submit these into every field that becomes a link or redirect target: javascript:alert(1), JaVaScRiPt:alert(1), data:text/html,hello, javascript:alert(1) with a leading space, and //evil.example. None may appear as a working href. For parameters, submit a&b=c#d and confirm the outgoing URL contains a%26b%3Dc%23d.

Common mistakes

  • Blocking only the lowercase string javascript:.
  • Encoding the whole URL, which breaks the slashes, instead of each value.
  • Using encodeURI for a parameter value. It leaves & and = alone.
  • Allowing relative redirects that start with two slashes.

Where this goes wrong in practice

Open redirects are the most common real world case. A login page takes ?next=/account and sends the user there after sign in. An attacker changes it to //evil.example or https://evil.example and shares the link, and the user lands on a fake page that looks trusted because the first click was on your domain. Treat the redirect target the same way as any other URL input: parse it, accept only relative paths that start with a single slash, or compare the host against a short allowlist.

Another frequent case is API clients that build request URLs from IDs. If the ID is ../admin, a path like /users/ plus the ID reaches a different endpoint. Encoding the ID with encodeURIComponent turns the slashes into %2F, which keeps it in one path segment. Add a check on the shape of the ID as well, because encoding stops structure changes but does not prove the value is sensible.

Quick check

Which function encodes a single query value in JavaScript?

encodeURIComponent, or better, URLSearchParams through the URL object. encodeURI is meant for whole URLs and leaves & untouched.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.