Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions .agents/skills/add-block/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ export const {ServiceName}Block: BlockConfig = {
// Multi-line input
{ id: 'field', title: 'Label', type: 'long-input', placeholder: '...', rows: 6 }

// Password input
// Password input: only for legacy blocks or a secret with no saved-credential fit
// (model-provider keys, hosted-key pairs); a service token uses a saved credential
{ id: 'apiKey', title: 'API Key', type: 'short-input', password: true }
```

Expand Down Expand Up @@ -136,6 +137,32 @@ export const {ServiceName}Block: BlockConfig = {
}
```

A service-account-only picker (a saved token or key credential) instead sets
`credentialKind: 'service-account'` and `mode: 'basic'` (no scopes), and pairs with an advanced
twin; `isSavedCredentialPicker` matches only this shape:
```typescript
{
id: 'credential',
title: '{Service} Account',
type: 'oauth-input',
serviceId: '{service}',
credentialKind: 'service-account',
canonicalParamId: 'oauthCredential',
mode: 'basic',
placeholder: 'Select {Service} credential',
required: true,
},
{
id: 'manualCredential',
title: '{Service} Account',
type: 'short-input',
canonicalParamId: 'oauthCredential',
mode: 'advanced',
placeholder: 'Enter credential ID',
required: true,
}
```

**Scopes:** Always use `getScopesForService(serviceId)` from `@/lib/oauth/utils` for `requiredScopes`. Never hardcode scope arrays — the single source of truth is `OAUTH_PROVIDERS` in `lib/oauth/oauth.ts`.

**Scope descriptions:** When adding a new OAuth provider, also add human-readable descriptions for all scopes in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`.
Expand Down Expand Up @@ -695,7 +722,8 @@ outputs: {
When creating V2 blocks (alongside legacy V1):

```typescript
// V1 Block - mark as legacy
// V1 Block - mark as legacy. A credential-only successor is the exception: the
// successor keeps the legacy block's name, so neither block is renamed.
export const ServiceBlock: BlockConfig = {
type: 'service',
name: 'Service (Legacy)',
Expand Down
29 changes: 22 additions & 7 deletions .agents/skills/add-integration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ supported paths and choose the simplest supported setup for the intended user:
| Customer-owned client credentials | Saved `service_account` credential with a client ID and secret | Internal-app eligibility, grant activation, scope syntax, token lifetime and revocation behavior |
| API token or personal access token | Saved token service account when reusable connections are useful | Token permissions, identity verification, expiry and rotation |
| Private key, certificate, or service-account JSON | Existing key-based service-account framework | Signing algorithm, audience, subject, tenant binding and key rotation |
| Cloud workload identity (AWS role, Databricks/Snowflake/Entra/GCP federation) | Keyless saved credential: Sim signs a short-lived OIDC token the customer's cloud trusts. Add the provider to every table in `lib/credentials/workload-identity/audiences.ts` (`WORKLOAD_IDENTITY_PROVIDERS`, its service-account provider ID map, `WORKLOAD_IDENTITY_KEYLESS_CONFIGS`, `WORKLOAD_IDENTITY_AUDIENCES`) and to `MIXED_MODE_PROVIDERS` in `lib/credentials/application/prepare-workload-identity.ts` | Issuer and audience the provider accepts, how the trust pins the subject, token lifetime |

API, CLI, and MCP callers set up keyless in two steps: `POST /api/v2/credentials/workload-identity/subjects`
(or `sim credentials keyless-subject <providerId> --output json`) returns the subject to trust and a
`keylessConfig`; they add its `requiredConfigFields` and create the credential with it. The provider
catalog advertises `keyless` only when the deployment can sign tokens.

Do not present customer-owned credentials as a way around a provider's approval requirements
for a shared public integration. Explain the supported account/app type in the setup docs. Keep
Expand Down Expand Up @@ -86,15 +92,23 @@ service id:
answers), and `errorBodyEchoesToken` when an error body echoes the token.
3. `apps/sim/lib/oauth/oauth.ts` and `types.ts`: the service entry (`authType: 'service_account'`,
`serviceAccountProviderId`).
4. `apps/sim/tools/shared/saved-credentials.ts`: `tokenCredentialOAuth('<service>', { <toolParam>:
'accessToken' | 'domain' | 'providerWorkspaceId' })`, listed in `SAVED_CREDENTIAL_OAUTH`, and set
as `oauth` on every tool. Map every param that decides the request host; the executor overwrites
mapped params after authorizing the credential, and `check-block-registry` probes the tools' real
URL builders and fails on a host-steering param left unmapped.
4. The tools' credential policy. A NEW integration's tools take a hidden `accessToken` and declare
`oauth: { required: true, provider: '<service>', credentialKind: 'service-account' }`, pinning
any host or account param with `authoritativeParams` (Flow's and Snowflake's tools are the
pattern). `tokenCredentialOAuth('<service>', { <toolParam>: 'accessToken' | 'domain' |
'providerWorkspaceId' })` in `apps/sim/tools/shared/saved-credentials.ts`, listed in
`SAVED_CREDENTIAL_OAUTH`, is for existing tools that already take the token inline (successors).
Either way, the executor overwrites mapped and pinned params after authorizing the credential,
and `check-block-registry` probes the real URL builders of every block with a saved-credential
picker and fails on a host-steering param left unmapped or unpinned.
5. A credential-only block (including a successor) uses `authMode: AuthMode.OAuth`, so the
generated catalog maps the service; a token-only block that keeps `AuthMode.ApiKey`, as Coda
does, registers in `tokenCredentialIntegrationTypes` instead (see below). The credential picker
sits directly under the operation dropdown (`add-block` → OAuth/Credentials). An existing block that already takes the secret inline is never edited in
sits directly under the operation dropdown (`add-block` → OAuth/Credentials). A new block
writes the picker inline, as Higgsfield does: `{ id: 'credential', type: 'oauth-input', serviceId,
credentialKind: 'service-account', canonicalParamId: 'oauthCredential', mode: 'basic',
required: true }`, plus a `manualCredential` `short-input` with the same `canonicalParamId` in
`mode: 'advanced'`. An existing block that already takes the secret inline is never edited in
place: add a credential-only successor (`add-block` → Credential-only successor).

Then run `generate-docs`, `generate:block-successors`, `generate:deployment-config`, and
Expand Down Expand Up @@ -461,7 +475,8 @@ If creating V2 versions (API-aligned outputs):

1. **V2 Tools** - Add `_v2` suffix, version `2.0.0`, flat outputs
2. **V2 Block** - Add `_v2` type, use `createVersionedToolSelector`
3. **V1 Block** - Add `(Legacy)` to name, set `hideFromToolbar: true`, and add
3. **V1 Block** - Add `(Legacy)` to name (a credential-only successor is the exception: it keeps
the legacy block's name), set `hideFromToolbar: true`, and add
`sunset: { status: 'legacy', replacedBy: '{service}_v2' }` — `check-block-registry`
fails a legacy block with no `replacedBy`, and the amber legacy badge plus its
click-to-upgrade action read from that field.
Expand Down
7 changes: 7 additions & 0 deletions .agents/skills/validate-integration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ For **each tool** in `tools.access`:
`tokenCredentialOAuth` policy, every
param that decides the request host is mapped, and the stored domain is in the form the tools
build URLs from (`check-block-registry.ts origin/staging` passes)
- [ ] A non-successor token block's tools declare `oauth: { required: true, provider,
credentialKind: 'service-account' }` and pin every host or account param in
`authoritativeParams` (`check-block-registry` enforces this for every block with a
saved-credential picker)
- [ ] A keyless provider appears in every table in `lib/credentials/workload-identity/audiences.ts`
and in `MIXED_MODE_PROVIDERS` (`prepare-workload-identity.ts`), and the provider catalog
shows `keyless` only when the deployment can sign tokens
- [ ] The `tokenCheckValidator` request uses the tools' host and auth, its normalizers refuse
anything that could steer the host or path, and a fake key is rejected without a valid
scoped key being refused
Expand Down
8 changes: 4 additions & 4 deletions apps/docs/content/docs/cli/credentials.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ The proof expires after 24 hours and works only for the user who requested it, w
**Examples**

```bash
sim credentials keyless-subject <providerId> --output json > subject.json
sim credentials keyless-subject aws-service-account --output json > subject.json
jq '.keylessConfig + {"roleArn":"arn:aws:iam::123456789012:role/SimWorkflows"}' subject.json > keyless.json
sim credentials create <providerId> --name "Keyless" --credentials @keyless.json
sim credentials create aws-service-account --name "Keyless" --credentials @keyless.json
```

API request and response schemas: [createServiceAccountCredential](/api-reference/credentials/createServiceAccountCredential).
Expand Down Expand Up @@ -373,9 +373,9 @@ The proof expires after 24 hours and works only for the user who requested it, w
**Examples**

```bash
sim credentials keyless-subject <providerId> --output json > subject.json
sim credentials keyless-subject aws-service-account --output json > subject.json
jq '.keylessConfig + {"roleArn":"arn:aws:iam::123456789012:role/SimWorkflows"}' subject.json > keyless.json
sim credentials create <providerId> --name "Keyless" --credentials @keyless.json
sim credentials create aws-service-account --name "Keyless" --credentials @keyless.json
```

API request and response schemas: [createCredentialWorkloadIdentitySubject](/api-reference/credentials/createCredentialWorkloadIdentitySubject).
Expand Down
8 changes: 4 additions & 4 deletions apps/docs/content/docs/cli/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -786,9 +786,9 @@ The proof expires after 24 hours and works only for the user who requested it, w
**Examples**

```bash
sim credentials keyless-subject <providerId> --output json > subject.json
sim credentials keyless-subject aws-service-account --output json > subject.json
jq '.keylessConfig + {"roleArn":"arn:aws:iam::123456789012:role/SimWorkflows"}' subject.json > keyless.json
sim credentials create <providerId> --name "Keyless" --credentials @keyless.json
sim credentials create aws-service-account --name "Keyless" --credentials @keyless.json
```

API request and response schemas: [createServiceAccountCredential](/api-reference/credentials/createServiceAccountCredential).
Expand Down Expand Up @@ -822,9 +822,9 @@ The proof expires after 24 hours and works only for the user who requested it, w
**Examples**

```bash
sim credentials keyless-subject <providerId> --output json > subject.json
sim credentials keyless-subject aws-service-account --output json > subject.json
jq '.keylessConfig + {"roleArn":"arn:aws:iam::123456789012:role/SimWorkflows"}' subject.json > keyless.json
sim credentials create <providerId> --name "Keyless" --credentials @keyless.json
sim credentials create aws-service-account --name "Keyless" --credentials @keyless.json
```

API request and response schemas: [createCredentialWorkloadIdentitySubject](/api-reference/credentials/createCredentialWorkloadIdentitySubject).
Expand Down
7 changes: 5 additions & 2 deletions apps/docs/content/docs/integrations/aws-credentials.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ STS **Get Session Token** works only with an **Access keys** credential: AWS ref
In the AWS IAM console, open **Identity providers → Add provider**, choose **OpenID Connect**, and enter the dialog's **Issuer** as the **Provider URL**, and its **Audience**. You only do this once per AWS account.
</Step>
<Step>
Create a role (or edit an existing one) and set its trust policy to the **Role trust policy** shown in the dialog. It trusts only this credential's **Subject**, so no other Sim credential, workspace, or customer can assume the role.
Enter the role's ARN (`arn:aws:iam::<account ID>:role/<role name>`) in the dialog first. The **Role trust policy** fills in its account ID; until then it shows `<AWS_ACCOUNT_ID>`, which IAM rejects.
</Step>
<Step>
Attach the permissions your workflows need to the role, then paste its ARN into the dialog and save. Sim verifies the connection by assuming the role before saving it, and refuses a role that would also trust a different subject.
Create the role with that name (or edit the existing one) and set its trust policy to the **Role trust policy** shown in the dialog. It trusts only this credential's **Subject**, so no other Sim credential, workspace, or customer can assume the role.
</Step>
<Step>
Attach the permissions your workflows need to the role, then save. Sim verifies the connection by assuming the role before saving it, and refuses a role that would also trust a different subject.
</Step>
</Steps>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Bitbucket shows actions taken with the token (comments, merges, pipeline runs) u

## Prerequisites

Admin access to the workspace, project, or repository the token is for. Workspace access tokens require a Bitbucket Cloud Premium plan; project and repository access tokens do not.
Admin access to the workspace, project, or repository the token is for. Workspace and project access tokens require a Bitbucket Cloud Premium plan; repository access tokens do not.

## Create the access token [#creating-the-access-token]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Every option resolves on Sim's servers at run time; tokens never appear in block
In Sim, add a **Databricks connection**, choose **Service principal (keyless, via Sim OIDC)**, and enter the workspace host and the service principal's **application ID**.
</Step>
<Step>
A Databricks account admin runs the **Federation policy** command from the dialog with the service principal's numeric ID (find it with `databricks account service-principals list --filter 'applicationId eq "<application ID>"'`, or on the service principal's page in the account console). The policy trusts Sim's issuer, the Sim audience, and this connection's subject only.
A Databricks account admin runs the **Federation policy** command from the dialog with the service principal's numeric ID. First sign in to the account with `databricks auth login --host <account console URL> --account-id <account ID>` (not a workspace profile or the workspace web terminal). Find the numeric ID with `databricks account service-principals list --filter 'applicationId eq "<application ID>"'`, or on the service principal's page in the account console. The policy trusts Sim's issuer, the Sim audience, and this connection's subject only.
</Step>
<Step>
Make sure the service principal can access the workspace and the resources your workflows use, then save. Sim verifies the connection by exchanging a token and reading the service principal's identity, and refuses a policy that would also trust a different subject, since every Sim workspace shares the issuer and audience.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,16 @@ Keyless credentials work with Google Cloud APIs that the service account calls a

<Steps>
<Step>
In the [Google Cloud Console](https://console.cloud.google.com/), enable the IAM, Service Account Credentials, and Security Token Service APIs in the project that will hold the workload identity pool, and note its **project number** (on the project's Dashboard; it's numeric, not the project ID).
In the [Google Cloud Console](https://console.cloud.google.com/), enable the IAM, Resource Manager, Service Account Credentials, and Security Token Service APIs in the project that will hold the workload identity pool, and note its **project number** (on the project's Dashboard; it's numeric, not the project ID).
</Step>
<Step>
Create or pick a service account, and grant it the roles your workflows need (for example, BigQuery Data Viewer and BigQuery Job User, or Vertex AI User). No key is needed.
</Step>
<Step>
In Sim, open the **Google BigQuery** integration, choose **Add service account**, and set **Authentication** to **Workload Identity Federation (keyless, via Sim OIDC)**. Enter the project number, a pool ID and provider ID (4 to 32 lowercase letters, digits, or hyphens, not starting with `gcp-`), and the service account email (`name@project-id.iam.gserviceaccount.com`).
In Sim, open the **Google BigQuery** integration, choose **Add service account**, and set **Authentication** to **Workload Identity Federation (keyless, via Sim OIDC)**. Enter the project number, a pool ID and provider ID (4 to 32 lowercase letters, digits, or hyphens, not starting with `gcp-`), and the service account email (`name@project-id.iam.gserviceaccount.com`; user-managed service account only, so default accounts such as `…@developer.gserviceaccount.com` are refused).
</Step>
<Step>
The dialog shows the **Issuer**, **Audience**, and **Subject**, and three `gcloud` commands with your values filled in. Run them in that project. They run against the project number you entered and create the pool, an OIDC provider for Sim's issuer with the attribute mapping `google.subject=assertion.sub` and an attribute condition that accepts only this credential's subject, and grant that subject `roles/iam.workloadIdentityUser` on the service account:
The dialog shows the **Issuer**, **Audience**, and **Subject**, and three `gcloud` commands with your values filled in. Run them in that project. They run against the project number you entered and create the pool, an OIDC provider for Sim's issuer with the attribute mapping `google.subject=assertion.sub` and an attribute condition that accepts only this credential's subject, and grant that subject `roles/iam.workloadIdentityUser` on the service account. If the pool already exists, skip the first command:

```bash
gcloud iam workload-identity-pools create POOL_ID \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Snowflake's [workload identity federation](https://docs.snowflake.com/en/user-gu
In Sim, add a **Snowflake** connection, choose **Service user (keyless, via Sim OIDC)**, and enter the account host (`myorg-myaccount.snowflakecomputing.com`). The dialog shows the issuer, audience, and subject for this connection, and the statement to run.
</Step>
<Step>
A user whose role can create users (and has `OWNERSHIP` or `MODIFY PROGRAMMATIC AUTHENTICATION METHODS` on the user) runs the statement from the dialog. It looks like this:
A user whose role can create users (for example USERADMIN) runs the statement from the dialog. If SIM_WORKFLOWS already exists, change the name in the statement you run and in the follow-up GRANT and ALTER commands below. It looks like this:

```sql
CREATE USER SIM_WORKFLOWS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ OAuth integrations require a provider application and its credentials on a self-

### Keyless cloud credentials [#workload-identity]

Saved AWS and Databricks credentials can trust Sim through OpenID Connect instead of storing keys. Leave these unset to offer only key- and secret-based credentials.
Saved AWS, Databricks, Snowflake, Microsoft app, and Google Cloud credentials can trust Sim through OpenID Connect instead of storing keys. Leave these unset to offer only key- and secret-based credentials.

| Variable | Description |
| --- | --- |
Expand Down
16 changes: 8 additions & 8 deletions apps/docs/scripts/fixtures/content-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -3016,8 +3016,8 @@
{
"lang": "bash",
"meta": null,
"sha256": "e7dfe3c54d5966f79a4c8fcc7287fe9c29e893326977e4264a7cdc35822c076f",
"bytes": 262,
"sha256": "48d4019bc1905094aec776873af1d5a4e94ec2f52d043c51bbbb559b6a876944",
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
"bytes": 276,
"line": 102
},
{
Expand Down Expand Up @@ -3079,8 +3079,8 @@
{
"lang": "bash",
"meta": null,
"sha256": "e7dfe3c54d5966f79a4c8fcc7287fe9c29e893326977e4264a7cdc35822c076f",
"bytes": 262,
"sha256": "48d4019bc1905094aec776873af1d5a4e94ec2f52d043c51bbbb559b6a876944",
"bytes": 276,
"line": 375
}
],
Expand Down Expand Up @@ -11190,8 +11190,8 @@
{
"lang": "bash",
"meta": null,
"sha256": "e7dfe3c54d5966f79a4c8fcc7287fe9c29e893326977e4264a7cdc35822c076f",
"bytes": 262,
"sha256": "48d4019bc1905094aec776873af1d5a4e94ec2f52d043c51bbbb559b6a876944",
"bytes": 276,
"line": 788
},
{
Expand All @@ -11204,8 +11204,8 @@
{
"lang": "bash",
"meta": null,
"sha256": "e7dfe3c54d5966f79a4c8fcc7287fe9c29e893326977e4264a7cdc35822c076f",
"bytes": 262,
"sha256": "48d4019bc1905094aec776873af1d5a4e94ec2f52d043c51bbbb559b6a876944",
"bytes": 276,
"line": 824
},
{
Expand Down
Loading
Loading