Entity Classification — Identity vs Permission-Grouping (Cross-System)
The one-sentence rule: SecurityV0 classifies an entity by what it does, not by the word its source system calls it. The same word — most notably "role" — means opposite things in different systems, so we map on behavior.
This page exists because one classification reliably looks wrong on first contact:
- An AWS IAM Role appears with type
identity. - An AWS IAM Policy appears with type
role.
To anyone fluent in AWS this reads backwards. It is not a bug. This page is the engineering reasoning; the Bottom line box below is the client-safe summary, and the diagram is the artifact to put in front of a non-technical stakeholder. The short version: "role" is a homonym — in AWS it names a principal, in most other systems it names a permission grouping.
Bottom line (for a client or executive):
- We classify every entity by behavior, so identities are directly comparable across AWS, Entra ID, ServiceNow, GitHub and more — that's what makes cross-system attack paths visible.
- You always see your native vendor terms in the UI ("IAM Role," "IAM Policy") — the abstract type is an engine detail underneath.
- None of this changes which risks or access paths we detect. A label being abstract never hides a real path.
1. The four authorization concepts
Every authorization system, regardless of vendor, is built from four building blocks. SecurityV0 normalizes every connector onto these (see Data Model):
| Concept | Platform entity_type | What it is | The question it answers |
|---|---|---|---|
| Principal | identity | A credential-bearing actor that authenticates and is the subject of authorization | Who can act |
| Permission grouping | role / permission_set | A named bundle of capabilities that a principal holds | How authority is packaged |
| Permission | permission | A single capability (one action / effect) | What can be done |
| Resource | resource | The thing acted upon | To what |
The authority path is always the same shape (01-data-model.md):
Identity ─HAS_ROLE→ Role/PermissionSet ─GRANTS→ Permission ─APPLIES_TO→ Resource
(who) (how it's bundled) (what) (to what)
A fifth type,
workload, is executable code that does not authenticate itself — itRUNS_ASan identity (e.g. a Lambda function, a ServiceNow Business Rule). Workloads are the "what runs," identities are the "who it runs as." That distinction is covered in the data model; this page is about the principal vs grouping axis.
Off-path: principal groups (membership). A group of principals — AWS IAM Group, Entra security / M365 group, ServiceNow
sys_user_group, GitHub Team, Kubernetes Group — is neither a principal (a group cannot authenticate or appear as an actor in an audit log) nor a permission grouping (it is not itself a named capability-bundle type, and never authenticates or acts — even when a group is directly granted a role, e.g. an Entra security group assigned an App Role, the authority resolves to its members). It is a collection of identities on a membership axis, off the authority path. The platform has no first-class groupentity_typetoday; group-conveyed authority is made visible by flattening it onto the member identities — e.g. the AWS connector emits a directHAS_ROLEedge from each member to the policies their groups attach (sv0-connectors#234). So in the table below, groups appear on their own row, not in the principal or grouping columns.
2. The litmus test: is this thing an identity (principal)?
An entity is an identity / principal if and only if all three are true:
- Authentication — does it carry or assume credentials? (long-lived keys, an assumed-role session, a client secret, a federated token)
- Subject of authorization — is it the entity the policy engine evaluates for? (the "who" in "is
Xallowed to doY?") - Actor in the audit log — can it appear as the actor in an access/audit record? (CloudTrail
userIdentity, Entra sign-in log, ServiceNowsys_created_by)
If it instead is a bundle of permissions you attach to a principal, it is a permission grouping (role / permission_set), not an identity.
Applied to AWS
| Authenticates? | Subject of authz? | Actor in CloudTrail? | → Verdict | |
|---|---|---|---|---|
IAM Role (en-default-data-cross-account-trust) | ✅ assumes STS creds | ✅ | ✅ userIdentity.type=AssumedRole | identity |
IAM Policy (AmazonDynamoDBFullAccess) | ❌ | ❌ (it is what is evaluated) | ❌ | permission grouping |
An IAM role passes all three. An IAM policy fails all three. The classification follows the behavior, and AWS's own vocabulary is the only thing that makes it feel surprising.
3. "Role" is a homonym — the cross-system mapping
Read the two middle rows across the columns below. In Entra ID, ServiceNow, GitHub and Kubernetes, the vendor's word "role" denotes a permission grouping. In AWS, the vendor's word "role" denotes a principal, and AWS's permission grouping is the "policy".
| Platform concept | AWS | Entra ID / Azure | ServiceNow | GitHub | Kubernetes |
|---|---|---|---|---|---|
Principal → identity | IAM Role, IAM User, Root, Identity Center user | User, Service Principal, Managed Identity | sys_user, integration user | User, GitHub App, Actions OIDC identity | ServiceAccount, User |
Permission grouping → role / permission_set | IAM Policy (managed/inline), Identity Center Permission Set | Directory Role, App Role (Entra); RBAC role definition (Azure) | ServiceNow Role | Repo/Org Role (incl. custom) | Role / ClusterRole |
Permission → permission | Policy statement (action+effect) | Role action / Graph scope | ACL / operation | Scope / fine-grained permission | Rule (verbs) |
Resource → resource | S3 bucket, DynamoDB table, Lambda | Mailbox, directory object; subscription (Azure) | Table, record | Repo, secret, environment | Pod, Secret, ConfigMap |
| Principal group (membership, off-path — see §1 note) | IAM Group | Security group, M365 group | sys_user_group | Team | Group (RBAC group claim) |
The platform's type name role inherited the Entra/ServiceNow meaning (permission grouping). That meaning collides head-on with AWS's role (principal). Same five letters, opposite concept. The engine resolves the collision by mapping AWS's IAM Role to identity and AWS's IAM Policy to the grouping type.
Notes on the table. (1) The Entra ID / Azure column folds two authorization planes for brevity — Entra ID directory roles and Azure RBAC/ARM roles are governed by different engines and scopes; the cells tag which plane each example belongs to. (2) The Principal group row is the membership axis from §1 — those entities are collections of identities, not principals or permission groupings, and the platform flattens their authority onto members rather than modeling a group node. (3) Coverage: AWS and Entra ID / ServiceNow are ingested by connectors today; GitHub is on the connector roadmap; Kubernetes is included to show the model generalizes, not as a current connector.
4. AWS specifics — why the labels are correct, and the one refinement planned
Every AWS mapping below is behaviorally correct today. One of them (IAM Policy) sits on a planned refinement to a more precise type — but the current label is a deliberate, validated choice, not a defect:
| AWS object | Platform entity_type | subtype shipped by connector | Status |
|---|---|---|---|
| IAM Role | identity | iam_role | ✅ Correct & intended |
| IAM User | identity | iam_user | ✅ Correct & intended |
| IAM managed policy | role (today) → permission_set (planned) | iam_policy | ✅ Correct now; type refinement planned |
| IAM inline policy | permission (today) → permission_set (planned) | iam_inline_policy | ⚠️ Currently a different type than managed — being unified |
| Policy statement | permission | — | ✅ |
| S3 / DynamoDB / Lambda / … | resource | service-specific | ✅ |
Managed vs inline today: the connector currently emits managed policies as
rolebut inline policies aspermission— the same concept (a permission grouping) on two different types. Both converge onpermission_setunder the migration below; the inconsistency is tracked as part of the connector cleanup.
The intended AWS authority model per ADR-014 is:
workload → runtime role (identity) → permission_set → permission → resource
…i.e. an IAM Policy should be a permission_set, attached via HAS_PERMISSION_SET. Today the connector emits the policy as role via HAS_ROLE as a deliberate stand-in, because the permission_set / HAS_PERMISSION_SET traversal is not yet chain-visible end-to-end (the same materializer gap that retired the original demo-aws seed). This is tracked in sv0-platform#1390 (AWS IAM modeling roadmap).
Why this is safe in the meantime: authority traversal is edge-driven — the path materializer and chain builder walk
HAS_ROLE → GRANTS → APPLIES_TOand never inspect the grantor'sentity_type. So therole-vs-permission_setlabel does not change which authority paths are discovered; it only changes the word on the node. The fix is about clarity and ADR-014 conformance, not about correctness of findings.
5. We already keep the precise vendor type
Every connector preserves the exact source-system type in a subtype property even when the abstract entity_type is coarser — but the property key currently varies by connector: subtype for AWS resources/policies, identitySubtype for Entra/ServiceNow identities, workloadSubtype for workloads. (Today the platform's reclassification reads the typed keys, not bare subtype; normalizing all connectors onto one canonical subtype field is tracked separately — see Connector Interface.) Live AWS scans show the AWS shape:
// IAM Role
{ "nodeType": "identity", "properties": { "subtype": "iam_role" } }
// IAM Policy
{ "nodeType": "role", "properties": { "subtype": "iam_policy", "policyType": "managed" } }
This is the key to making the model crystal clear to clients without sacrificing the cross-system graph:
- The engine reasons in the abstract four-concept model so an Entra Service Principal, an AWS IAM Role, and a GitHub Actions identity are all comparable
identitynodes and cross-system paths traverse cleanly. - The UI should surface the
subtypeso a client sees their native vocabulary — "IAM Role", "IAM Policy" — not the abstractidentity/role. (UI subtype-surfacing is tracked separately; the data is already present.)
Two layers, one truth: native words on the surface, normalized concepts underneath.
6. FAQ
Q: An IAM Role shows as identity. Isn't that wrong?
No. An IAM role authenticates (assumes STS credentials), is the subject of policy evaluation, and is the actor in CloudTrail — it passes all three legs of the litmus test. It is a principal. The surprise is purely the AWS word "role," which does not mean "permission grouping" the way it does in Entra/ServiceNow.
Q: An IAM Policy shows as role. Isn't that wrong?
The concept is right (a policy is a permission grouping), but the target type is permission_set, not role. The current role is a temporary, behavior-equivalent stand-in (sv0-platform#1390). It does not affect which findings or paths are produced.
Q: Will re-scanning fix the labels?
No. Both are platform-side modeling/UI matters, not connector data errors. The connector already fetches and ships the correct data, including the precise subtype.
Q: Where do groups (IAM Group, Entra security group, GitHub Team) go?
On the membership axis, not the authority path — they're collections of identities, not principals and not permission groupings (see the §1 note). The platform has no first-class group type today, so a member's group-conveyed authority is flattened onto the member (e.g. the AWS connector emits a direct HAS_ROLE from each member to the policies their groups attach, sv0-connectors#234). The member's effective access is therefore complete; the group node itself just isn't drawn.
Q: How do I decide for a brand-new connector?
Run the litmus test. If the thing authenticates and is the subject of authz → identity. If it is a named bundle of capabilities attached to a principal → role / permission_set. Map on behavior; record the vendor's word in subtype.
References
- Data Model — the canonical type system and authority path
- Platform Mental Model — connector-discovery → platform-storage bridge
- Connector Interface —
NormalizedNodeTypecontract andsubtype - ADR-014: AWS IAM Policy Entity Type — AWS
permission_set/HAS_PERMISSION_SETauthority model - sv0-platform#1390 — AWS IAM modeling roadmap (IAM Policy →
permission_setmigration)