projectdiscovery/nuclei · error

failed to parse url: %s

Error message

failed to parse url: %s

What it means

ParseRawRequest could not turn the request target (second token of the method line) into a URL. Absolute-form targets (http://.../https://...) go through urlutil.ParseAbsoluteURL with strict=true; origin-form targets go through urlutil.ParseRawRelativePath, which expects a path starting with '/' and valid path/query syntax.

Source

Thrown at pkg/input/types/http.go:258

	method := parts[0]
	rr.Request.Method = method

	// the request target is normally an origin-form path, but proxy captures and
	// .http files use the absolute form, which already carries the authority
	var urlx *urlutil.URL
	target := parts[1]
	if stringsutil.HasPrefixAnyI(target, urlutil.HTTP+urlutil.SchemeSeparator, urlutil.HTTPS+urlutil.SchemeSeparator) {
		// urlutil.ParseAbsoluteURL only accepts lowercase schemes; preserve the
		// remainder of the request target unchanged.
		if scheme, rest, ok := strings.Cut(target, urlutil.SchemeSeparator); ok {
			target = strings.ToLower(scheme) + urlutil.SchemeSeparator + rest
		}
		urlx, err = urlutil.ParseAbsoluteURL(target, true)
	} else {
		urlx, err = urlutil.ParseRawRelativePath(target, true)
	}
	if err != nil {
		return nil, fmt.Errorf("failed to parse url: %s", err)
	}
	rr.URL = *urlx

	// parse headers
	rr.Request.Headers = mapsutil.NewOrderedMap[string, string]()
	for {
		line, err := protoReader.ReadLine()
		if err != nil {
			return nil, fmt.Errorf("failed to read header line: %s", err)
		}
		if line == "" {
			// end of headers next is body
			break
		}
		key, value, found := strings.Cut(line, ":")
		if !found || key == "" {
			return nil, fmt.Errorf("invalid header line: %s", line)
		}

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Use an origin-form target starting with '/', e.g. '/api/v1/users?id=1', or a full absolute URL
  2. Percent-encode spaces and unsafe characters in the path/query
  3. Replace 'OPTIONS *' with 'OPTIONS / HTTP/1.1' (asterisk-form is unsupported)
  4. Wrap IPv6 hosts in brackets: http://[::1]:8080/

Example fix

# before
raw: |
  GET api/users?id=1 HTTP/1.1

# after
raw: |
  GET /api/users?id=1 HTTP/1.1
Defensive patterns

Strategy: validation

Validate before calling

target := parts[1]
if strings.HasPrefix(strings.ToLower(target), "http://") || strings.HasPrefix(strings.ToLower(target), "https://") {
    if _, err := url.Parse(target); err != nil { return err }
} else if !strings.HasPrefix(target, "/") {
    return fmt.Errorf("origin-form target must start with '/': %q", target)
}

Try / catch

Catch the parse failure, normalize the target (add leading '/', percent-encode spaces), and re-parse once before giving up on the entry.

Prevention

When it happens

Trigger: Relative target without a leading slash ('api/foo'), containing spaces or control chars, or '*'; absolute target with bad authority (e.g. 'http:///path', 'https://host:port:notaport/'). Mixed-case schemes like HTTP:// are handled by lowercasing, so that is not the cause.

Common situations: Pasting paths without the leading slash; unencoded spaces in query strings from manual edits; asterisk-form OPTIONS requests ('OPTIONS * HTTP/1.1') which this parser does not support; IPv6 literals missing brackets.

Understand the failure class

Related errors


AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15). Data as JSON: /api/errors/d6c8c02e0121042a. Report an issue: GitHub.