ory/hydra · error
invalid SQL identifier '%s'
Error message
invalid SQL identifier '%s'
What it means
Identifier validates that a string is a safe SQL identifier: it must start with a letter and contain only letters, digits, and underscores (pattern ^[a-zA-Z][a-zA-Z0-9_]*$). The library refuses anything else so interpolated identifiers cannot be used for SQL injection. Any identifier used in a SQL template must pass this check.
Source
Thrown at oryx/popx/sql_template_funcs.go:20
// SPDX-License-Identifier: Apache-2.0
package popx
import (
"regexp"
"github.com/pkg/errors"
)
var SQLTemplateFuncs = map[string]interface{}{
"identifier": Identifier,
}
var identifierPattern = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_]*$")
func Identifier(i string) (string, error) {
if !identifierPattern.MatchString(i) {
return "", errors.Errorf("invalid SQL identifier '%s'", i)
}
return i, nil
}
View on GitHub (pinned to 4174065ffb)
Solutions
- Rename the table/column to only [a-zA-Z0-9_] characters starting with a letter, or pre-compute the quoted form in your own code if the library rejects valid-but-exotic names.
- Strip schema qualification before validation and pass each part separately (schema via a different mechanism), e.g. "public.users" -> "users".
- Sanitize dynamic input: reject or transform names containing spaces, dashes, dots, or leading digits before they reach templates.
- If the name is static and safe but fails the pattern, hardcode it in the template instead of passing it through Identifier.
Example fix
// before
Identifier("public.users") // error: invalid SQL identifier 'public.users'
// after
table := strings.TrimPrefix(name, "public.")
Identifier(table) // "users"
// or rename the column: my-table -> my_table Defensive patterns
Strategy: validation
Validate before calling
var identRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_]*$")
func safeIdentifier(s string) error {
if !identRe.MatchString(s) {
return fmt.Errorf("unsafe SQL identifier: %q", s)
}
return nil
}
// call safeIdentifier(tableName) before building the query Type guard
func isSQLIdentifier(s string) bool {
if s == "" { return false }
for i, r := range s {
ok := (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (i > 0 && r >= '0' && r <= '9') || (i > 0 && r == '_')
if !ok { return false }
}
return true
} Try / catch
ident, err := popx.Identifier(name)
if err != nil {
return fmt.Errorf("refusing to use table name %q: %w", name, err)
} Prevention
- Constrain table/column names in migrations to letters, digits, and underscores starting with a letter.
- Never pass raw user input as an identifier; validate or map to an allowlist first.
- Strip schema prefixes ("public.") before validating, and validate each part separately.
- Add unit tests that assert dynamic identifier construction rejects exotic names.
When it happens
Trigger: Passing an empty string, a string with spaces/hyphens/dots/quotes (e.g. "users.name", "my-table", `"quoted"`), a leading digit ("1st_col"), or a reserved character sequence into Identifier — typically from pop SQL template helper calls ({{identifier ...}}) where a dynamic table/column name is inserted.
Common situations: Table or column names defined with hyphens or camelCase-with-prefixes in the schema; schema-qualified names like "public.users" passed whole; identifiers built by concatenation that accidentally include whitespace; user-supplied names that contain characters the regex rejects.
Related errors
- migration %s has no corresponding down migration
- global secret is too short
- Token is expired
- Token used before issued
- Token is not valid yet
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/5fa5d0e4e0dba8ed.
Report an issue: GitHub.