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
- Trim the input: strings.TrimSpace(rawURL) before validation/calling Ensure.
- Reject or sanitize at config-load time — strip control characters and re-validate.
- If the character is legitimate data (it rarely is in a URL), percent-encode it instead of embedding it raw.
- 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
- Trim all remote URLs read from shell output, files, or env vars (strings.TrimSpace).
- Strip CR from Windows/CRLF-edited config files at load time.
- Never interpolate raw multiline strings into URL config values.
- Log URLs with %q so invisible characters become visible during debugging.
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
- remote URL cannot be empty
- remote URL must not start with a dash
- no store is open for this workspace
- not found
- no absolute native user directory is available
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/29d0bb21488721f1.
Report an issue: GitHub.