From c08b04adbc1606246895a61a00a2085625d4b5b1 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 24 Jun 2026 14:27:23 -0500 Subject: [PATCH] feat: escape composite-literal fields in NameOrganizationPair (#26675) --- coderd/database/types.go | 24 +++++++++++++++++++++++- coderd/idpsync/role_test.go | 3 +++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/coderd/database/types.go b/coderd/database/types.go index e0ab43b9ff..f543288c04 100644 --- a/coderd/database/types.go +++ b/coderd/database/types.go @@ -294,7 +294,29 @@ func (*NameOrganizationPair) Scan(_ interface{}) error { // // SELECT ARRAY[('customrole'::text,'ece79dac-926e-44ca-9790-2ff7c5eb6e0c'::uuid)]; func (a NameOrganizationPair) Value() (driver.Value, error) { - return fmt.Sprintf(`(%s,%s)`, a.Name, a.OrganizationID.String()), nil + // The string values must be escaped in case there are special characters, quotes, etc. + // 'NameOrganizationPair' is a composite value, which has no driver handler + // in the `pq` package. + // + // pq.StringArray formats the single name as `{""}`. Strip + // the outer braces to get the quoted+escaped form that composite + // literal syntax accepts unchanged. + // + // Ideally `appendArrayQuotedBytes` would be exported, and we could call + // it directly. + v, err := (&pq.StringArray{a.Name}).Value() + if err != nil { + return nil, err + } + + s, ok := v.(string) + if !ok { + return nil, xerrors.Errorf("unexpected type %T", v) + } + + stripCurlyBraces := s[1 : len(s)-1] + + return fmt.Sprintf("(%s,%s)", stripCurlyBraces, a.OrganizationID.String()), nil } // AgentIDNamePair is used as a result tuple for workspace and agent rows. diff --git a/coderd/idpsync/role_test.go b/coderd/idpsync/role_test.go index 6ec082d4e7..421a19c051 100644 --- a/coderd/idpsync/role_test.go +++ b/coderd/idpsync/role_test.go @@ -31,6 +31,9 @@ func TestRoleSyncTable(t *testing.T) { "foo", "bar", "baz", "create-bar", "create-baz", "legacy-bar", rbac.RoleOrgAuditor(), + // Some arbitrary values to attempt to trip up the SQL in the matching. + "Role with (Special Characters)", + "NULL", }, // bad-claim is a number, and will fail any role sync "bad-claim": 100,