Skip to content

fix(credentials): capture the correct provider identity on connect and rotate - #6201

Open
waleedlatif1 wants to merge 2 commits into
stagingfrom
customer-report-triage
Open

fix(credentials): capture the correct provider identity on connect and rotate#6201
waleedlatif1 wants to merge 2 commits into
stagingfrom
customer-report-triage

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Attio OAuth recorded an arbitrary workspace member instead of the authorizing user — data[0] off an unordered workspace-wide list. The wrong member id landed in account.accountId, and the stale-sibling dedupe keys on that prefix, so two Attio members connecting under one Sim user silently collapsed into one account row. Now resolves via /v2/self/v2/workspace_members/{id}; no new scopes
  • Notion read profile.person.email, which never exists on a bot token, so every Notion connect fell through to a synthetic address. Fixed the traversal to bot.owner.user.person.email and the name precedence (a bot's top-level name is always the integration's)
  • Synthetic connector emails were minted on live third-party domains (@x.com, @hubspot.com, @salesforce.com, @atlassian.com). All 25 now route through one documented helper on .invalid (RFC 2606). Better Auth 1.6.23 hard-rejects a falsy email, so the placeholders are required — the helper explains why and when they can go
  • Google service-account key rotation re-encrypted the new key but never updated displayName, leaving the credential permanently labeled with the old client_email. Root cause was a hand-rolled branch bypassing the shared builder; deleting it also fixed rotation dropping audit metadata
  • Box and Salesforce identity lookups failed silently — no logger in either file — so a degraded connect was indistinguishable from a healthy one
  • Service-account principals are now one ServiceAccountPrincipal union (user / tenant / lookup_failed / null), required-and-nullable so a new provider can't compile without deciding, mirrored centrally into both audit and stored metadata. tenant exists because Attio, Shopify, Webflow, Zoom and Zoho Desk expose no actor at all; lookup_failed finally distinguishes "the lookup broke" from "this provider has no identity"

Type of Change

  • Bug fix

Testing

502 tests pass across the touched suites (3554/3555 repo-wide; the one failure is a pre-existing environmental rg-on-PATH issue in executor/handlers/pi, untouched here). Typecheck, check:api-validation and biome clean.

Every behavioral change was verified against the vendor's official docs. Not live-tested against real provider accounts — the Attio two-call flow and the Notion traversal each deserve one real connect before this goes out. Notion's fix is also inert until "user information with email addresses" is enabled in our Notion developer portal, which forces re-authorization and should be a separate decision.

Note for release: provider-named audit keys (airtableUserId, trelloMemberId, …) are replaced by uniform principalId/principalKind/principalLabel. Historical rows keep the old keys, so any external drain filtering on them needs updating.

Known limitations

  • The Google/Slack re-label is inert through the normal reconnect UI. The reconnect modals prefill displayName and send it, which skips the re-derivation — it only fires if the user clears the name field first. The audit-metadata half of that fix does run on every rotation, so a rotated credential's new principal is always recorded; the stale label mostly persists. Worth a follow-up on the modals.
  • principalLabel puts email addresses into audit_log.metadata, where previously only opaque ids lived. A deliberate data-classification change, not just a key rename.
  • Salesforce identity depends on the customer's Connected App scopes. The client-credentials minter sends no scope parameter, so if the app lacks openid the userinfo lookup can 403 and the run-as user degrades to lookup_failed. Non-fatal — the connection still works. Connect help text now says so.

Verification notes

Every behavioral change was checked against vendor docs, and the three least-certain claims were independently re-verified: Salesforce's sub really is documented as the UserInfo endpoint URL (so using user_id is correct), Attio's authorized_by_workspace_member_id is documented required for active tokens and this code path only ever sees user-authorized OAuth tokens, and Box's id is a required property of User--Base in Box's own OpenAPI spec.

Still not live-tested against real provider accounts — a real Attio connect and a real Notion connect are the remaining gap.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…d rotate

Attio OAuth recorded an arbitrary workspace member instead of the authorizing
user, so two members connecting under one Sim user collapsed into a single
account row via the stale-sibling dedupe. Notion read `profile.person.email`,
which never exists on a bot token. Synthetic connector emails were minted on
live third-party domains. Google service-account rotation left the credential
labeled with the old key's client_email and skipped audit metadata entirely.
Box and Salesforce identity lookups failed silently with no logger in either
file.

Service-account principals are now a single ServiceAccountPrincipal union
(user / tenant / lookup_failed / null) mirrored centrally into both audit and
stored metadata, so a principal can no longer be captured and forgotten, and
"which account is this credential?" is answerable from SQL.
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 3, 2026 5:03am

Request Review

@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches auth OAuth identity, credential encryption/audit metadata, and connect/rotate paths across many providers; audit key shape changes (principalId vs legacy provider-specific ids) affect downstream consumers.

Overview
Fixes wrong or missing actor identity when connecting OAuth and service-account credentials, and makes rotation update labels and audit data consistently.

OAuth connector callbacks now resolve the real authorizing user where APIs allow it (e.g. Attio via /v2/self then member fetch; Notion via bot.owner.user). When providers omit email, placeholders go through syntheticConnectorEmail on @connectors.sim.invalid instead of live third-party domains.

Service accounts adopt a required ServiceAccountPrincipal (user / tenant / lookup_failed / none), flattened to uniform principalKind / principalId / principalLabel in audit and encrypted blob metadata across token validators, client-credential minters, and verifyAndBuildServiceAccountSecret. Box/Salesforce identity lookups log failures and record lookup_failed instead of silently pretending no principal exists.

Credential update/create audit spreads provider metadata first so route keys cannot be shadowed. Google/Slack reconnect re-derives displayName when the stored label still matches the old principal; custom names are preserved. Shopify validation no longer treats partial-scope GraphQL errors as invalid tokens when shop is present.

Reviewed by Cursor Bugbot for commit 416af7c. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR standardizes connector and service-account identity capture while correcting Attio, Notion, and credential-rotation behavior.

  • Resolves Attio and Notion identities from provider-specific authorizing-user data.
  • Replaces synthetic emails on live domains with centralized .invalid placeholders.
  • Introduces uniform user, tenant, lookup-failure, and absent principal metadata.
  • Routes service-account rotation through the shared verification and secret-building path.
  • Adds identity lookup diagnostics and expands affected credential tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains in the eligible follow-up review scope.

Important Files Changed

Filename Overview
apps/sim/lib/auth/auth.ts Corrects provider identity traversal and adopts safe synthetic connector emails while preserving session-bound linking.
apps/sim/lib/auth/connector-email.ts Centralizes deterministic placeholder addresses on the reserved .invalid domain.
apps/sim/lib/credentials/principal.ts Defines and serializes the uniform required-and-nullable service-account principal model.
apps/sim/lib/credentials/service-account-secret.ts Centralizes principal metadata in encrypted credential blobs and audit metadata for all builder variants.
apps/sim/lib/credentials/orchestration/index.ts Rebuilds rotated credentials through shared verification while preserving explicit display-name precedence and audit authority.
apps/sim/lib/credentials/client-credential-accounts/minters/box.ts Captures the Box service-account user and records identity lookup failures without rejecting token minting.
apps/sim/lib/credentials/client-credential-accounts/minters/salesforce.ts Captures the Salesforce run-as user and distinguishes unavailable identity information from tenant-only credentials.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Connect or rotate credential] --> B[Verify provider secret]
  B --> C[Resolve user or tenant identity]
  C --> D[Build principal metadata]
  D --> E[Encrypt credential blob]
  D --> F[Write audit metadata]
  E --> G[Store credential]
Loading

Reviews (2): Last reviewed commit: "fix(credentials): keep the provider-repo..." | Re-trigger Greptile

…p degrades

Box and Salesforce returned early on a missing user id, discarding a `name` or
`login` the response did carry and relabeling the credential to the enterprise
or host fallback. Only the principal should degrade; the human label still
beats an id-derived string.

Also notes the Salesforce `openid` scope in the connect help text. The client
credentials minter sends no scope parameter — effective scopes come from the
customer's Connected App — so without `openid` the userinfo lookup can 403 and
the run-as user silently never reaches the audit record.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Second commit bd79ba1f8e landed after the first review pass — Greptile and Bugbot both verdicted against 416af7c57f and did not re-trigger, so requesting a fresh look at the current head.

Scope is small: Box and Salesforce were returning early on a missing user id and discarding a name/login the response did carry, relabeling the credential to the enterprise/host fallback. Now only the principal degrades and the human label is kept. Plus a help-text line noting that the Salesforce client-credentials minter sends no scope parameter, so without openid on the customer's Connected App the run-as user never reaches the audit record.

@greptileai review

bugbot run

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.

1 participant