Guide

How to detect and fix a double-encoded URL

Recognize patterns such as %2520, find the boundary that encoded twice, and remove one layer without activating hidden delimiters.

by Tools in a Tab · Published on · Reviewed on

Short answer

%2520, %252F, or %253F often signals double encoding: %25 represents the percent sign, so decoding one layer leaves %20, %2F, or %3F. Treat this as evidence to investigate, not permission to decode twice. Identify the component that needed encoding and fix the boundary that added the extra layer.

How %2520 is produced

Component encoding turns a space into %20. If another layer receives those three literal characters and encodes them again, % becomes %25, producing %2520:

space → %20 → %2520

The URL encoder warns when input being encoded already contains a %HH escape or when decoding one layer still leaves escape-like text. The warning is not proof of an error: sometimes an application intentionally transports the literal string %20.

Useful signals and false positives

Look for %25 followed by two hexadecimal digits, especially %2520, %252F, %253A, %253F, %2526, and %253D. Compare the value at every boundary: browser, proxy, framework, logs, and application. The first point where %20 becomes %2520 identifies the duplicated encoding step.

Not every %25 is wrong. The human value 50% is correctly represented as 50%25, and a page teaching the literal text %20 must transport it as %2520. Only the field contract distinguishes literal data from an escape that was applied twice.

Decode a component, not the whole address

Suppose a query parameter contains a return URL. The outer URL encodes the inner one as a value, so nested escapes are expected:

/login?next=https%3A%2F%2Fexample.test%2Fa%3Fx%3D1%25202

Parse the outer URL and extract next first, then decode exactly the layer defined for that value. Applying decodeURIComponent twice to the complete address can turn %26 into & too early, creating another parameter or changing path structure.

RFC 3986 recommends encoding or decoding once unless a specific scheme defines additional layers.

Fixing the source

Keep raw values inside the program. If a function returns an already encoded component, do not pass it through encodeURIComponent again. Better, change the contract so it returns the raw value and make one layer responsible for the URL boundary.

For queries, call URLSearchParams.set(name, rawValue). For path segments, encode each data value once immediately before inserting it. Avoid storing pre-encoded values in a database because different consumers may need HTML, JSON, URL, or another output context.

Safe repair procedure

  1. Preserve the exact input and final URL so the failure is reproducible.
  2. Parse scheme, path, query, and fragment instead of editing the whole string.
  3. Identify data fields and the number of layers documented by the protocol.
  4. Decode one layer of the affected component.
  5. If %HH remains, determine whether it is literal text or another real layer.
  6. Correct the producer and add a round-trip test.

Never add a global rule that replaces %25 with %. It may introduce invalid escapes, change delimiters, or let user-controlled input alter URL structure.

Tests worth keeping

Cover a space, literal percent sign, &, =, /, Unicode, and a nested URL. Assert both the received value and the final structure. A strong test does not merely check that %2520 disappeared; it proves that the original data returns after the exact documented number of layers.