gastownhall/beads · error

remote URL contains control character at position %d (0x%02x

Error message

remote URL contains control character at position %d (0x%02x)

What it means

ValidateRemoteURL() rejects any URL containing control characters (bytes < 0x20 or 0x7f — null bytes, newlines, tabs, CR, etc.), reporting the byte position and hex value. Control characters in a URL can corrupt exec.Command arguments or inject into SQL/logs, so this is a hard security rejection rather than a lenient parse.

Source

Thrown at internal/remotecache/url.go:87

	return gitSSHPattern.MatchString(s)
}

// ValidateRemoteURL performs strict security validation on a remote URL.
// It rejects URLs containing control characters (including null bytes),
// validates structural correctness per scheme, and rejects leading dashes
// that could be interpreted as CLI flags.
//
// This is a security boundary — all remote URLs should pass through this
// before reaching exec.Command arguments or SQL parameters.
func ValidateRemoteURL(rawURL string) error {
	if rawURL == "" {
		return fmt.Errorf("remote URL cannot be empty")
	}

	// Reject control characters (null bytes, newlines, tabs, etc.)
	for i, c := range rawURL {
		if c < 0x20 || c == 0x7f {
			return fmt.Errorf("remote URL contains control character at position %d (0x%02x)", i, c)
		}
	}

	// Reject leading dash (CLI flag injection via exec.Command arguments)
	if strings.HasPrefix(rawURL, "-") {
		return fmt.Errorf("remote URL must not start with a dash")
	}

	// SCP-style URLs (user@host:path) are validated separately
	if gitSSHPattern.MatchString(rawURL) {
		return validateSCPURL(rawURL)
	}

	// Parse as standard URL
	return validateSchemeURL(rawURL)
}

// validateSchemeURL validates a scheme-based URL (https://, dolthub://, etc.)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Trim the input: strings.TrimSpace(rawURL) before validation/calling Ensure.
  2. Reject or sanitize at config-load time — strip control characters and re-validate.
  3. If the character is legitimate data (it rarely is in a URL), percent-encode it instead of embedding it raw.
  4. Fix the source (config editor line endings, shell command substitution) producing the stray control char — the error's position/hex value identifies which.

Example fix

// before: URL straight from command output (trailing \n)
urlBytes, _ := exec.Command("git", "remote", "get-url", "origin").Output()
remote := string(urlBytes)
_, err := cache.Ensure(ctx, remote) // control character error
// after: trim before use
remote := strings.TrimSpace(string(urlBytes))
_, err = cache.Ensure(ctx, remote)
Defensive patterns

Strategy: validation

Validate before calling

func sanitizeRemoteURL(raw string) (string, error) {
    s := strings.TrimSpace(raw)
    for i, c := range s {
        if c < 0x20 || c == 0x7f {
            return "", fmt.Errorf("control char 0x%02x at %d; fix config/shell quoting", c, i)
        }
    }
    return s, remotecache.ValidateRemoteURL(s)
}

Try / catch

remote, err := sanitizeRemoteURL(cfg.RemoteURL)
if err != nil {
    if strings.Contains(err.Error(), "control character") {
        return fmt.Errorf("re-quote the value in your config (stray newline/tab?): %w", err)
    }
    return err
}
return cache.Ensure(ctx, remote)

Prevention

When it happens

Trigger: ValidateRemoteURL (via Ensure or ValidateRemoteURLWithPatterns) receives a URL containing e.g. a trailing newline from shell/CI output, a tab or CR from parsing `git remote -v`, a null byte from bad binary data, or an embedded carriage return from Windows-edited config.

Common situations: Capturing a remote URL with `$(...)` command substitution (keeps trailing newline); copy-pasting a URL with invisible characters; Windows CRLF config files; template interpolation inserting a newline; programmatic URL assembly that appends '\n'.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/29d0bb21488721f1. Report an issue: GitHub.