The role that both issues and approves.
CC6.3 is the SOC 2 criterion about what each role is allowed to do. Behind it is a simple question: when someone gets access to data or a function, is that grant decided by a role with a defined job, or by circumstance? The criterion wants the answer to come from roles and responsibilities, with two ideas doing the work: least privilege, a role carries what its job needs and no more, and segregation of duties, which looks for incompatible steps sitting with one person where the access model treats them as such, like issuing and approving the same invoice. That mapping of duties to roles is not only an org-chart question; it lives in code: a permissions matrix, a policy config, a role check that decides what a signed-in user may do.
The shapes the same control failure takes.
CC6.3 weakens in the thing that maps duties to roles, usually with a small edit that looks like an unblock. The recurring shapes:
A role gains a permission that conflicts with one it holds
The role that issues an invoice or a refund also gains the right to approve it, so the maker-and-checker pairing segregation of duties relies on collapses inside a single role.
A role gains what its job does not need
A permission is added for convenience (support gets bulk export, a read-only role gains delete), and the role now covers actions unrelated to its purpose.
Roles inherit duties nobody assigned
A permission arrives through broad inheritance (a super-group, a default role, an everything-granting membership) instead of being assigned to the role that needs it, so the matrix reads cleaner than the system behaves.
Two roles fold into one
A cleanup or reorg merges the two roles a flow depends on being separate, and the combined role holds both halves of the control.
A temporary duty-widening becomes standing
A role is widened to cover someone being away or a one-off need, with no expiry and no owner who revisits it, so the widened duties stay after the reason for them is gone.
An editor role quietly gains approval rights.
A finance module has three roles: viewer, editor, and approver. Editors issue invoices, approvers approve and refund them. The approver is out for the week and a queue of invoices is stuck, so a small PR adds invoice:approve to the editor role to unblock the flow, merged with a comment saying it is temporary. Nothing in the diff expires the grant or restores the matrix.
const ROLE_PERMISSIONS = { viewer: ["invoice:read"],- editor: ["invoice:read", "invoice:issue"],+ editor: ["invoice:read", "invoice:issue", "invoice:approve"], // OPS-4489: approvals while finance is out approver: ["invoice:read", "invoice:approve", "refund:issue"],};This adds invoice:approve to the editor role. Editors already hold invoice:issue, so the role that issues invoices can now also approve them, which puts both halves of the invoice flow inside a single role, the pairing segregation of duties is meant to keep apart. Whether the same person actually sits on both sides depends on who holds which role and whether anything else blocks the combination, and the diff cannot show that: it cannot show the user-to-role assignments, whether another layer enforces the separation at transaction time, or whether the change itself was reviewed against the access model. The comment marks the grant temporary, and nothing in the diff expires it or puts the matrix back. Remove invoice:approve from the editor role, or make it expire in code and restore the matrix once finance is back.
CC6.3 is tested through the role design and who holds it.
A SOC 2 auditor testing CC6.3 looks at the role design and how it is populated: which duties map to which roles, who actually holds the conflicting ones, whether role and permission changes were authorized during the period, and whether access reviews catch someone sitting on both sides of a flow that should have two people. In a codebase the permission matrix is one of the artifacts behind that picture, and its change history is supporting evidence; a one-line grant that lets a role both issue and approve is exactly the exception this test is built to find, and it entered through a diff.
A review, not your access model.
heygrc flags changes that loosen role-based authorization and cites the criterion so the fix happens in the pull request. It does not know your org chart, who holds which role today, or whether a compensating control exists. It catches the moment the role map is loosened, at the diff.