Outcome
By the end of this lesson, you will be able to configure team-scoped access using the allResources and resourceIds fields, distinguish team scoping from role scoping, and reason about the three-state scope model (nil / empty / list).
| Prerequisites | M4.1.L1-L3 (policy table, system roles, custom roles) |
| Time | 9 minutes |
1. Concept
A role says what somebody may do. A team scope says what to.
They are separate questions, and mixing them up is the most common permissions mistake we see. Somebody grants a broader role because a person could not see something, when the thing they could not see was a scope problem and the role was already right.
Team scoping restricts a user’s effective policy set to a subset of the org’s resources. A user who holds Editor as their role on the platform team can apply Editor actions only to resources assigned to that team; the rest of the estate is invisible to them. The role has to come through the team membership: a role assigned to the user org-wide covers every resource and overrides the team scope.
SCOPE FLAG MEANING───────────────────────────────────────────────────────allResources=true The user can act on ALL resources in the org. Used for cross-team roles (Admin, FinOps Lead).
allResources=false The user is restricted to a specific set. Then resourceIds[] determines that set.
resourceIds=[...] The explicit list of resource UIDs the user can see and (per role) act on. Computed at request-time from team membership.How team-scoped access works in practice
A typical setup:
USER: jane@platform.comTEAMS: [platform], with Editor as her role on that team (no org-wide role)SCOPE FIELDS (computed): allResources = false resourceIds = [resources assigned to the platform team] + [members of resource groups assigned to platform]
EFFECT when Jane logs in: Inventory page filtered to platform's resources only Recommendations only on platform's resources Audit log whole org (not narrowed by team) Org-wide rollups hidden or aggregated without other teams Cost dashboards scoped to platform spendThe scope follows from team membership. Resources assigned to the platform team (or sitting in a resource group assigned to it) are scoped to Jane. Tags play no part. A new resource becomes visible to Jane once someone assigns it to the team or adds it to one of the team’s groups; no role re-assignment needed.
The three-state scope model
ZopNight’s scope field has three valid states, each with distinct database-level behavior:
STATE SQL EQUIVALENT MEANING──────────────────────────────────────────────────────────────────nil (null) no WHERE clause User sees everything. Equivalent to allResources=true.
[] (empty) WHERE id IN () User sees nothing. Total isolation. Happens for a member of a team that has no resources or groups yet.
[uid1, uid2,…] WHERE id IN (uid1,...) User sees only listed resources. The normal team-scoped state.The empty-list state is intentional: it is the safe default for a team member whose team owns nothing yet. They can log in and see the product structure, but no data leaks until resources or groups are assigned to the team. Without this state, an empty team would either grant everything (insecure) or fail logins (bad UX). Note the opposite case: a newly invited user given a role directly, with no team, gets nil and sees everything.
Team scoping vs custom roles
PROBLEM SOLUTION──────────────────────────────────────────────────────────────"Engineer can do Editor actions Team scoping. Role stays Editor; but only on their team's stuff" scope restricts the data.
"Engineer can do Editor actions Custom role. Edit the actions, but cannot apply recommendations" not the data scope.
"Engineer can do Editor actions BOTH. Custom role for the action on their team's stuff and cannot restriction; team scope for the apply recommendations" data restriction.This split is what makes the model composable. The role describes capability; the scope describes visibility. Combining them lets you express arbitrary “role × team” matrices without exploding role count.
Cross-team users
For practitioners who legitimately need to span multiple teams (a FinOps lead reviewing all teams’ spend, a security auditor investigating an incident across teams):
OPTION 1: Multiple team memberships jane@finops.com teams: [platform, product, data] resourceIds = union of all three teams' resources
Pro: bounded view (still excludes unassigned teams) Con: must add team manually as new teams are created
OPTION 2: Admin role jane@finops.com role: Admin allResources = true
Pro: automatic visibility of new teams Con: also grants RBAC-management capability; usually too broad for FinOps practitioners
OPTION 3: Custom "Cross-team Viewer" role Custom role with view-only across operational entities Combined with allResources=true
Pro: cross-team visibility without RBAC-management power Con: requires custom-role design (L3)Most customers settle on option 3 for FinOps leads and option 2 for true org admins. Option 1 is brittle but occasionally right when a practitioner truly only needs two or three teams.
Resource discovery and team scope
When a new resource is discovered (cloud-account sync), it belongs to no team. It joins a team’s scope in one of two ways:
WAY WHERE──────────────────────────────────────────────────────────1. Assigned to the team directly Team page, assign resources2. Member of a group the team owns Resource group assigned to the team(otherwise) Stays in the org-wide poolTags and cloud accounts do not assign team scope. Unassigned resources are visible only to users with allResources=true. This is intentional: it surfaces ownership gaps. A team-scoped user simply does not see the unassigned resource until someone assigns it. Assigning groups rather than single resources is the low-effort path: anything later added to the group comes along.
How ZopNight uses the scope model
The two checks happen in different places:
client → gateway policy check (role → can action), 403 if denied → backend scope lookup (which resources may this user see?) and the query is filtered to that setThe gateway only answers “may this user take this action at all?”. Each backend service then asks for the user’s scope and filters its own query, so an out-of-scope resource simply is not in the results. The role-permission 403 fires when the user is trying an action they cannot perform anywhere, not when the resource is outside their scope.
2. Demo
A mid-size org’s structure after a clean team scope design:
ORG: Acme Corp─────────────────────────────────────────────────────────────TEAMS: platform (6 engineers + 1 lead) scope=platform product (12 engineers + 1 lead) scope=product data (4 engineers + 1 lead) scope=data
CROSS-TEAM ROLES: finops-lead (1 person) Custom "Cross-team Viewer" + recommendation:update on all security (2 people) Custom "Compliance Auditor" + audit-log:view on all org-admin (2 people) Admin (allResources=true)
EFFECT: Platform engineer sees only platform's resources FinOps lead sees all teams' resources, can apply recs Security can view all audit logs, modify nothing Org admins can do everything (rotation, role mgmt)
UNASSIGNED RESOURCES: Visible to org-admins and security; invisible to team-scoped users Surfaces ownership gaps automatically3. Hands-on (6 min)
Map your org’s structure:
TEAMS (list): __________________________________________________________
PER-USER scope decisions: USER ROLE SCOPE (team / cross / all) __________________ ___________ ___________________________ __________________ ___________ ___________________________ __________________ ___________ ___________________________
CROSS-TEAM USERS: for each, choose Option 1/2/3 from the concept: __________________ Option: ___ (why?) __________________ Option: ___ (why?)
UNASSIGNED RESOURCES (estimated count): _____ Action plan: assign them to a team or accept them in the org-wide poolThe unassigned-resource count is the team-scope hygiene number to track quarterly. Healthy orgs trend toward zero.
4. Knowledge check
Q1
A user is a member of the platform team (Editor on that team) and has no org-wide role. What do they see?
A. Only resources assigned to platform, directly or through the team’s resource groups
B. Nothing at all, since team members are restricted by default
C. Every resource in the whole of the organisation, without any exception
D. Only those resources that they themselves originally provisioned themselves
Show answer
Correct: A. resourceIds is computed at request time from the team’s assigned resources plus the members of its assigned groups. Tags play no part. A new resource becomes visible once it is assigned to the team or added to one of its groups.
Q2
A FinOps lead needs to see cost across all teams but should not manage RBAC. Best approach:
A. Grant them the full Admin role right across every team in the organisation
B. Multiple team memberships, one for every single team in the whole organisation at once
C. The Editor role, with a manual filter applied on every single report
D. Custom “Cross-team Viewer” role (view-only across operational entities) with allResources=true
Show answer
Correct: D. The role excludes role/user/cloud-account management. Scope grants visibility; role limits capability. The combination of a narrow custom role and broad scope is the cleanest way to express “broad view, narrow capability.” Admin would over-grant; multi-team is brittle as teams are added.
Q3
The scope state [] (empty list) means:
A. That all of the resources are visible
B. Total isolation: user sees no resources
C. A bug in the scope computation code
D. It is precisely equivalent to nil
Show answer
Correct: B. It happens for a member of a team that has no resources or groups assigned yet. It produces a WHERE id IN () query: valid SQL, zero results. Different from nil (no filter, sees everything), which is what a user with a directly assigned role and no team gets.
5. Apply
Manage team scopes on the Teams page. Add users to a team with a role on that team; assign resources to the team, or assign resource groups so their members come along.
Quarterly hygiene: review the count of resources assigned to no team. Trending up is a signal of weakening team-scoping discipline.
Related lessons
- L3: Custom roles
- L5: Scope states (nil / empty / list) (next: covers the three-state model in depth)
- L6: Frontend gating with usePermission
- T4.M4.5.L2: Team attribution