Skip to content

Use raw role names verbatim for worker connections - #364

Merged
Pino de Candia (pinodeca) merged 1 commit into
microsoft:mainfrom
pinodeca:fix/verbatim-worker-role-name
Aug 29, 2026
Merged

Use raw role names verbatim for worker connections#364
Pino de Candia (pinodeca) merged 1 commit into
microsoft:mainfrom
pinodeca:fix/verbatim-worker-role-name

Conversation

@pinodeca

Copy link
Copy Markdown
Contributor

Summary

  • pass the exact catalog role name to the PostgreSQL connection builder
  • remove SQL-identifier normalization from the wire-protocol username path
  • preserve current host and application-name connection behavior
  • add negative coverage for quote-wrapped role names that resemble a superuser
  • add positive coverage proving a literal quote-wrapped role executes as itself
  • update the unreleased changelog

Security impact

Catalog role names are literal strings, not quoted SQL identifiers. Reinterpreting a quote-wrapped name could collapse it onto a distinct login role and make the worker execute under the wrong identity.

Validation

  • cargo fmt -p pg_durable -- --check
  • cargo check --features pg17
  • cargo clippy --features pg17 -- -D warnings
  • ./scripts/test-e2e-local.sh --verbose 17_superuser_guc (passed)
  • ./scripts/test-e2e-local.sh --verbose 49_quoted_role_names (passed)

Pass the pg_roles.rolname value directly to the connection builder instead of reinterpreting it as a quoted SQL identifier. This keeps quote-wrapped catalog names from collapsing onto a different login role.

Cover both the rejected superuser-identity substitution and successful execution as a literal quote-wrapped role.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens pg_durable’s worker connection authentication by ensuring the background worker passes catalog role names (rolname) verbatim to the libpq/sqlx connection builder, avoiding any reinterpretation of quote-wrapped names as SQL identifiers (which could otherwise collapse onto a different login role).

Changes:

  • Remove SQL-identifier-style “unquoting/normalization” from the worker connection username path and pass the role name through unchanged.
  • Extend E2E coverage for quote-sensitive role-name edge cases, including a boundary case where a role name literally includes quote bytes.
  • Update the unreleased changelog to document the security-relevant fix.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
src/types.rs Stops normalizing role names for connections; passes user directly into PgConnectOptions::username.
tests/e2e/sql/49_quoted_role_names.sql Adds a boundary regression ensuring a role named "quoteedge" (quotes included) runs as itself, not as quoteedge.
tests/e2e/sql/17_superuser_guc.sql Adds a negative test that a quoted catalog role resembling postgres cannot authenticate as the real superuser.
CHANGELOG.md Documents the fix under the unreleased version.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@pinodeca Pino de Candia (pinodeca) left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One test reliability issue found. The production change is directionally correct and the positive current_user assertion covers the central quote-wrapped-role regression, but the new negative superuser test has a race that can yield a false pass.

Comment on lines +369 to +394
SET SESSION AUTHORIZATION df_e2e_user;
INSERT INTO _su_guc_t6
SELECT df.start(
'INSERT INTO su_guc_quoted_role_sentinel VALUES (''unexpected superuser identity'')',
'su-guc-test6-quoted-role-identity'
);
RESET SESSION AUTHORIZATION;

SET SESSION AUTHORIZATION su_guc_forger;
DO $forge_quoted_role$
DECLARE
inst TEXT;
BEGIN
SELECT instance_id INTO inst FROM _su_guc_t6;
UPDATE df.instances
SET submitted_by = (
SELECT oid::regrole FROM pg_roles WHERE rolname = chr(34) || 'postgres' || chr(34)
)
WHERE id = inst;
UPDATE df.nodes
SET submitted_by = (
SELECT oid::regrole FROM pg_roles WHERE rolname = chr(34) || 'postgres' || chr(34)
)
WHERE instance_id = inst;
END $forge_quoted_role$;
RESET SESSION AUTHORIZATION;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 — Make submission and forgery atomic. df.start() commits before these updates, so the worker can load or execute the graph as the original df_e2e_user first. That role also cannot insert into su_guc_quoted_role_sentinel, producing exactly the asserted failed status and empty sentinel; the test can therefore pass without exercising the quoted-role connection path. Please wrap submission plus both identity updates in one explicit transaction so the worker cannot observe the instance until the forged identity is committed. It would also help to assert the failure detail identifies authentication of the literal quoted role rather than accepting any failure.

@pinodeca
Pino de Candia (pinodeca) merged commit b4b80c5 into microsoft:main Aug 29, 2026
7 checks passed
@pinodeca
Pino de Candia (pinodeca) deleted the fix/verbatim-worker-role-name branch August 29, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants