Skip to content

Silent Data Corruption via Unescaped Enum Values in Generated Go Code #4448

@Kr1shna4garwal

Description

@Kr1shna4garwal

Version

1.31.1

What happened?

Summary

sqlc v1.31.1 and earlier versions generate Go string constants from PostgreSQL enum values without applying Go string escaping. Enum values containing backslash characters are reinterpreted as Go escape sequences, producing constants whose runtime values silently differ from the corresponding database values. Enum values containing double-quote characters produce Go string concatenation expressions that compile successfully but evaluate to incorrect values.

Details:

The Go code generation template at internal/codegen/golang/templates/template.tmpl interpolates enum values using:

{{.Name}} {{$enumName}} = "{{.Value}}"

The .Value field comes from internal/codegen/golang/result.go, which passes the raw SQL enum string without transformation:

Value: v,

This creates three verified impacts:

Silent semantic corruption: PostgreSQL enum value user\nadmin (literal backslash + n) generates Go constant "user\nadmin" (newline character). The 12-byte DB value becomes a 10-byte Go constant. Comparisons silently fail.

Build disruption: Enum values containing " cause go/format.Source() to reject the generated code, breaking sqlc generate.

Constant value manipulation: Enum values like injected" + "arbitrary_go_code" + " produce valid Go string concatenation that compiles with a different value.

Both SQL file parsing and database introspection (database.uri) paths are affected.

schema.sql:

CREATE TYPE user_role AS ENUM (
    'admin',
    'user',
    'readonly',
    'user\nadmin'
);

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    role user_role NOT NULL DEFAULT 'user'
);

query.sql:

-- name: GetUser :one
SELECT id, name, role FROM users WHERE id = $1;

sqlc.yaml:

version: "2"
sql:
  - engine: "postgresql"
    queries: "query.sql"
    schema: "schema.sql"
    gen:
      go:
        package: "authdb"
        out: "go"

Run sqlc generate. The generated go/models.go contains:

const (
    UserRoleAdmin      UserRole = "admin"
    UserRoleUser       UserRole = "user"
    UserRoleReadonly   UserRole = "readonly"
    UserRoleUsernadmin UserRole = "user\nadmin"  // \n interpreted as newline by Go
)

Verification:

package main

import "fmt"

func main() {
    goConstant := "user\nadmin"   // what sqlc generated (10 bytes, newline)
    dbValue := "user\\nadmin"     // what PostgreSQL stores (11 bytes, literal backslash)
    fmt.Printf("Go: %d bytes %q\n", len(goConstant), goConstant)
    fmt.Printf("DB: %d bytes %q\n", len(dbValue), dbValue)
    fmt.Printf("Match: %v\n", goConstant == dbValue)  // false
}

String concatenation variant. schema.sql:

CREATE TYPE injection AS ENUM (
    'safe_value',
    'injected" + "arbitrary_go_code" + "'
);

Generated output:

InjectionInjectedarbitraryGoCode Injection = "injected" + "arbitrary_go_code" + ""

Compiles as Go string concatenation. Constant value differs from DB value.

Impact

Silent data corruption in any Go application using sqlc with PostgreSQL enum types containing backslashes. Role checks, status comparisons, and other enum-based logic fail silently. In an authorization context, if user.Role == UserRoleUsernadmin never matches, potentially granting or denying access incorrectly.

Fix:

Replace "{{.Value}}" on template.tmpl line 95 with {{printf "%q" .Value}}. Go’s %q verb properly escapes backslashes, double quotes, and non-printable characters.

Relevant log output

Database schema

SQL queries

Configuration

Playground URL

No response

What operating system are you using?

macOS

What database engines are you using?

PostgreSQL

What type of code are you generating?

Go

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions