{"record":{"id":"40ab3bebb8d971cd","repo":"gastownhall/beads","slug":"invalid-table-name-s","errorCode":null,"errorMessage":"invalid table name: %s","messagePattern":"invalid table name: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/history.go","lineNumber":51,"sourceCode":"\tif len(name) > 64 {\n\t\treturn fmt.Errorf(\"database name too long\")\n\t}\n\tif !validDatabasePattern.MatchString(name) {\n\t\treturn fmt.Errorf(\"invalid database name: %s\", name)\n\t}\n\treturn nil\n}\n\n// validateTableName checks if a table name is safe to use in queries\nfunc validateTableName(table string) error {\n\tif table == \"\" {\n\t\treturn fmt.Errorf(\"table name cannot be empty\")\n\t}\n\tif len(table) > 64 {\n\t\treturn fmt.Errorf(\"table name too long\")\n\t}\n\tif !validTablePattern.MatchString(table) {\n\t\treturn fmt.Errorf(\"invalid table name: %s\", table)\n\t}\n\treturn nil\n}\n\n// issueHistory represents an issue at a specific point in history\ntype issueHistory struct {\n\tIssue      *types.Issue\n\tCommitHash string\n\tCommitter  string\n\tCommitDate time.Time\n}\n\n// getIssueHistory returns the complete history of an issue\nfunc (s *DoltStore) getIssueHistory(ctx context.Context, issueID string) ([]*issueHistory, error) {\n\t// Wrap in a subquery to avoid Dolt's max1Row optimization on PK lookup.\n\t// dolt_history_* tables return multiple rows per PK (one per commit),\n\t// but the query planner incorrectly assumes WHERE id=? returns one row.\n\trows, err := s.queryContext(ctx, `","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/history.go#L33-L69","documentation":"validateTableName rejects names that do not match validTablePattern. Because table names are interpolated into backtick-quoted SQL statements, characters that could escape the quoting (backticks, quotes, special symbols) are forbidden. The offending name appears in the error message.","triggerScenarios":"Passing a table name containing backticks, quotes, spaces, semicolons, slashes, or other non-identifier characters into a query path guarded by validateTableName.","commonSituations":"User-supplied table/filter input reaching an internal query; dynamically built table names from external config with path-like values ('org/repo'); injection attempts being safely rejected by this guard.","solutions":["Sanitize the table name to plain identifier characters (letters, digits, underscores) before passing it to any dolt query helper.","Never accept raw user input as a table name; map user choices to a fixed allowlist of known table names.","If the name comes from configuration, validate the config value at load time against the same pattern."],"exampleFix":"// before\ntable := r.URL.Query().Get(\"table\") // \"issues; DROP TABLE x\"\nstore.QueryHistory(ctx, id, table)\n// after\nvar allowed = map[string]bool{\"issues\": true, \"wisps\": true}\ntable := r.URL.Query().Get(\"table\")\nif !allowed[table] { return fmt.Errorf(\"unknown table\") }\nstore.QueryHistory(ctx, id, table)","handlingStrategy":"validation","validationCode":"var tablePattern = regexp.MustCompile(`^[A-Za-z0-9_]+$`)\nfunc validTableName(t string) bool { return tablePattern.MatchString(t) }\nif !validTableName(table) { return fmt.Errorf(\"table name %q contains invalid characters\", table) }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never feed user input directly into table names; map user choices to an allowlist of known tables.","Validate table names from config at load time with the same pattern the library uses.","Keep table-name construction in one audited helper."],"tags":["validation","sql-injection-prevention","table-name","dolt"],"backgroundTag":"invalid-identifier-name","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}