Skip to content

Commit 64f547f

Browse files
fix(resource-policies): authorize credential use only
1 parent 94b6796 commit 64f547f

8 files changed

Lines changed: 25 additions & 81 deletions

File tree

apps/sim/lib/credential-groups/application/list-credentials.test.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -230,21 +230,6 @@ describe('listCredentialGroupCredentials', () => {
230230
})
231231
})
232232

233-
it('lists the entire group for a matching resource-policy grant', async () => {
234-
mocks.findPolicyGrant.mockResolvedValueOnce({
235-
id: 'grant-1',
236-
subject: { type: 'workflow', workflowId: 'workflow-1' },
237-
actions: ['credential_groups.credentials.list', 'credential_groups.credentials.use'],
238-
})
239-
240-
await listCredentialGroupCredentials.execute({ principal: executorPrincipal(), input })
241-
242-
expect(mocks.loadEnrollmentAccess).not.toHaveBeenCalled()
243-
expect(mocks.listCredentials).toHaveBeenCalledWith(
244-
expect.objectContaining({ credentialGroupEnrollmentId: undefined })
245-
)
246-
})
247-
248233
it('filters by canonical providers active in the group', async () => {
249234
await listCredentialGroupCredentials.execute({
250235
principal: executorPrincipal(),

apps/sim/lib/credential-groups/application/manage-access.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe('Credential Group access policy operations', () => {
142142
{
143143
id: expect.any(String),
144144
subject,
145-
actions: ['credential_groups.credentials.list', 'credential_groups.credentials.use'],
145+
actions: ['credential_groups.credentials.use'],
146146
},
147147
],
148148
},

apps/sim/lib/credential-groups/application/manage-access.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ import {
1616
resourcePolicySubjectSchema,
1717
} from '@/lib/resource-policies/types'
1818

19-
const CREDENTIAL_GROUP_ALL_ACTIONS = [
20-
'credential_groups.credentials.list',
21-
'credential_groups.credentials.use',
22-
] as const
19+
const CREDENTIAL_GROUP_ACCESS_ACTIONS = ['credential_groups.credentials.use'] as const
2320

2421
interface CredentialGroupAccessTargetInput {
2522
assertedWorkspaceId: string
@@ -91,7 +88,7 @@ export const updateCredentialGroupAccess = defineAuthorizedWorkspaceUseCase({
9188
grants: input.grants.map((grant, index) => ({
9289
id: grant.id ?? generateId(),
9390
subject: subjects[index],
94-
actions: [...CREDENTIAL_GROUP_ALL_ACTIONS],
91+
actions: [...CREDENTIAL_GROUP_ACCESS_ACTIONS],
9592
})),
9693
},
9794
})

apps/sim/lib/resource-policies/authorization.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function policy(subject: Record<string, unknown>) {
7878
{
7979
id: 'grant-1',
8080
subject,
81-
actions: ['credential_groups.credentials.list', 'credential_groups.credentials.use'],
81+
actions: ['credential_groups.credentials.use'],
8282
},
8383
],
8484
},
@@ -98,7 +98,7 @@ describe('findResourcePolicyGrant', () => {
9898
context,
9999
resourceType: 'credential_group' as const,
100100
resourceId: 'group-1',
101-
action: 'credential_groups.credentials.list' as const,
101+
action: 'credential_groups.credentials.use' as const,
102102
}
103103

104104
await expect(

apps/sim/lib/resource-policies/types.test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ describe('resource policy documents', () => {
1313
{
1414
id: 'grant-1',
1515
subject: { type: 'workflow' as const, workflowId: 'workflow-1' },
16-
actions: [
17-
'credential_groups.credentials.list' as const,
18-
'credential_groups.credentials.use' as const,
19-
],
16+
actions: ['credential_groups.credentials.use' as const],
2017
},
2118
],
2219
}
@@ -53,7 +50,7 @@ describe('resource policy documents', () => {
5350
{
5451
id: 'grant-2',
5552
subject: { type: 'workflow', workflowId: 'workflow-1' },
56-
actions: ['credential_groups.credentials.list'],
53+
actions: ['credential_groups.credentials.use'],
5754
},
5855
],
5956
},

apps/sim/lib/resource-policies/types.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import { z } from 'zod'
22

33
export const RESOURCE_POLICY_VERSION = 1 as const
44
export const RESOURCE_POLICY_RESOURCE_TYPES = ['credential_group'] as const
5-
export const RESOURCE_POLICY_ACTIONS = [
6-
'credential_groups.credentials.list',
7-
'credential_groups.credentials.use',
8-
] as const
5+
export const RESOURCE_POLICY_ACTIONS = ['credential_groups.credentials.use'] as const
96
export type ResourcePolicyResourceType = (typeof RESOURCE_POLICY_RESOURCE_TYPES)[number]
107
export type ResourcePolicyAction = (typeof RESOURCE_POLICY_ACTIONS)[number]
118

design/credential-group-authorization.md

Lines changed: 13 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Credential Groups collect external users' managed credentials and let workflows
88

99
The default is simple:
1010

11-
> An execution with a verified actor may list and use only that actor's credentials.
11+
> An execution may list every non-secret credential reference, but it may use only the verified actor's credential.
1212
1313
Explicit resource-policy grants can expand a user, Access Control Group, workspace role, or deployed workflow to every credential in the Credential Group.
1414

@@ -60,7 +60,7 @@ List Credentials
6060

6161
Its contract is:
6262

63-
> Return every credential that this execution is currently authorized to use.
63+
> Return every active credential reference in the group; selecting a reference does not authorize its use.
6464
6565
Inputs may select the Credential Group, provider option, and pagination. Inputs never select identity or authorization scope:
6666

@@ -83,28 +83,7 @@ The input does not contain:
8383

8484
## List authorization
8585

86-
The application operation loads the canonical Credential Group, verifies workspace access, and evaluates the current resource policy.
87-
88-
```text
89-
matching credentials.list grant
90-
-> list all active credentials in the group
91-
92-
otherwise verified actor with active enrollment
93-
-> list only credentials for that exact enrollment
94-
95-
otherwise
96-
-> fail
97-
```
98-
99-
The internal result is a database query constraint, not persisted policy state:
100-
101-
```ts
102-
type CredentialListAuthorization =
103-
| { enrollmentId: string }
104-
| { grantId: string }
105-
```
106-
107-
The exact enrollment constraint is applied to cursor validation and every page query. A credential ID returned from an earlier call is not an authorization capability.
86+
The application operation loads the canonical Credential Group, verifies executor delegation, workspace binding, group scope, status, and entitlement, then returns a bounded page of all active credential references in the group. The result contains opaque IDs and account metadata but no token material, and a returned credential ID is never an authorization capability.
10887

10988
## Credential use authorization
11089

@@ -131,7 +110,6 @@ Tokens and refresh tokens never appear in block output or execution logs. List r
131110
The Credential Group resource policy can grant:
132111

133112
```text
134-
credential_groups.credentials.list
135113
credential_groups.credentials.use
136114
```
137115

@@ -144,10 +122,7 @@ Example workflow grant:
144122
"type": "workflow",
145123
"workflowId": "wf_support"
146124
},
147-
"actions": [
148-
"credential_groups.credentials.list",
149-
"credential_groups.credentials.use"
150-
]
125+
"actions": ["credential_groups.credentials.use"]
151126
}
152127
```
153128

@@ -162,18 +137,17 @@ Example Access Control Group grant:
162137
"type": "access_control_group",
163138
"accessControlGroupId": "pg_support_admins"
164139
},
165-
"actions": [
166-
"credential_groups.credentials.list",
167-
"credential_groups.credentials.use"
168-
]
140+
"actions": ["credential_groups.credentials.use"]
169141
}
170142
```
171143

172144
Membership is evaluated at operation time. Removing a user from the Access Control Group revokes access immediately on the next protected operation.
173145

174146
## Execution behavior
175147

176-
| Execution | Result |
148+
All executions that pass the list operation's workspace and group checks can see the same credential references. Using a selected credential resolves as follows:
149+
150+
| Execution | Credential use result |
177151
| --- | --- |
178152
| Manual actor without an explicit grant | Actor's enrollment only |
179153
| Manual actor in a granted Access Control Group | All credentials |
@@ -184,7 +158,7 @@ Membership is evaluated at operation time. Removing a user from the Access Contr
184158
| Actorless schedule without a workflow grant | Fail |
185159
| Actor with no enrollment and no explicit grant | Fail |
186160

187-
There is no silent fallback from a requested all-credentials mode because the block has no caller-controlled access mode. It simply returns the set authorized for the current execution.
161+
There is no silent fallback from a requested all-credentials mode because the block has no caller-controlled access mode. Authorization happens when the selected credential is assumed.
188162

189163
Manual testing exercises the actor path with the tester's credential. Full group-wide behavior is tested through a deployed execution, preferably against a staging Credential Group in a forked workspace.
190164

@@ -197,7 +171,7 @@ Slack signature and installation verified
197171
-> webhook Principal with (teamId, userId) subject
198172
-> workflow execution
199173
-> Credential Group enrollment resolution
200-
-> actor-scoped credential list/use
174+
-> actor-scoped credential use
201175
```
202176

203177
The Slack trigger subscription credential only receives events. It is not the external user's downstream credential.
@@ -208,7 +182,7 @@ Bot events and events without a verified human subject have no actor. They requi
208182

209183
Credential Group creation, options, invitations, enrollment lifecycle, and resource-policy changes remain control-plane operations requiring current workspace-admin authorization and audit.
210184

211-
Managing the policy does not automatically grant the administrator permission to list or use credentials. Data-plane access still requires actor ownership or an explicit grant.
185+
Managing the policy does not automatically grant the administrator permission to use credentials. Data-plane credential use still requires actor ownership or an explicit grant.
212186

213187
## Current implementation delta
214188

@@ -217,12 +191,12 @@ The branch:
217191
- threads the original Principal into Credential Group executor delegation;
218192
- removes caller-supplied email filtering from credential listing;
219193
- resolves a verified Sim user to an exact active enrollment;
220-
- filters list pagination by that enrollment;
194+
- lists bounded non-secret references for every active credential in the group;
221195
- rechecks the same enrollment when resolving managed OAuth tokens;
222196
- fails actorless execution instead of substituting a billing or workflow owner.
223197
- stores and evaluates generic allow-only resource policies;
224198
- resolves user, workspace-role, Access Control Group, external-identity, and deployed-workflow subjects;
225-
- grants whole-group list and use access when an explicit policy matches;
199+
- grants whole-group credential use when an explicit policy matches;
226200
- binds Slack's verified provider subject to an active enrollment;
227201
- exposes an audited, optimistic-concurrency admin management API and structured Access tab.
228202

design/resource-policies.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,21 +100,15 @@ Policies store stable subject references, never session IDs, API keys, delegatio
100100
"type": "workflow",
101101
"workflowId": "wf_support"
102102
},
103-
"actions": [
104-
"credential_groups.credentials.list",
105-
"credential_groups.credentials.use"
106-
]
103+
"actions": ["credential_groups.credentials.use"]
107104
},
108105
{
109106
"id": "support-admins",
110107
"subject": {
111108
"type": "access_control_group",
112109
"accessControlGroupId": "pg_support_admins"
113110
},
114-
"actions": [
115-
"credential_groups.credentials.list",
116-
"credential_groups.credentials.use"
117-
]
111+
"actions": ["credential_groups.credentials.use"]
118112
}
119113
]
120114
}
@@ -261,7 +255,7 @@ Routes, blocks, tools, Copilot adapters, and executor handlers do not query poli
261255

262256
## Current implementation
263257

264-
The generic policy document, strict parser, optimistic repository, subject evaluator, and subject-management validation are implemented. Credential Groups are the first resource type and currently expose one managed permission in the UI: list and use every credential in the group.
258+
The generic policy document, strict parser, optimistic repository, subject evaluator, and subject-management validation are implemented. Credential Groups are the first resource type and currently expose one managed permission in the UI: use every credential in the group.
265259

266260
Future resource types extend the exact resource/action registry and call the same evaluator from their application operations. Knowledge Base/table enforcement and log-provenance redaction remain separate follow-up work.
267261

@@ -288,7 +282,7 @@ Provenance and viewer-specific log shielding are a later phase. They use the sam
288282
1. Add strict policy types, validation, storage, and evaluator.
289283
2. Add admin policy read/write application operations and audit.
290284
3. Add deployed-workflow authority to execution identity.
291-
4. Integrate Credential Group list and use authorization.
285+
4. Integrate Credential Group credential-use authorization.
292286
5. Add user, workspace-role, and Access Control Group subject resolution.
293287
6. Add policy management UI on Credential Groups.
294288
7. Extend the same evaluator to Knowledge Bases and tables.

0 commit comments

Comments
 (0)