{"record":{"id":"5fa5d0e4e0dba8ed","repo":"ory/hydra","slug":"invalid-sql-identifier-s","errorCode":null,"errorMessage":"invalid SQL identifier '%s'","messagePattern":"invalid SQL identifier '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/popx/sql_template_funcs.go","lineNumber":20,"sourceCode":"// SPDX-License-Identifier: Apache-2.0\n\npackage popx\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/pkg/errors\"\n)\n\nvar SQLTemplateFuncs = map[string]interface{}{\n\t\"identifier\": Identifier,\n}\n\nvar identifierPattern = regexp.MustCompile(\"^[a-zA-Z][a-zA-Z0-9_]*$\")\n\nfunc Identifier(i string) (string, error) {\n\tif !identifierPattern.MatchString(i) {\n\t\treturn \"\", errors.Errorf(\"invalid SQL identifier '%s'\", i)\n\t}\n\treturn i, nil\n}\n","sourceCodeStart":2,"sourceCodeEnd":24,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/popx/sql_template_funcs.go#L2-L24","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nIdentifier(\"public.users\") // error: invalid SQL identifier 'public.users'\n\n// after\ntable := strings.TrimPrefix(name, \"public.\")\nIdentifier(table) // \"users\"\n// or rename the column: my-table -> my_table","handlingStrategy":"validation","validationCode":"var identRe = regexp.MustCompile(\"^[a-zA-Z][a-zA-Z0-9_]*$\")\nfunc safeIdentifier(s string) error {\n    if !identRe.MatchString(s) {\n        return fmt.Errorf(\"unsafe SQL identifier: %q\", s)\n    }\n    return nil\n}\n// call safeIdentifier(tableName) before building the query","typeGuard":"func isSQLIdentifier(s string) bool {\n    if s == \"\" { return false }\n    for i, r := range s {\n        ok := (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (i > 0 && r >= '0' && r <= '9') || (i > 0 && r == '_')\n        if !ok { return false }\n    }\n    return true\n}","tryCatchPattern":"ident, err := popx.Identifier(name)\nif err != nil {\n    return fmt.Errorf(\"refusing to use table name %q: %w\", name, err)\n}","preventionTips":["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."],"tags":["sql","validation","security","injection"],"backgroundTag":"invalid-sql-identifier","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}