{"record":{"id":"7e23ddd9c95736f9","repo":"gastownhall/beads","slug":"remote-url-must-not-start-with-a-dash","errorCode":null,"errorMessage":"remote URL must not start with a dash","messagePattern":"remote URL must not start with a dash","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/remotecache/url.go","lineNumber":93,"sourceCode":"// 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.)\nfunc validateSchemeURL(rawURL string) error {\n\t// net/url doesn't understand git+ssh:// etc., so we normalize first\n\tnormalizedURL := rawURL\n\tscheme := \"\"\n\tif idx := strings.Index(rawURL, \"://\"); idx > 0 {\n\t\tscheme = rawURL[:idx]","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/remotecache/url.go#L75-L111","documentation":"ValidateRemoteURL() rejects URLs beginning with '-' because exec.Command passes arguments directly to the dolt CLI — a URL like '-oProxyCommand=...' could be parsed as a flag (CLI flag injection). This is a deliberate security-boundary check applied to all remote URLs before they reach command arguments.","triggerScenarios":"ValidateRemoteURL (via Ensure or ValidateRemoteURLWithPatterns) receives a string whose first character is '-', e.g. a mangled config value, an argument-order bug where a flag was passed where the URL belongs, or a deliberately crafted input.","commonSituations":"Swapped CLI arguments (bd sync --remote -f); config file where the value got merged with a flag; hand-edited config with a stray dash; scripts passing options positionally into the URL slot.","solutions":["Correct the call/config so the remote URL (starting with a scheme like dolthub:// or https://) is passed as the URL, and flags use their own flags.","Inspect config/env sources for a leading '-' typo and remove it.","If you need to pass something dash-prefixed, it is not a URL — use the proper parameter/flag instead.","Add a caller-side check that the remote URL contains '://' or matches SCP-style user@host:path before invoking."],"exampleFix":"// before: flag accidentally passed as URL\n_, err := cache.Ensure(ctx, \"-verbose\")\n// after: guard and pass a real URL\nif !strings.Contains(remoteURL, \"://\") && !strings.Contains(remoteURL, \"@\") {\n    return fmt.Errorf(\"%q is not a remote URL\", remoteURL)\n}\n_, err = cache.Ensure(ctx, remoteURL)","handlingStrategy":"validation","validationCode":"func looksLikeRemoteURL(s string) bool {\n    if strings.HasPrefix(s, \"-\") {\n        return false\n    }\n    return remotecache.IsRemoteURL(s)\n}\n// before calling Ensure:\nif !looksLikeRemoteURL(arg) {\n    return fmt.Errorf(\"%q is not a remote URL (did you swap flag and URL?)\", arg)\n}","typeGuard":null,"tryCatchPattern":"if err := cache.Ensure(ctx, remoteURL); err != nil {\n    if strings.Contains(err.Error(), \"must not start with a dash\") {\n        return fmt.Errorf(\"argument parsing bug: %q used as URL; check flag order\", remoteURL)\n    }\n    return err\n}","preventionTips":["Always pass remote URLs as dedicated named flags/options, never positionally next to flags.","Validate user input with ValidateRemoteURL as early as possible (CLI arg parsing stage).","Treat any dash-leading value in a URL slot as an argument-order bug, not data.","Escape or reject '-'-prefixed values in scripts that build bd commands dynamically."],"tags":["go","validation","security","cli-injection"],"backgroundTag":"invalid-remote-url","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}