Skip to main content

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.

Entity classification mental model — the four authorization concepts (principal, permission grouping, permission, resource) across AWS, Entra ID, ServiceNow, GitHub and Kubernetes, highlighting that the word "role" lands in the PRINCIPAL column for AWS but the PERMISSION GROUPING column everywhere else.

Bottom line (for a client or executive):

  1. 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.
  2. You always see your native vendor terms in the UI ("IAM Role," "IAM Policy") — the abstract type is an engine detail underneath.
  3. 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):

ConceptPlatform entity_typeWhat it isThe question it answers
PrincipalidentityA credential-bearing actor that authenticates and is the subject of authorizationWho can act
Permission groupingrole / permission_setA named bundle of capabilities that a principal holdsHow authority is packaged
PermissionpermissionA single capability (one action / effect)What can be done
ResourceresourceThe thing acted uponTo 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 — it RUNS_AS an 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 group entity_type today; group-conveyed authority is made visible by flattening it onto the member identities — e.g. the AWS connector emits a direct HAS_ROLE edge 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:

  1. Authentication — does it carry or assume credentials? (long-lived keys, an assumed-role session, a client secret, a federated token)
  2. Subject of authorization — is it the entity the policy engine evaluates for? (the "who" in "is X allowed to do Y?")
  3. Actor in the audit log — can it appear as the actor in an access/audit record? (CloudTrail userIdentity, Entra sign-in log, ServiceNow sys_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 credsuserIdentity.type=AssumedRoleidentity
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 conceptAWSEntra ID / AzureServiceNowGitHubKubernetes
PrincipalidentityIAM Role, IAM User, Root, Identity Center userUser, Service Principal, Managed Identitysys_user, integration userUser, GitHub App, Actions OIDC identityServiceAccount, User
Permission groupingrole / permission_setIAM Policy (managed/inline), Identity Center Permission SetDirectory Role, App Role (Entra); RBAC role definition (Azure)ServiceNow RoleRepo/Org Role (incl. custom)Role / ClusterRole
PermissionpermissionPolicy statement (action+effect)Role action / Graph scopeACL / operationScope / fine-grained permissionRule (verbs)
ResourceresourceS3 bucket, DynamoDB table, LambdaMailbox, directory object; subscription (Azure)Table, recordRepo, secret, environmentPod, Secret, ConfigMap
Principal group (membership, off-path — see §1 note)IAM GroupSecurity group, M365 groupsys_user_groupTeamGroup (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 objectPlatform entity_typesubtype shipped by connectorStatus
IAM Roleidentityiam_role✅ Correct & intended
IAM Useridentityiam_user✅ Correct & intended
IAM managed policyrole (today)permission_set (planned)iam_policy✅ Correct now; type refinement planned
IAM inline policypermission (today)permission_set (planned)iam_inline_policy⚠️ Currently a different type than managed — being unified
Policy statementpermission
S3 / DynamoDB / Lambda / …resourceservice-specific

Managed vs inline today: the connector currently emits managed policies as role but inline policies as permission — the same concept (a permission grouping) on two different types. Both converge on permission_set under 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_TO and never inspect the grantor's entity_type. So the role-vs-permission_set label 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 identity nodes and cross-system paths traverse cleanly.
  • The UI should surface the subtype so a client sees their native vocabulary"IAM Role", "IAM Policy" — not the abstract identity / 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