{"record":{"id":"29d0bb21488721f1","repo":"gastownhall/beads","slug":"remote-url-contains-control-character-at-position","errorCode":null,"errorMessage":"remote URL contains control character at position %d (0x%02x)","messagePattern":"remote URL contains control character at position (.+?) \\(0x%02x\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/remotecache/url.go","lineNumber":87,"sourceCode":"\treturn gitSSHPattern.MatchString(s)\n}\n\n// ValidateRemoteURL performs strict security validation on a remote URL.\n// It rejects URLs containing control characters (including null bytes),\n// validates structural correctness per scheme, and rejects leading dashes\n// that could be interpreted as CLI flags.\n//\n// This is a security boundary — all remote URLs should pass through this\n// before reaching exec.Command arguments or SQL parameters.\nfunc ValidateRemoteURL(rawURL string) error {\n\tif rawURL == \"\" {\n\t\treturn fmt.Errorf(\"remote URL cannot be empty\")\n\t}\n\n\t// Reject control characters (null bytes, newlines, tabs, etc.)\n\tfor i, c := range rawURL {\n\t\tif c < 0x20 || c == 0x7f {\n\t\t\treturn fmt.Errorf(\"remote URL contains control character at position %d (0x%02x)\", i, c)\n\t\t}\n\t}\n\n\t// Reject leading dash (CLI flag injection via exec.Command arguments)\n\tif strings.HasPrefix(rawURL, \"-\") {\n\t\treturn fmt.Errorf(\"remote URL must not start with a dash\")\n\t}\n\n\t// SCP-style URLs (user@host:path) are validated separately\n\tif gitSSHPattern.MatchString(rawURL) {\n\t\treturn validateSCPURL(rawURL)\n\t}\n\n\t// Parse as standard URL\n\treturn validateSchemeURL(rawURL)\n}\n\n// validateSchemeURL validates a scheme-based URL (https://, dolthub://, etc.)","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/remotecache/url.go#L69-L105","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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."],"exampleFix":"// before: URL straight from command output (trailing \\n)\nurlBytes, _ := exec.Command(\"git\", \"remote\", \"get-url\", \"origin\").Output()\nremote := string(urlBytes)\n_, err := cache.Ensure(ctx, remote) // control character error\n// after: trim before use\nremote := strings.TrimSpace(string(urlBytes))\n_, err = cache.Ensure(ctx, remote)","handlingStrategy":"validation","validationCode":"func sanitizeRemoteURL(raw string) (string, error) {\n    s := strings.TrimSpace(raw)\n    for i, c := range s {\n        if c < 0x20 || c == 0x7f {\n            return \"\", fmt.Errorf(\"control char 0x%02x at %d; fix config/shell quoting\", c, i)\n        }\n    }\n    return s, remotecache.ValidateRemoteURL(s)\n}","typeGuard":null,"tryCatchPattern":"remote, err := sanitizeRemoteURL(cfg.RemoteURL)\nif err != nil {\n    if strings.Contains(err.Error(), \"control character\") {\n        return fmt.Errorf(\"re-quote the value in your config (stray newline/tab?): %w\", err)\n    }\n    return err\n}\nreturn cache.Ensure(ctx, remote)","preventionTips":["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."],"tags":["go","validation","security","input-sanitization"],"backgroundTag":"invalid-remote-url","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}