Short answer: %252F is the text %2F encoded as data: %25 represents its percent sign. One component decode produces %2F; a second produces slash. Decode exactly one layer at one documented boundary, validate the representation consumed by the privileged operation, and make every proxy, router, framework, and application agree on normalization.

How one escape becomes two layers

Percent-encoding represents a byte as %HH. A slash byte becomes %2F. If that three-character text must itself travel inside another encoded component, its percent sign is data and becomes %25. The outer result is %252F. This is not a special double-encoding syntax; it is ordinary component encoding applied to already encoded text.

Layer-by-layer interpretation
raw data               /
after one encode        %2F
after a second encode   %252F

decode once             %2F
decode twice            /

The same pattern applies to a percent-encoded dot %2E, question mark %3F, ampersand %26, or literal percent %25. Looking for the substring %25 can identify candidates, but it does not prove malicious intent. A literal percent sign and a legitimate nested URI also contain that sequence.

Nested layers can be legitimate

An OAuth callback can be a query parameter inside an authorization URL. A source URL can be a value in an image proxy request. A complete link can appear inside a debugging URL. Each outer component must protect the inner percent signs so they survive transport. The correct receiver removes the outer layer once and hands the still-encoded inner value to the component that owns it.

Trouble starts when ownership is implicit. If a framework automatically decodes before application code runs and the application calls decodeURIComponent again, two layers disappear even though the programmer sees only one explicit call. Document whether each API returns raw bytes, a URL string, or an already-decoded component.

Why inconsistent decoding bypasses policy

Imagine an edge filter that rejects a literal slash in a user-controlled path token. It sees %252F, decodes once to %2F, finds no literal slash, and allows the request. A backend decodes the value again and now sees /. The filter and backend made decisions about different paths.

The same disagreement can expose dot segments, query separators, fragment markers, NUL bytes in unsafe native integrations, or characters meaningful to a downstream interpreter. Encoding is not the vulnerability by itself; mismatched canonicalization plus a privileged operation is. Test the complete deployed chain, including CDN, reverse proxy, web server, router, framework, and business code.

A dangerous ordering
request:          %252e%252e%252fprivate
filter decodes:   %2e%2e%2fprivate      # policy allows encoded-looking text
router decodes:   ../private            # structure appears after validation

This example illustrates a class of risk, not a claim that every server accepts encoded slashes or traverses a filesystem. Many servers reject or preserve them. Security comes from explicit agreement, not from assuming one product's default applies everywhere.

RFC 3986 says not to process the same string twice

RFC 3986 section 2.4 states that implementations must not percent-encode or decode the same string more than once. A decoded percent sign can otherwise be misread as the beginning of a new escape, while re-encoding an existing escape turns its percent sign into data.

That guidance does not prohibit a documented nested protocol from removing one layer at each separate boundary. It prohibits treating one undifferentiated string as a puzzle to transform repeatedly. Preserve typed boundaries: outer query value, inner URL, inner path segment, and final application identifier are different values even if represented as strings in code.

Strict single-round component decoding

For a UTF-8 text component, a strict decoder validates every percent sign followed by exactly two hexadecimal digits, collects the represented bytes, and rejects invalid UTF-8. ECMAScript's decodeURIComponent throws URIError for incomplete escapes, non-hex pairs, impossible UTF-8 continuation sequences, and overlong or otherwise invalid encodings.

Strict decoding must still happen at the right component boundary. Running decodeURIComponent on an assembled URL can turn an escaped question mark or ampersand into structure before parsing. Parse the complete URL first, select the one component owned by the current code, and decode one expected layer.

  1. Capture the raw representation. Preserve it long enough to diagnose which layer arrived.
  2. Parse URL structure. Do not decode separators before identifying components.
  3. Confirm ownership. Know whether an upstream layer already decoded this value.
  4. Decode once. Reject malformed escapes and invalid UTF-8.
  5. Canonicalize for the sink. Apply path, identifier, or query rules required by the final operation.
  6. Validate after final normalization. The security check and privileged sink must interpret the same value.

Avoid permissive recovery anti-patterns

A loop such as “while the text contains a percent sign, decode again” is unsafe and incorrect. Percent can be legitimate data, an inner encoding layer, or malformed input. Catching URIError and returning a partially decoded prefix is also dangerous because different callers can receive different interpretations of the same bytes.

Do not normalize by replacing + with space outside a known form-urlencoded field. Plus handling is separate from percent-decoding and is explained in %20 versus +. Do not encode an entire URL again to “repair” stray characters; use the component model in the percent-encoding guide.

Raw, once-decoded, and twice-decoded diagnostics

Raw inputAfter one strict component decodeAfter an unsafe extra decode
%252F%2F/
%252E%2E.
%2526role%253Dadmin%26role%3Dadmin&role=admin
100%2525100%25100%

A diagnostic interface may show possible additional rounds to explain an input, but production code must not use that display as authorization. The LiveParse URL Decoder is deliberately a local inspection tool, not a sanitizer. Compare one-round output with the raw string and the actual consuming contract.

Design one canonical request pipeline

Define the representation at every interface. A reverse proxy should either forward a raw request target with documented guarantees or forward structured, normalized values whose status is explicit. An application route should know whether path parameters are decoded. A file or object-store access layer should validate its final canonical identifier and keep it within the intended namespace.

Apply allowlists to semantics, not encoded spellings. For a fixed identifier, accept only the expected character set after the one owned decode. For filesystem paths, resolve against a fixed root using the platform's path API and confirm containment after normalization. For redirects or outbound fetches, parse with a standards-based URL parser and enforce scheme, credentials, host, and port policy on the parsed result. Percent-encoding alone neither grants nor removes authority.

Inspect locally without overstating the result

Use the URL Parser to identify structure, the Query String Parser for form-style pairs, the URL Decoder for one strict component round, and the URL Encoder to reproduce the expected layer. The tools process input in the browser, make no request to the parsed destination, and do not upload values to a processing API.

General text/component input is limited to 200,000 UTF-16 code units. Complete URL and base URL fields are limited to 32,768. Those are local responsiveness limits, not an assurance that a proxy or server accepts inputs of that size and not a security certification of decoded output.

Primary specifications

Investigating a repeated escape? Keep the raw value, decode one component round, and compare it with the layer your application actually owns.

Open the URL Decoder

Frequently asked questions

What is double URL encoding?

Double URL encoding occurs when percent-encoded text is percent-encoded again. The percent sign in %2F becomes %25, producing %252F; one decode yields %2F and a second yields slash.

Is double URL encoding always malicious?

No. It can arise legitimately when one encoded component is transported as data inside another layer. It becomes dangerous when system layers disagree about ownership, decoding count, or the point at which validation occurs.

Why is repeated URL decoding a security risk?

A validator can approve the first-round text while a later proxy, router, or application decodes hidden delimiters such as slash or dot. The later interpretation may create a path or parameter that the earlier policy never checked.

Should a decoder keep decoding until the value stops changing?

No. Decode exactly once at the boundary that owns one encoding layer. A decode-until-stable loop destroys layer information, makes ambiguous input executable, and directly contradicts RFC 3986 guidance.

What should strict URL decoding reject?

For a UTF-8 text component, reject stray percent signs, incomplete or non-hex escapes, and percent-encoded byte sequences that are not valid UTF-8. Keep the raw input for diagnosis and do not silently replace errors.

Does the LiveParse URL Decoder make an encoded value safe?

No. It performs local, single-round inspection without fetching a destination or sending input to a processing API. It does not authorize a path or sanitize data for a particular server. General input is limited to 200,000 UTF-16 code units; URL and base fields are limited to 32,768.