{"record":{"id":"1a4d82b4735c4789","repo":"gastownhall/beads","slug":"invalid-ref-format-s","errorCode":null,"errorMessage":"invalid ref format: %s","messagePattern":"invalid ref format: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/migration_content_hashes.go","lineNumber":27,"sourceCode":"\n\t\"github.com/steveyegge/beads/internal/storage/dberrors\"\n)\n\n// validMigrationRefPattern matches the refs this package builds for AS OF reads\n// (Dolt commit hashes or branch/remote-tracking names like\n// \"remotes/origin/main\"). It mirrors issueops.ValidateRef but is kept local so\n// the schema package — which sits below issueops — has no import-cycle risk.\nvar validMigrationRefPattern = regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`)\n\nfunc validateMigrationRef(ref string) error {\n\tif ref == \"\" {\n\t\treturn fmt.Errorf(\"ref cannot be empty\")\n\t}\n\tif len(ref) > 128 {\n\t\treturn fmt.Errorf(\"ref too long\")\n\t}\n\tif !validMigrationRefPattern.MatchString(ref) {\n\t\treturn fmt.Errorf(\"invalid ref format: %s\", ref)\n\t}\n\treturn nil\n}\n\n// ReadMigrationContentHashes reads version -> content_hash from schema_migrations,\n// either at HEAD (ref == \"\") or AS OF ref (e.g. \"remotes/origin/main\"). NULL/empty\n// hashes are dropped. It returns an error when the table, column, or ref is\n// unavailable; the caller classifies it with RemoteRefUnavailableErr /\n// MissingMigrationObjectErr.\n//\n// Dolt requires a literal ref in AS OF: bind parameters (including inside CONCAT)\n// fail server-side with `unbound variable \"v1\" in query`, so the validated ref is\n// interpolated into the SQL text (bd-6dnrw.27).\nfunc ReadMigrationContentHashes(ctx context.Context, db DBConn, ref string) (map[int]string, error) {\n\tvar (\n\t\trows *sql.Rows\n\t\terr  error\n\t)","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/migration_content_hashes.go#L9-L45","documentation":"validateMigrationRef checks the ref against ^[a-zA-Z0-9_./-]+$ and rejects anything else. Because the ref is interpolated into an AS OF SQL literal (bind params are not supported there), this allowlist is the SQL-injection guard; unsafe characters are rejected outright.","triggerScenarios":"Calling ReadMigrationContentHashes with refs containing quotes, spaces, colons, or other characters outside the allowlist — e.g. \"origin/main feature\" (space), \"refs/heads/main:tip\", a ref with a single quote, or user-supplied input passed through unchecked.","commonSituations":"User/CLI input passed directly as ref; shell interpolation leaving stray whitespace or quotes; attempting to pass SHA:size or other decorated syntax; injection attempts caught by the validator.","solutions":["Normalize the ref to allowed characters (letters, digits, _, ., /, -) before calling.","Strip whitespace and decorations; use the plain ref name (e.g. remotes/origin/main).","Reject/escape at the CLI/config boundary so bad input never reaches the query.","If the ref is user-controlled, validate with the same pattern before passing it in."],"exampleFix":"// before\nref := strings.TrimSpace(userInput) // may contain quotes/spaces\nschema.ReadMigrationContentHashes(ctx, db, ref)\n// after\nvar refRe = regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`)\nref := strings.TrimSpace(userInput)\nif !refRe.MatchString(ref) {\n    return fmt.Errorf(\"unsupported ref %q\", ref)\n}\nschema.ReadMigrationContentHashes(ctx, db, ref)","handlingStrategy":"validation","validationCode":"var refRe = regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`)\nif !refRe.MatchString(ref) { return fmt.Errorf(\"ref has unsupported characters: %q\", ref) }","typeGuard":"func validRef(ref string) bool {\n    return regexp.MustCompile(`^[a-zA-Z0-9_./-]+$`).MatchString(ref)\n}","tryCatchPattern":null,"preventionTips":["Sanitize any user/CLI-supplied ref with the same allowlist.","Strip whitespace and shell artifacts before calling.","Remember the ref is interpolated into SQL — never relax the allowlist."],"tags":["validation","sql-injection","git-ref","security"],"backgroundTag":"invalid-git-ref","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}