Skip to content

Guard role grant emission behind WillSyncResourceType - #33

Merged
laurenleach merged 1 commit into
mainfrom
lauren/guard-role-grant-emission
Aug 29, 2026
Merged

Guard role grant emission behind WillSyncResourceType#33
laurenleach merged 1 commit into
mainfrom
lauren/guard-role-grant-emission

Conversation

@laurenleach

Copy link
Copy Markdown
Contributor

Gates cross-type grant emission from the user syncer on the customer's sync
filter, so grants aren't emitted for a resource type the sync excludes.
Reference: ConductorOne/baton-linear#55.

userResourceType.Grants() emits the role grants (read from each admin user's
roleName in the pass it already makes, rather than re-scanning every user once
per role -- which is why resourceTypeRole already carries SkipGrants). When
role is excluded from the sync those grants would point at an unsynced type, so
newUserBuilder clones the user resource type and annotates it
SkipEntitlementsAndGrants and the SDK skips the pass entirely. Users have no
entitlements of their own, so the unfiltered case is annotated SkipEntitlements.

The flag is named skipRoleResourceType and stored inverted so the zero value
means "sync everything".

baton_capabilities.json is regenerated: the user type now advertises
SkipEntitlements in the unfiltered capability set.

Build, tests, and golangci-lint (0 issues) pass.

Supersedes #31, which used a syncRoles flag instead of the skip<Type>ResourceType
convention.

Gates cross-type role-grant emission from the user syncer on the customer's
sync filter, so grants aren't emitted for a resource type the sync excludes.

The user builder clones the user resource type and annotates it
SkipEntitlements, or SkipEntitlementsAndGrants when role is filtered out --
users have no entitlements of their own and their only grants are the
cross-type role grants, so the whole pass can be skipped.

The flag is named skipRoleResourceType and stored inverted so the zero value
means "sync everything".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment on lines +44 to +53
if rt.GetId() != resourceTypeUser.GetId() {
continue
}
if hasGuardAnno(rt, &v2.SkipEntitlementsAndGrants{}) {
t.Fatal("zero-value Connector advertised SkipEntitlementsAndGrants")
}
}
}

// RoleResourceTypeID is what the sync filter is queried with; it must match the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: this test passes vacuously if the user syncer ever disappears from ResourceSyncers — the loop continues on every non-user type and then asserts nothing. Consider tracking a found bool and failing with t.Fatal("user syncer not registered") when it stays false, so the guard can't silently stop testing anything.

@github-actions

Copy link
Copy Markdown
Contributor

Connector PR Review: Guard role grant emission behind WillSyncResourceType

Blocking Issues: 0 | Suggestions: 2 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base 4bb976f09761.
Review mode: full
View review run

Review Summary

Scanned the full PR diff (5 files, no go.mod/go.sum changes) for security and correctness. The guard is sound: userResourceType.Grants() emits only cross-type role grants and Entitlements() returns nil, so annotating the cloned user resource type SkipEntitlementsAndGrants when role is filtered out — and SkipEntitlements otherwise — matches what pkg/sync/syncer.go (shouldSkipEntitlements/shouldSkipGrants) actually consults, and group/app grants that use users as principals are unaffected. proto.Clone correctly avoids mutating the package-level resourceTypeUser, the inverted flag keeps the zero-value Connector unfiltered, and baton_capabilities.json matches the unfiltered path taken by NewLambdaConnector when cliOpts has no filter. Only two minor non-blocking items.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connector/role.go:35ResourceType: "role" still hardcodes the literal that the new RoleResourceTypeID const was introduced to eliminate; TestRoleResourceTypeIDMatches does not cover this site.
  • pkg/connector/users_guard_test.go:44-53TestZeroValueConnector_DoesNotSkipGrants passes vacuously if the user syncer is ever dropped from ResourceSyncers, since the loop asserts nothing when no user type is found.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/connector/role.go`:
- Around line 35: The resource ID literal ResourceType: "role" duplicates the string
  that the newly added RoleResourceTypeID constant (pkg/connector/resource_types.go:11)
  exists to centralize. Replace the literal with resourceTypeRole.Id (or
  RoleResourceTypeID) so all references to the role resource type ID come from one
  place. Note that pkg/connector/users.go:73 already uses resourceTypeRole.Id, so
  this is the last remaining duplicate.

In `pkg/connector/users_guard_test.go`:
- Around line 44-53: TestZeroValueConnector_DoesNotSkipGrants iterates the syncers and
  continues for every resource type that is not the user type, so if the user syncer
  were ever removed or renamed in ResourceSyncers, the test would pass without
  asserting anything. Add a found boolean variable, set it to true inside the user
  branch, and after the loop fail the test with t.Fatal when it is still false.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

@laurenleach
laurenleach merged commit b232adc into main Aug 29, 2026
8 of 9 checks passed
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