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

  1. 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.
  2. Strip schema qualification before validation and pass each part separately (schema via a different mechanism), e.g. "public.users" -> "users".
  3. Sanitize dynamic input: reject or transform names containing spaces, dashes, dots, or leading digits before they reach templates.
  4. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/5fa5d0e4e0dba8ed. Report an issue: GitHub.