{"record":{"id":"194d168ae7f4ea65","repo":"cloudflare/cloudflared","slug":"url-s-has-invalid-escape-characters-s","errorCode":null,"errorMessage":"URL %s has invalid escape characters %s","messagePattern":"URL (.+?) has invalid escape characters (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"validation/validation.go","lineNumber":100,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"URL should not be empty\")\n\t}\n\n\tif net.ParseIP(originUrl) != nil {\n\t\treturn validateIP(\"\", originUrl, \"\")\n\t} else if strings.HasPrefix(originUrl, \"[\") && strings.HasSuffix(originUrl, \"]\") {\n\t\t// ParseIP doesn't recoginze [::1]\n\t\treturn validateIP(\"\", originUrl[1:len(originUrl)-1], \"\")\n\t}\n\n\thost, port, err := net.SplitHostPort(originUrl)\n\t// user might pass in an ip address like 127.0.0.1\n\tif err == nil && net.ParseIP(host) != nil {\n\t\treturn validateIP(\"\", host, port)\n\t}\n\n\tunescapedUrl, err := url.PathUnescape(originUrl)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"URL %s has invalid escape characters %s\", originUrl, unescapedUrl)\n\t}\n\n\tparsedUrl, err := url.Parse(unescapedUrl)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"URL %s has invalid format\", originUrl)\n\t}\n\n\t// if the url is in the form of host:port, IsAbs() will think host is the schema\n\tvar hostname string\n\thasScheme := parsedUrl.IsAbs() && parsedUrl.Host != \"\"\n\tif hasScheme {\n\t\terr := validateScheme(parsedUrl.Scheme)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\t// The earlier check for ip address will miss the case http://[::1]\n\t\t// and http://[::1]:8080\n\t\tif net.ParseIP(parsedUrl.Hostname()) != nil {","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/validation/validation.go#L82-L118","documentation":"validateUrlString percent-decodes the origin URL with url.PathUnescape before parsing. If the string contains a malformed percent-escape (e.g. a lone '%' not followed by two hex digits), PathUnescape fails and this error is returned. Note the message has a bug: on error unescapedUrl is always the empty string, so the second %s prints nothing useful; the offending input is originUrl.","triggerScenarios":"Calling ValidateUrl or NewAccessValidator with a URL containing an invalid percent sequence such as 'http://example.com/100%' or 'localhost%zz:8080'. Only reached when the input is not a bare IP or host:port IP form.","commonSituations":"Config file or CLI flag where a URL was copied with a raw '%' (e.g. a query string like '?width=50%' or a password containing '%'), or shell/env interpolation left a stray percent; also URLs built by string concatenation with encoded characters.","solutions":["Fix the URL in your config so every '%' is a valid escape (%XX with hex digits) or encode a literal percent as %25","Check the value actually passed: log or print the originUrl before calling ValidateUrl, since the error's second %s is always empty","If the percent is intentional in a query/path, percent-encode it correctly (50%25) before validation","If you only have a hostname, pass just the hostname instead of a full URL"],"exampleFix":"// before\norigin := \"http://service/api?filter=100%\"\nValidateUrl(origin) // invalid escape characters\n// after\norigin := \"http://service/api?filter=100%25\"\nValidateUrl(origin)","handlingStrategy":"validation","validationCode":"func validEscapes(s string) bool {\n    _, err := url.PathUnescape(s)\n    return err == nil\n}\nif !validEscapes(origin) { /* reject before ValidateUrl */ }","typeGuard":null,"tryCatchPattern":"if _, err := validation.ValidateUrl(origin); err != nil {\n    if strings.Contains(err.Error(), \"invalid escape characters\") {\n        // fall back to percent-encoding literal '%'\n    }\n}","preventionTips":["Percent-encode literal '%' as %25 before passing URLs","Trim and sanitize config values read from files/env","Never build URLs by naive string concatenation with unencoded values"],"tags":["url","validation","percent-encoding","configuration"],"backgroundTag":"invalid-url-format","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}