fix: escape policy role names and function/procedure names in generated SQL#296
Merged
Merged
Conversation
ca79e35 to
ee02c49
Compare
alexaub-stripe
requested changes
Jul 3, 2026
alexaub-stripe
left a comment
Collaborator
There was a problem hiding this comment.
Looks great! One minor question
Two additional SQL injection sinks identified via the same root cause as the enum label fix (#295): schema-derived values re-emitted into DDL without proper escaping. 1. Policy role names (policy_sql_generator.go): AppliesTo role names from pg_roles.rolname were interpolated raw into CREATE/ALTER POLICY ... TO statements. A user with CREATEROLE could create a role with an embedded double-quote to inject arbitrary SQL. Fixed by adding escapeRoleNames() that applies EscapeIdentifier to each role, preserving PUBLIC as an unquoted SQL keyword (matching the existing pattern in privilege_sql_generator.go). 2. Function/procedure names (schema.go buildProcName): The function used hand-rolled quoting with fmt.Sprintf that did not double embedded double-quotes, allowing a user with CREATE FUNCTION to inject SQL via function names containing double-quote characters. Fixed by replacing the hand-rolled quoting with EscapeIdentifier. Includes unit tests, acceptance tests against a live PostgreSQL instance, and verified failure evidence when the fix is reverted. Co-authored-by: Cursor <cursoragent@cursor.com> Committed-By-Agent: cursor Co-authored-by: Cursor <cursoragent@cursor.com> Committed-By-Agent: cursor Co-authored-by: Cursor <cursoragent@cursor.com> Committed-By-Agent: cursor
ee02c49 to
d93d417
Compare
alexaub-stripe
approved these changes
Jul 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two additional SQL injection sinks identified via the same root cause as #295 (enum labels). Schema-derived values were re-emitted into generated DDL without proper escaping.
1. Policy role names (
policy_sql_generator.go)AppliesTorole names (sourced frompg_roles.rolname) were interpolated raw intoCREATE POLICY ... TOandALTER POLICY ... TOstatements. A user withCREATEROLEprivilege could plant a role whose name contains an embedded double-quote to inject arbitrary SQL during plan execution.Fix: Added
escapeRoleNames()helper that appliesEscapeIdentifierto each role name, preservingPUBLICas an unquoted SQL keyword (matching the existing pattern inprivilege_sql_generator.go).2. Function/procedure names (
schema.gobuildProcName)The function used hand-rolled quoting (
fmt.Sprintf("\"%s\"(%s)", name, ...)) that did not double embedded double-quotes. A user withCREATE FUNCTIONprivilege could create a function with"in its name to inject SQL whenDROP FUNCTION/DROP PROCEDUREstatements are generated.Fix: Replaced the hand-rolled quoting with
EscapeIdentifier(name).Evidence of vulnerability without fix
buildProcName (function names)
When the fix is reverted,
buildProcNameproduces broken/injectable identifiers:Policy role names
When the fix is reverted, role names are emitted raw:
Evidence of all acceptance tests passing with fix
All 50 policy and function acceptance tests pass against a live PostgreSQL 14 instance (Docker, with
-race):Test plan
buildProcNamewith normal, double-quote, and injection payloadsescapeRoleNamesincluding PUBLIC keyword handlingpolicySQLGenerator.Addverifying escaped role output