Repository navigation
fix(release): keyless subject uniqueness, NO_PROXY IPv6/CIDR, proxied boot secret, and remaining audit follow-ups - #9866
Merged
Conversation
…ready holds A keyless subject proof stays valid for 24 hours and is not bound to one credential, so a member removed from a keyless AWS or Databricks credential could save a second credential with the same subject and proof and keep assuming the customer's role. Creating or reconnecting a credential onto a subject it did not already hold now refuses (409 conflict) when another non-revoked credential of the same owner holds it. The subject lives only in the encrypted secret, so the check decrypts the owner's keyless credentials inside the write transaction, under a per-subject advisory lock so concurrent saves serialize. A reconnect keeping its stored subject binds nothing new and skips the check.
…ntialPicker credentialSuppliedParamsFor matched any oauth-input feeding oauthCredential, without the credentialKind === 'service-account' check isSavedCredentialPicker applies. It now uses that predicate. No registered block changes: every block whose loose-match picker lacks the service-account kind has no tool that declares credentialSuppliedParams.
The Secrets Manager client used the SDK's Node handler, which ignores HTTPS_PROXY, so a container whose only egress is a proxy could not load its env and never booted. Under Bun with a proxy set, send through Bun's fetch, which honors HTTPS_PROXY/HTTP_PROXY/NO_PROXY. apps/sim's awsRequestHandler is out of reach for packages and the realtime bootstrap needs the same path.
… default NVIDIA's model page states the nvidia/nemotron-3-super-120b-a12b API is no longer supported after 2026-10-19, and Pi 1.0.1 dropped it as its NVIDIA default for the same reason. Mark it deprecated (hidden from pickers, still a known id for pinned workflows) and default the NVIDIA provider to Nemotron 3 Ultra, which the Pi catalog also serves. The Mistral IDs Pi 1.5 dropped (mistral-large-2411, devstral-small-2507, devstral-medium-2507) were retired by Mistral on 2026-05-31 and are already deprecated here, so saved Pi blocks fail before credentials with the choose-a-model error instead of being mapped onto a different model.
…ck helper and refuse on unreadable siblings assertWorkloadIdentitySubjectUnclaimed now takes a DbTransaction and locks through acquireAdvisoryXactLock, so the lock carries its SQLCommenter tag and cannot be taken on the pool by mistake. A sibling keyless credential whose secret does not decrypt or parse used to throw a generic error, failing every keyless create or rotation for the owner with a 500. Since it might hold the subject, the save is now refused as a conflict naming that credential, with a warning logged, and stored subjects are read with a schema safeParse.
…nd CIDR A bare IPv6 entry such as ::1 was read as host ':' port 1 and never matched, IP entries only matched their exact spelling, and CIDR ranges were ignored. Entries now follow curl: domains cover their subdomains, bracketed IPv6 may carry a port, IP entries match any spelling, and CIDR ranges match IP-literal destinations. NO_PROXY is parsed once per distinct value. The comments and the self-hosting networking page describe these rules, and note that Bun's default fetch applies its own.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
[High impact] The PR appears safe to merge; no new actionable issue remains. SummaryAdds checks that stop AWS and Databricks credentials from sharing a workload-identity subject.
No new actionable issues were found.
|
…s reconnect A reconnect that kept its subject skipped the uniqueness check, but whether it kept the subject was decided from the secret read before provider verification. If another reconnect moved the credential to a new subject and a second credential claimed the old one meanwhile, the stale reconnect wrote the old subject back unchecked and two credentials held it. Every keyless reconnect now locks the subject it is writing, then the credential row (FOR UPDATE), re-reads the row's current subject, and runs the sibling check whenever the two differ, with the decision and the write in one transaction.
Bun's fetch picks a proxy by scheme only, so with just HTTP_PROXY set the https Secrets Manager request went direct and a proxy-only container still could not boot. Move proxy selection (HTTPS_PROXY falling back to HTTP_PROXY, curl-style NO_PROXY) from apps/sim into @sim/security/environment-proxy, and have runtime-secrets pass the selected proxy to Bun's fetch explicitly, or send through the Node handler when NO_PROXY excludes the endpoint.
Manual runs need api:write, which read-only personal keys lack, workspace write access, and a personal key or OAuth token; workspace API keys are refused. Regenerate the OpenAPI document and the MCP operation catalog.
Collaborator
Author
Collaborator
Author
|
@cubic-dev-ai review this PR |
Contributor
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 29 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
6 tasks done
waleedlatif1
added a commit
that referenced
this pull request
Nov 11, 2026
…eyless, GitHub and Bitbucket tokens, DocuSign JWT (#9869) * feat(credentials): keyless Snowflake connections through workload identity federation A Snowflake connection can now trust Sim's OIDC issuer instead of holding a programmatic access token. Sim signs a short-lived token (audience snowflakecomputing.com) for the credential's own random subject; the customer creates a SERVICE user with WORKLOAD_IDENTITY = (TYPE = OIDC ISSUER SUBJECT). - Save verifies by minting a token and running the identity statement at the pinned account host through the guarded egress path; Snowflake's refusals keep the shared codes (invalid_credentials, site_not_found, provider_unavailable). Subjects bind to their creator like AWS/Databricks. - Runtime: the resolver returns authStyle 'snowflake-workload-identity' for a keyless credential, and the tools and selectors send Bearer WIF.OIDC.<token> with the WORKLOAD_IDENTITY_FEDERATION token type. authStyle and domain are authoritative on every Snowflake tool, so a caller can no longer choose the token type or steer the account host of a saved credential. - PAT credentials resolve and verify exactly as before. - Connect dialog offers the keyless service user with the CREATE USER statement; it falls back to the token dialog where keyless is unavailable. - Docs: Snowflake connections page with keyless setup, troubleshooting, and the 60-minute Snowflake key cache on signing-key rotation. * fix(snowflake): run Cortex Analyst SQL with the resolved credential and reopen keyless reconnects as keyless Cortex Analyst's "run the SQL" went through a nested snowflake_execute_sql call carrying the already-resolved token and host. With authStyle and domain now authoritative, the executor cleared both before the nested call, so it failed for every credential (and a keyless token would have been sent as a PAT). The generated SQL now runs as one guarded statement request built from the parent call's credential-derived host, token, and token type, through the same requestTarget egress the tool transport uses, with redirects refused. The Snowflake connect dialog opened every reconnect on the programmatic access token. The workload-identity prepare call now takes the reconnected credential and reports whether it is keyless (only to an admin of that credential in the same workspace), and the dialog opens on that mode. Docs: the 60-minute wait applies before signing with a new key; removing the old key needs 10 minutes with keyless Snowflake connections. * fix(snowflake): cancel Cortex Analyst's generated SQL with the run The generated SQL used to run as a nested tool call, which inherited the run's abort signal; running it directly dropped that. postProcess now receives the executor's effective abort signal as an optional fourth argument (the same signal the tool's own request used), and Cortex Analyst forwards it to the DNS check, the guarded fetch, and the response read. Other tools' postProcess hooks are unchanged. * feat(credentials): saved GitHub personal access tokens and Bitbucket access tokens GitHub: a saved personal access token credential (fine-grained or classic), verified against api.github.com/user with the tools' own headers. GitHub's workflow block authenticates with an inline token rather than OAuth, so it follows the token-block pattern: github_v3 is the credential-only successor of github_v2, which stays exactly as it was, marked legacy and hidden from the toolbar. Every GitHub tool declares the optional saved-token policy; inline tokens keep working. The GitHub App installation credential (Search) is a separate service and is untouched: the Assistant still projects GitHub tools onto the person's connected GitHub account, and Search policy now follows the visible GitHub block (github_v3). Bitbucket: a workspace, project, or repository access token as a second option next to OAuth (the Linear pattern), verified by listing one repository of the typed workspace on api.bitbucket.org; the slug is validated so it cannot steer the path. List Workspaces needs a signed-in user, so it now refuses a saved access token before any request. Also: isHostNotFound no longer lets an outer error code mask ENOTFOUND on its cause. * fix(credentials): close wave 2a review findings on GitHub and Bitbucket tokens - The GitHub token service is `github-token`, so it no longer shares an id with the GitHub sign-in provider: parseProvider and getProviderIdFromServiceId answer for sign-in exactly as before, and a token-only service is never marked connected from a sign-in account whose id prefixes it. - Suggested connect rows offer each integration once and count it connected when any of its services' OAuth or service-account credentials is saved (GitHub's Search App and saved tokens share one integration). - Assistant GitHub calls bind through the connected-account policy the Assistant projects (the person's github-repositories OAuth account) in the token resolver too, on both the ordinary and managed paths; workflow runs and saved workspace tokens are unaffected. - Bitbucket verification reads one repository (`workspace/repository`), the request Atlassian documents for workspace, project, and repository access tokens alike; listing a workspace is not documented for repository tokens. - prepare-workload-identity test uses the central credentials-access mock. * feat(credentials): DocuSign JWT Grant credentials next to OAuth A saved DocuSign credential that authenticates with JWT Grant (an integration key, an impersonated user ID, the app's RSA private key, and an environment), offered as a second option beside the existing OAuth connection, which is unchanged. The minter signs an RS256 assertion (iss = integration key, sub = user, aud = the environment's authentication host without a scheme, scope "signature impersonation"), exchanges it at that host's /oauth/token, then reads /oauth/userinfo on the same host to bind the pinned account (or the user's default) and returns its eSignature REST base. Both hosts come only from the stored environment; a base_uri outside that environment's docusign.net shape is never trusted with the token. GUIDs and the key are checked before any request, and tokens are re-minted about 15 minutes before they expire, as DocuSign advises. DocuSign tools take the account base as an authoritative credential param, so a caller can never supply it, and DocuSignClient skips its deployment-host userinfo lookup when one is bound. OAuth tokens resolve exactly as before. Save-time failures map to consent_required (the connect modal shows the exact consent URL for the key and environment) and account_not_found (a pinned account the user does not belong to), alongside the shared invalid_credentials and provider_unavailable codes. * fix(credentials): close wave 2b review findings on DocuSign JWT Grant - The DocuSign knowledge connector now honours a JWT Grant credential's bound account. The resolved token's instanceUrl reaches the connector as credentialInstanceUrl in the sync context (seeded on every path through syncContextForToken). The connector re-validates it with the same parser as DocuSignClient, lists and fetches from that account in the credential's environment, links envelopes to that environment's web app, and validates without asking the deployment host. A seeded value of the wrong shape fails closed. OAuth syncs are unchanged. - RSA keys under 2048 bits are refused as invalid_credentials with copy that names the size, instead of jose's raw TypeError. - A short expires_in no longer makes every call re-mint: the cached lifetime floors at six minutes while staying a minute under the real expiry. - The connect modal shows the consent URL as a link with a copy button, and account_not_found says "pinned account" only when one was entered. * feat(credentials): Microsoft Entra app-only credentials for Entra ID, SharePoint, Dataverse, and Dynamics 365 A saved Microsoft app credential (OAuth 2.0 client credentials) as an additional option next to Microsoft OAuth, which is unchanged. One app registration serves the four services whose tools run without a signed-in user; Outlook, OneDrive, Excel, Word, Planner, and Teams never offer it and token resolution refuses it for them (provider mismatch). - Modes: client secret; certificate (PS256 client assertion with the certificate's x5t#S256 thumbprint, aud = the tenant token endpoint, 5-minute lifetime; key/certificate pairing, RSA size, and expiry checked before any request); keyless federated credential (a Sim workload-identity token for the credential's own subject, audience api://AzureADTokenExchange, subject bound to the saving user, refused if the app also accepts an unheld subject). - The token resource comes from the tool's service, passed from the server-validated tool policy: Graph for Entra ID and SharePoint, the stored environment for Dataverse and Dynamics 365. Any other service is refused. - The environment URL is stored on the credential (public-cloud Dynamics hosts, https root only). Dataverse tools take credentialType and instanceUrl as authoritative params; an app credential's request goes only to its own environment and a block environment that names another is refused. OAuth requests keep the block environment exactly as before. Dynamics 365 already matched instanceUrl against the block environment. - Save check: a Graph token reading the app's own service principal (no permission needed), plus Dataverse WhoAmI when an environment is set. Entra AADSTS codes map to actionable copy; runtime failures keep the shared invalid_credentials / provider_unavailable split. Tokens cache per credential, resource, and stored-secret version in an LRU. - Azure AD Reset password refuses app-only credentials: Graph does not support application permissions for resetPassword. - Connect dialog with tenant ID, client ID, environment URL, and the issuer/audience/subject to paste; docs page with per-mode setup. * fix(credentials): close wave 3 review findings on Microsoft app credentials - SharePoint selectors (workspace and organization paths) and knowledge connectors now pass the service they resolve for (the selector's own serviceId, the connector's declared auth provider), so a Microsoft app credential browses SharePoint with a Graph token instead of being refused. The resolution option is renamed from toolService to service; it is only ever set from server-side policy. - AADSTS700211 and 700212 are no longer mapped: Microsoft documents only 70021 and 700213 for federated credential mismatches. Other keyless refusals still name the mismatch. - DocuSign minter test mocks carry account_name, and the connector access-token test narrows its tokens with a guard instead of non-null assertions. * feat(credentials): keyless Google Cloud credentials via Workload Identity Federation A keyless mode of the existing Google service account credential (google-service-account) for Google Cloud APIs. JSON-key credentials, their connect flow, and their token minting are unchanged. - Flow: Sim signs a short-lived token for the credential's own subject with aud = the customer's provider resource name (https://iam.googleapis.com/projects/<number>/locations/global/ workloadIdentityPools/<pool>/providers/<provider>), exchanges it at sts.googleapis.com/v1/token (token-exchange, jwt subject token, cloud-platform scope, audience //iam.googleapis.com/...), then calls iamcredentials generateAccessToken for the service account with the tool's scopes and a 1h lifetime. Impersonation rather than direct federated access: some Cloud APIs reject federated tokens, and the service account keeps the same identity and roles a JSON key uses. - Inputs validated against Google's documented formats before any request: numeric project number, pool/provider IDs (4-32 [a-z0-9-], no gcp-), and a user-managed *.iam.gserviceaccount.com email, so nothing can steer the STS audience or IAM URL. Requests never follow redirects. - Scope: refuses domain-wide delegation (an impersonated user) and any service but BigQuery and Vertex AI before reading further or contacting Google, with a new shared unsupported_use code and copy pointing to a JSON key. Workspace blocks never offer the mode. - Save check: STS exchange, impersonation, and tokeninfo (no IAM role or enabled API needed), plus a probe that a subject no credential holds cannot impersonate the service account. Subject bound to the saving user with the signed proof; kept across reconnects. - STS/IAM failures map to actionable copy (provider settings, issuer reachability, the exact workloadIdentityUser binding) without Google's descriptions or any token; runtime keeps the invalid_credentials / provider_unavailable split. Tokens cache per credential, secret version, and scopes in an LRU. - Per-credential audiences: audiences.ts gains google without a fixed audience; the setup response omits audience for it and reports a keyless reconnect. Connect dialog shows the issuer, audience, subject, and filled-in gcloud commands (pool, OIDC provider with attribute condition on the subject, IAM binding); docs section added. * fix(credentials): close wave 4 review findings on keyless Google Cloud credentials - Drop the tokeninfo check: a token from generateAccessToken already proves the provider accepted Sim's token and the impersonation binding, and tokeninfo puts a live token in a query string. - Test that saving fails closed when the stray-subject pin probe hits an STS outage or an unreachable IAM endpoint. - Pass --project=<project number> on all three gcloud setup commands, drop the unused Resource Manager API from the docs, and raise the shared validation error for an empty scope list. * fix(credentials): keep Snowflake, Microsoft app, and Google keyless subjects unique The subject-uniqueness scan from #9866 covered only AWS and Databricks. It now also scans Snowflake, Microsoft app, and Google service account credentials, so no two live credentials of an owner hold one keyless subject. Their keyed secrets that carry no config (a Google JSON key, a Snowflake programmatic access token) hold no subject and no longer read as unreadable. The three keyless save paths report a newly bound subject so create checks it. * fix(credentials): close round-1 CI and review findings on alt auth - Knowledge connector tests expect the service now passed with Google service-account token requests; a new test proves a JSON-key mint signs the same request with or without it. - Re-record the tool-registry-boundary baseline: the keyless Snowflake, Microsoft app, and Google modules add 5 modules behind credential-service, which tipped credit-usage past its allowance on top of staging's drift. - Docs compatibility baseline: record the new code examples in the Google and Snowflake service account pages. - Generate the Microsoft certificate test fixture at run time instead of committing a PEM key and certificate. - A Microsoft app credential reconnect opens on keyless only when the stored credential is federated (the prepare step now reports it), like Snowflake; new connections still default to keyless. - TSDoc on the Google project number, pool/provider ID, and service account email validators. * fix(credentials): offer keyless Google setup only in a Google Cloud context Reconnecting a keyless Google service account from a Workspace integration (Gmail, Drive, ...) offered the keyless form, saving a credential that integration can never use. The dialog now offers keyless only for a Google Cloud service (new or reconnect) or on the credential's own page; from a Workspace integration it explains the credential works only with BigQuery and Vertex AI and does not save. The chat connect control passes its service so it gets the same gate. * test(credentials): match the stable part of the subject-in-use refusal
This branch was successfully deployed
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
Release-audit follow-ups that no other open follow-up PR (#9857–#9865) covers; coordinated with those owners so nothing is duplicated.
acquireAdvisoryXactLock; a reconnect that keeps its own subject is unaffected; an unreadable sibling credential refuses with a clear 409 instead of a 500. Complements fix(security): harden saved-credential, proxy egress, and keyless-mint paths from the release audit #9865's subject-owner check at mint::1) and CIDR ranges now match (bare::1was parsed as host:port1); entries are parsed once per env value. Networking docs describe the rules and that Bun's defaultfetchfollows its own@sim/runtime-secretsuses the fetch handler under Bun whenHTTP(S)_PROXYis set, so a proxy-only-egress deployment can load its startup secretInvalid API keylike every other key failure, instead of a 500inputFieldson every listed point (only the selected point carries them, as withdisplay)credentialSuppliedParamsFordetects the saved-credential picker withisSavedCredentialPicker, the same predicate as the rest of the saved-credential code (no block changes behavior)api-key-read-onlyflag description says to turn it off before rolling back below the release that added read-only keysType of Change
Testing
workload-identity-subject-reuse.integration.ts(8: second credential and concurrent creates refused, reconnect onto a held subject refused, concurrent reconnects, own-subject reconnect allowed, unreadable siblings refuse with 409),pause-persistence.integration.ts(11); each guard reverted turns its test redenvironment-proxy.server.test.ts(28; bare IPv6, long IPv6 spelling, IPv4/IPv6 CIDR fail without the change), runtime-secretsproxy-egress.test.ts(real Bun subprocess through a CONNECT recorder; no tunnel before the fix),v2-api-key-auth.test.ts,pi-providers.test.ts,models.test.ts,human-in-the-loop-manager.test.ts, saved-credential and params teststscon apps/sim, runtime-secrets, and testing; root scripts and sim-cli suites;check:openapi,cli-api,mcp-operations,v2-route-table,boundaries,egress-boundary,realtime-prune,source-conventions,test-patterns,comment-hygiene; block registry check; biomeChecklist
test-auditauthoring gate)