Repository navigation
feat(api): expose credential sharing and SSO administration - #8770
waleedlatif1 wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
We detected this is a high-risk PR and are running a free ultrareview. An ultrareview is a deeper, multi-pass review that catches hard-to-find bugs a standard review can miss. We'll post the findings when it completes. This PR appears to change authentication, authorization, or input validation, where a missed bug can expose data or grant the wrong access, so a deeper multi-pass review is worth running. Want an ultrareview on every high-risk PR? Set up automated ultrareviews. |
|
| path: '/api/v2/organizations/[organizationId]/sso/providers/[providerId]/primary', | ||
| params: v2SsoProviderParamsSchema, | ||
| query: noInputSchema, | ||
| body: noInputSchema, |
There was a problem hiding this comment.
sim organizations sso providers primary and sim organizations domains verify send no request body, but both new contracts require JSON through body: noInputSchema. Their generated CLI entries omit body, so buildRequest returns undefined. The server rejects both commands with 400 before running them.
Remove the body requirement from these parameter-only operations, or make the generated CLI send an empty JSON object.
Knowledge Base Used: Developer interfaces
| const updated: Record<string, unknown> = { | ||
| ...current, | ||
| ...filterUndefined(config), | ||
| issuer, | ||
| } |
There was a problem hiding this comment.
Updating an existing OIDC provider through a personal API key or OAuth token silently ignores skipUserInfoEndpoint: true when an endpoint is already saved. saveSsoProvider clears the endpoint by setting it to undefined, but this merge drops that value and keeps current.userInfoEndpoint. The save succeeds while the stored settings still contain the endpoint the administrator asked to stop using.
Carry an explicit clear operation into the repository and remove the saved field.
There was a problem hiding this comment.
We detected this is a high-risk PR and ran a free ultrareview. An ultrareview is a deeper, multi-pass review that catches hard-to-find bugs a standard review can miss.
This PR appears to change authentication, authorization, or input validation, where a missed bug can expose data or grant the wrong access, so a deeper multi-pass review is worth running.
Want an ultrareview on every high-risk PR? Set up automated ultrareviews.
22 issues found across 68 files
Confidence score: 2/5
- The domain verification route requires a JSON body, but the generated CLI sends none, so verification fails with a 400; the OpenAPI contract also marks these no-input actions as requiring a body. Make the empty-body contract optional.
sso-presenters.tsturns legacy SAML metadata into an empty object, so saving settings can overwrite existing IdP metadata and break SAML sign-in. Preserve the stored metadata when presenting and saving it.provider-registration.tscan change an existing OIDC provider toclient_secret_postwhen discovery is unavailable, breaking IdPs that require Basic authentication. Preserve the stored authentication method on re-save.commands.tsexposes the write-only OIDC secret only as a plain argv value. Add prompt or file/stdin input before exposing this command.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/sim/app/api/v2/credentials/[credentialId]/members/[userId]/route.ts">
<violation number="1" location="apps/sim/app/api/v2/credentials/[credentialId]/members/[userId]/route.ts:18">
P2: This new public endpoint exposes a non-atomic grant revocation. Two concurrent DELETEs can both pass the initial active check, and the second returns 200 after updating an already-revoked row instead of the documented 404; lock and revalidate the grant, then make the status update conditional before reporting success.</violation>
</file>
<file name="apps/sim/lib/auth/sso/provider-adapter.ts">
<violation number="1" location="apps/sim/lib/auth/sso/provider-adapter.ts:54">
P3: This SCIM collision check is unreachable and cannot inspect Sim's SCIM connections, so API/OAuth registrations silently skip the conflict protection it advertises. Query the actual SCIM schema using its intended identifier, or remove this check if no shared ID contract exists.</violation>
</file>
<file name="apps/sim/app/api/v2/organizations/[organizationId]/domains/[domainId]/verify/route.ts">
<violation number="1" location="apps/sim/app/api/v2/organizations/[organizationId]/domains/[domainId]/verify/route.ts:9">
P1: This route requires a JSON `{}` body even though the generated CLI/MCP operation exposes no body and sends none, so `organizations domains verify` receives `400 Request body must be valid JSON` before verification runs. Make the no-input body optional with a `{}` default (and regenerate the clients), or otherwise allow an empty POST.</violation>
</file>
<file name="apps/sim/lib/api/server/routes/sso.ts">
<violation number="1" location="apps/sim/lib/api/server/routes/sso.ts:19">
P2: This maps Better Auth's 500 `INTERNAL_SERVER_ERROR` to a 503 Service Unavailable response, causing clients to retry an unexpected server fault as though the dependency were transiently unavailable. Preserve 500-class faults as `INTERNAL_ERROR` and reserve `SERVICE_UNAVAILABLE` for an explicitly transient status such as 503.</violation>
</file>
<file name="apps/sim/lib/api/contracts/auth.ts">
<violation number="1" location="apps/sim/lib/api/contracts/auth.ts:19">
P3: `orgId` now reports “Organization ID is required” for numbers or objects even though the field was supplied. Reuse `organizationIdSchema` (or `missingFieldError`) in both branches so malformed values retain an actionable type error.</violation>
</file>
<file name="apps/sim/lib/api/contracts/v2/sso.ts">
<violation number="1" location="apps/sim/lib/api/contracts/v2/sso.ts:21">
P3: This shared parameter description is inaccurate for the domain administration endpoints, so their generated CLI help and OpenAPI docs describe the wrong resource. Use wording that covers both SSO settings and verified-domain management.</violation>
</file>
<file name="apps/sim/lib/organizations/application/domain-settings.ts">
<violation number="1" location="apps/sim/lib/organizations/application/domain-settings.ts:116">
P2: This pagination branch bypasses the existing 25-domain enumeration cap for `organization_delegated` callers. Keep delegated principals on the capped branch (or reject pagination for them) so a delegated settings caller cannot enumerate every domain by following `nextCursorKeys`.</violation>
</file>
<file name="apps/sim/lib/auth/sso/application/provider-settings.ts">
<violation number="1" location="apps/sim/lib/auth/sso/application/provider-settings.ts:167">
P2: The membership check runs before the delete transaction, so a concurrent role downgrade or removal can commit before this request deletes the provider while the stale authorization is still accepted. Recheck current administrator membership under the transaction's organization/member lock before deleting.</violation>
</file>
<file name="apps/sim/lib/api/contracts/v2/openapi/sso.ts">
<violation number="1" location="apps/sim/lib/api/contracts/v2/openapi/sso.ts:112">
P2: These no-input actions are published with a required JSON request body, so generated clients and callers cannot invoke them as bodyless POSTs. Make the empty-body contract optional/defaulted (as other no-body POSTs do) before documenting it here, so OpenAPI does not require an artificial `{}`.</violation>
</file>
<file name="packages/sim-cli/src/contract/commands.ts">
<violation number="1" location="packages/sim-cli/src/contract/commands.ts:347">
P2: These new inventory list commands stop after the first 100 records by default and leave the remaining records behind a cursor. Add all three operations to the complete-list set so they follow the CLI’s all-pages default used by other inventories.</violation>
<violation number="2" location="packages/sim-cli/src/contract/commands.ts:1123">
P1: `saveSsoProvider` exposes the write-only OIDC secret only as a plain argv value. Add prompt or file/stdin-backed secret input before exposing this command, because the generic string path cannot use the repository’s safe secret-input convention and leaks values through shell history and process listings.</violation>
</file>
<file name="apps/sim/lib/api/server/sso-presenters.ts">
<violation number="1" location="apps/sim/lib/api/server/sso-presenters.ts:50">
P1: `publicConfig` converts legacy string-valued SAML metadata into `{}`. The settings editor then sends no metadata, and the next save overwrites the existing IdP metadata with `{ metadata: '' }`, potentially breaking SAML sign-in; preserve string values as metadata objects before projecting them.</violation>
</file>
<file name="apps/sim/lib/auth/sso/application/sso-requirement.ts">
<violation number="1" location="apps/sim/lib/auth/sso/application/sso-requirement.ts:94">
P2: Self-hosted SSO-disabled requests are labeled `ENTERPRISE_PLAN_REQUIRED` even though the message directs callers to set deployment flags. The v2 API exposes this as `error.details.code`, so clients may incorrectly direct users to upgrade a plan; return a codeless or configuration-specific refusal for this branch, and reserve the plan code for billing-enabled entitlement failures.</violation>
</file>
<file name="apps/sim/lib/auth/sso/provider-repository.ts">
<violation number="1" location="apps/sim/lib/auth/sso/provider-repository.ts:151">
P2: The SCIM namespace check is not race-safe. A concurrent SCIM and SSO registration can both pass this pre-check and commit the same provider ID; use a shared provider-ID lock or another cross-namespace coordination mechanism before inserting.</violation>
<violation number="2" location="apps/sim/lib/auth/sso/provider-repository.ts:240">
P2: API SAML saves replace stored nested metadata objects instead of merging them. This drops existing private keys, entity IDs, and encryption flags, and can make a certificate-only update look like an identity change for linked accounts; merge each nested metadata object with its stored value before serializing.</violation>
<violation number="3" location="apps/sim/lib/auth/sso/provider-repository.ts:266">
P2: The linked-account check is a TOCTOU race because locking the provider does not serialize account creation. A concurrent SSO callback can add the first account after this query, allowing an identity-changing update that should have been rejected; coordinate account creation and updates with the same provider-scoped lock.</violation>
</file>
<file name="apps/sim/lib/auth/sso/application/provider-registration.ts">
<violation number="1" location="apps/sim/lib/auth/sso/application/provider-registration.ts:105">
P2: A 2xx discovery response containing `null` or another non-object JSON value causes an unhandled exception during registration. Validate that the decoded document is a non-array object and return a validation failure otherwise.</violation>
<violation number="2" location="apps/sim/lib/auth/sso/application/provider-registration.ts:428">
P1: A re-save can silently change an existing provider’s token authentication method to `client_secret_post` when discovery is unavailable, breaking IdPs that require Basic authentication. Load the stored OIDC method and pass it as the fallback before selecting the default.</violation>
<violation number="3" location="apps/sim/lib/auth/sso/application/provider-registration.ts:467">
P2: Enabling `skipUserInfoEndpoint` on an existing OIDC provider does not remove its saved UserInfo endpoint because the update repository filters this `undefined` value. Clear the persisted endpoint or store an explicit skip flag so the provider stops calling UserInfo.</violation>
<violation number="4" location="apps/sim/lib/auth/sso/application/provider-registration.ts:687">
P2: A failed trust write can roll back another administrator’s later provider update because the rollback matches only the provider ID. Serialize provider saves or make the rollback conditional on the exact version/config written by this request.</violation>
<violation number="5" location="apps/sim/lib/auth/sso/application/provider-registration.ts:694">
P2: Changing an existing provider from SAML to OIDC or vice versa is not handled: the update sends only the new protocol config, so the repository rejects the switch or leaves the old protocol column behind. Reject protocol changes explicitly or replace both protocol columns atomically.</violation>
</file>
<file name="apps/sim/lib/api/contracts/v2/openapi/credential-members.ts">
<violation number="1" location="apps/sim/lib/api/contracts/v2/openapi/credential-members.ts:23">
P3: These credential-member use cases cannot produce a 409, but `RESOURCE_CONFLICT_ERRORS` advertises one in the public OpenAPI contract. Use `RESOURCE_ERRORS` for these routes so generated clients do not expose an unreachable conflict response.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
| import { organizationSecurityOperations } from '@/lib/organizations/application/operations' | ||
|
|
||
| export const POST = defineV2JsonRoute({ | ||
| contract: v2VerifyOrganizationDomainContract, |
There was a problem hiding this comment.
P1: This route requires a JSON {} body even though the generated CLI/MCP operation exposes no body and sends none, so organizations domains verify receives 400 Request body must be valid JSON before verification runs. Make the no-input body optional with a {} default (and regenerate the clients), or otherwise allow an empty POST.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/app/api/v2/organizations/[organizationId]/domains/[domainId]/verify/route.ts, line 9:
<comment>This route requires a JSON `{}` body even though the generated CLI/MCP operation exposes no body and sends none, so `organizations domains verify` receives `400 Request body must be valid JSON` before verification runs. Make the no-input body optional with a `{}` default (and regenerate the clients), or otherwise allow an empty POST.</comment>
<file context>
@@ -0,0 +1,17 @@
+import { organizationSecurityOperations } from '@/lib/organizations/application/operations'
+
+export const POST = defineV2JsonRoute({
+ contract: v2VerifyOrganizationDomainContract,
+ operation: organizationSecurityOperations.verifyDomain,
+ auth: v2ApiKeyAuth,
</file context>
| command: 'organizations sso providers get', | ||
| pathFlags: ORGANIZATION_FLAG, | ||
| }, | ||
| saveSsoProvider: { |
There was a problem hiding this comment.
P1: saveSsoProvider exposes the write-only OIDC secret only as a plain argv value. Add prompt or file/stdin-backed secret input before exposing this command, because the generic string path cannot use the repository’s safe secret-input convention and leaks values through shell history and process listings.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/sim-cli/src/contract/commands.ts, line 1123:
<comment>`saveSsoProvider` exposes the write-only OIDC secret only as a plain argv value. Add prompt or file/stdin-backed secret input before exposing this command, because the generic string path cannot use the repository’s safe secret-input convention and leaks values through shell history and process listings.</comment>
<file context>
@@ -1088,6 +1106,59 @@ export const CLI_CONTRACT: CliContract = {
+ command: 'organizations sso providers get',
+ pathFlags: ORGANIZATION_FLAG,
+ },
+ saveSsoProvider: {
+ command: 'organizations sso providers save',
+ pathFlags: ORGANIZATION_FLAG,
</file context>
| for (const key of fields) { | ||
| if (record[key] === undefined) continue | ||
| if (key === 'spMetadata' || key === 'idpMetadata') { | ||
| const metadata = toRecord(record[key]) |
There was a problem hiding this comment.
P1: publicConfig converts legacy string-valued SAML metadata into {}. The settings editor then sends no metadata, and the next save overwrites the existing IdP metadata with { metadata: '' }, potentially breaking SAML sign-in; preserve string values as metadata objects before projecting them.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/api/server/sso-presenters.ts, line 50:
<comment>`publicConfig` converts legacy string-valued SAML metadata into `{}`. The settings editor then sends no metadata, and the next save overwrites the existing IdP metadata with `{ metadata: '' }`, potentially breaking SAML sign-in; preserve string values as metadata objects before projecting them.</comment>
<file context>
@@ -0,0 +1,99 @@
+ for (const key of fields) {
+ if (record[key] === undefined) continue
+ if (key === 'spMetadata' || key === 'idpMetadata') {
+ const metadata = toRecord(record[key])
+ projected[key] = {
+ metadata: metadata.metadata,
</file context>
| oidcConfig.userInfoEndpoint || toStringOrNull(discovery.userinfo_endpoint) || undefined | ||
| oidcConfig.jwksEndpoint = | ||
| oidcConfig.jwksEndpoint || toStringOrNull(discovery.jwks_uri) || undefined | ||
| oidcConfig.tokenEndpointAuthentication = selectTokenEndpointAuthMethod( |
There was a problem hiding this comment.
P1: A re-save can silently change an existing provider’s token authentication method to client_secret_post when discovery is unavailable, breaking IdPs that require Basic authentication. Load the stored OIDC method and pass it as the fallback before selecting the default.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/auth/sso/application/provider-registration.ts, line 428:
<comment>A re-save can silently change an existing provider’s token authentication method to `client_secret_post` when discovery is unavailable, breaking IdPs that require Basic authentication. Load the stored OIDC method and pass it as the fallback before selecting the default.</comment>
<file context>
@@ -0,0 +1,807 @@
+ oidcConfig.userInfoEndpoint || toStringOrNull(discovery.userinfo_endpoint) || undefined
+ oidcConfig.jwksEndpoint =
+ oidcConfig.jwksEndpoint || toStringOrNull(discovery.jwks_uri) || undefined
+ oidcConfig.tokenEndpointAuthentication = selectTokenEndpointAuthMethod(
+ discovery.token_endpoint_auth_methods_supported,
+ oidcConfig.tokenEndpointAuthentication
</file context>
| rateLimit: v2RateLimits.publicApi, | ||
| errorPolicy: createV2ResourceConcealmentPolicy({ notFoundMessage: 'Credential not found' }), | ||
| mapInput: ({ params, query }) => ({ ...params, assertedWorkspaceId: query.workspaceId }), | ||
| useCase: removeCredentialMemberUseCase, |
There was a problem hiding this comment.
P2: This new public endpoint exposes a non-atomic grant revocation. Two concurrent DELETEs can both pass the initial active check, and the second returns 200 after updating an already-revoked row instead of the documented 404; lock and revalidate the grant, then make the status update conditional before reporting success.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/app/api/v2/credentials/[credentialId]/members/[userId]/route.ts, line 18:
<comment>This new public endpoint exposes a non-atomic grant revocation. Two concurrent DELETEs can both pass the initial active check, and the second returns 200 after updating an already-revoked row instead of the documented 404; lock and revalidate the grant, then make the status update conditional before reporting success.</comment>
<file context>
@@ -0,0 +1,20 @@
+ rateLimit: v2RateLimits.publicApi,
+ errorPolicy: createV2ResourceConcealmentPolicy({ notFoundMessage: 'Credential not found' }),
+ mapInput: ({ params, query }) => ({ ...params, assertedWorkspaceId: query.workspaceId }),
+ useCase: removeCredentialMemberUseCase,
+ present: ({ targetUserId }) => ({ data: { userId: targetUserId, revoked: true as const } }),
+})
</file context>
| } | ||
|
|
||
| if (skipUserInfoEndpoint) { | ||
| oidcConfig.userInfoEndpoint = undefined |
There was a problem hiding this comment.
P2: Enabling skipUserInfoEndpoint on an existing OIDC provider does not remove its saved UserInfo endpoint because the update repository filters this undefined value. Clear the persisted endpoint or store an explicit skip flag so the provider stops calling UserInfo.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/auth/sso/application/provider-registration.ts, line 467:
<comment>Enabling `skipUserInfoEndpoint` on an existing OIDC provider does not remove its saved UserInfo endpoint because the update repository filters this `undefined` value. Clear the persisted endpoint or store an explicit skip flag so the provider stops calling UserInfo.</comment>
<file context>
@@ -0,0 +1,807 @@
+ }
+
+ if (skipUserInfoEndpoint) {
+ oidcConfig.userInfoEndpoint = undefined
+ logger.info('Skipping UserInfo endpoint for provider, claims will come from the ID token', {
+ providerId,
</file context>
| ...context.trustedProviders, | ||
| ], | ||
| hasScimProvider: async (providerId) => | ||
| context.hasPlugin('scim') && |
There was a problem hiding this comment.
P3: This SCIM collision check is unreachable and cannot inspect Sim's SCIM connections, so API/OAuth registrations silently skip the conflict protection it advertises. Query the actual SCIM schema using its intended identifier, or remove this check if no shared ID contract exists.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/auth/sso/provider-adapter.ts, line 54:
<comment>This SCIM collision check is unreachable and cannot inspect Sim's SCIM connections, so API/OAuth registrations silently skip the conflict protection it advertises. Query the actual SCIM schema using its intended identifier, or remove this check if no shared ID contract exists.</comment>
<file context>
@@ -0,0 +1,62 @@
+ ...context.trustedProviders,
+ ],
+ hasScimProvider: async (providerId) =>
+ context.hasPlugin('scim') &&
+ Boolean(
+ await context.adapter.findOne({
</file context>
| skipUserInfoEndpoint: z.boolean().default(false), | ||
| jwksEndpoint: z.string().url().optional(), | ||
| ssoRegistrationInputSchema.options[0].omit({ organizationId: true }).extend({ | ||
| orgId: z.string({ error: 'Organization ID is required' }).min(1, 'Organization ID is required'), |
There was a problem hiding this comment.
P3: orgId now reports “Organization ID is required” for numbers or objects even though the field was supplied. Reuse organizationIdSchema (or missingFieldError) in both branches so malformed values retain an actionable type error.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/api/contracts/auth.ts, line 19:
<comment>`orgId` now reports “Organization ID is required” for numbers or objects even though the field was supplied. Reuse `organizationIdSchema` (or `missingFieldError`) in both branches so malformed values retain an actionable type error.</comment>
<file context>
@@ -14,69 +14,14 @@ export const authProviderStatusResponseSchema = z.object({
- skipUserInfoEndpoint: z.boolean().default(false),
- jwksEndpoint: z.string().url().optional(),
+ ssoRegistrationInputSchema.options[0].omit({ organizationId: true }).extend({
+ orgId: z.string({ error: 'Organization ID is required' }).min(1, 'Organization ID is required'),
}),
- z.object({
</file context>
| const v2SsoOrganizationParamsSchema = z | ||
| .object({ | ||
| organizationId: organizationIdSchema.describe( | ||
| 'Organization whose single sign-on settings are managed.' |
There was a problem hiding this comment.
P3: This shared parameter description is inaccurate for the domain administration endpoints, so their generated CLI help and OpenAPI docs describe the wrong resource. Use wording that covers both SSO settings and verified-domain management.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/api/contracts/v2/sso.ts, line 21:
<comment>This shared parameter description is inaccurate for the domain administration endpoints, so their generated CLI help and OpenAPI docs describe the wrong resource. Use wording that covers both SSO settings and verified-domain management.</comment>
<file context>
@@ -0,0 +1,375 @@
+const v2SsoOrganizationParamsSchema = z
+ .object({
+ organizationId: organizationIdSchema.describe(
+ 'Organization whose single sign-on settings are managed.'
+ ),
+ })
</file context>
| summary: 'List Credential Members', | ||
| description: `List explicit credential grants, including revoked grants, and inherited workspace administrator access. Requires workspace read access. Credentials must be OAuth or service-account connections. ${WORKSPACE_API_KEY_DENIED}`, | ||
| tags: ['Credentials'], | ||
| errors: RESOURCE_CONFLICT_ERRORS, |
There was a problem hiding this comment.
P3: These credential-member use cases cannot produce a 409, but RESOURCE_CONFLICT_ERRORS advertises one in the public OpenAPI contract. Use RESOURCE_ERRORS for these routes so generated clients do not expose an unreachable conflict response.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/sim/lib/api/contracts/v2/openapi/credential-members.ts, line 23:
<comment>These credential-member use cases cannot produce a 409, but `RESOURCE_CONFLICT_ERRORS` advertises one in the public OpenAPI contract. Use `RESOURCE_ERRORS` for these routes so generated clients do not expose an unreachable conflict response.</comment>
<file context>
@@ -0,0 +1,117 @@
+ summary: 'List Credential Members',
+ description: `List explicit credential grants, including revoked grants, and inherited workspace administrator access. Requires workspace read access. Credentials must be OAuth or service-account connections. ${WORKSPACE_API_KEY_DENIED}`,
+ tags: ['Credentials'],
+ errors: RESOURCE_CONFLICT_ERRORS,
+ success: { description: 'List Credential Members result.', headers: RATE_LIMIT_HEADERS },
+ },
</file context>
Summary
Type of Change
Testing
Checklist
test-auditauthoring gate)