Data Model
Overview
The SecurityV0 data model represents the execution/authority graph — a directed graph of 9 entity types connected by typed relationships. The graph answers two fundamental questions:
- What can this identity do? (authorization path)
- How does this workload reach external systems? (execution chain path)
It also tracks who is accountable for each identity (ownership) and how authority has changed over time (temporal drift).
Key distinction — identity vs workload: An identity (NHI — non-human identity) is a credential-bearing account that authenticates and acts in a system — e.g., an Azure Service Principal, an OAuth App registration, or a ServiceNow integration user. A workload is executable code that runs as an identity — e.g., a ServiceNow Business Rule that fires on incident creation, a Flow Designer workflow, or a scheduled data sync job. Workloads don't authenticate themselves; they delegate execution to an identity via RUNS_AS. This separation matters because one identity can be the execution context for dozens of workloads, and a single workload can chain through connections and credentials to reach a completely different identity in another system.
Terminology note: The entity type
workloadwas previously namedautomation. The rename (see ADR-010) aligns with cloud-native vocabulary and resolves naming collisions between the entity type and the broader "Automation Definition" concept used in W1 product documentation. The platform accepts both"workload"and"automation"asentity_type/NormalizedNodeTypevalues during the migration period."automation"is deprecated and will be removed in a future version.
Entity types at a glance
The 9 entity types fall into three functional groups:
| Group | Types | Role in the graph |
|---|---|---|
| Execution | workload, connection, credential | Define how code runs, where it connects, and how it authenticates |
| Authorization | identity, role, permission, resource | Define who authenticates, what authority they hold, and what they can reach |
| Governance | owner, execution_evidence | Track accountability and prove execution happened |
Full graph structure
┌──────────────┐ BELONGS_TO ┌──────────────┐ BELONGS_TO ┌──────────────┐
│ Owner │◀──────────────│ Owner │◀──────────────│ Owner │
│(business_unit)│ │ (team) │ │ (human) │
└──────────────┘ └──────┬───────┘ └──────┬───────┘
│ │
│ OWNED_BY (secondary) │ OWNED_BY (primary)
│ │
└───────────────┬──────────────┘
│
┌─────────────────────────────┤
│ │
▼ ▼
┌────────────┐ ┌──────────┐
│ Workload │ │ Identity │
│(exec logic)│ │ (NHI) │
└──┬───┬─────┘ └────┬─────┘
│ │ │
CALLS │ │ INVOKES ┌───────┼──────────────────────────┐
│ │ │ │ │
▼ ▼ HAS_ROLE AUTH_TO AUTHENTICATES_TO
┌──────────────┐ │ │ │
│ Connection │ ▼ ▼ ▼
│(outbound cfg)│ ┌──────────┐ ┌──────────────┐
└──────┬───────┘ │ Role │ │ Identity │
│ └─────┬────┘ │(target system│
USES │ │ e.g. SN) │
│ GRANTS └──────┬───────┘
▼ │ │
┌────────────┐ ▼ HAS_ROLE
│ Credential │ ┌────────────┐ │
│(auth material)│ │ Permission │ ▼
└──────┬───────┘ └─────┬──────┘ ┌──────────┐
│ │ │ Role │
AUTHENTICATES_AS APPLIES_TO │(target │
│ │ │ system) │
▼ ▼ └─────┬────┘
┌──────────┐ ┌────────────┐ │
│ Identity │ │ Resource │◀── APPLIES_TO ◀── GRANTS ──┘
└──────────┘ └────────────┘
Core paths
Two paths define execution authority:
-
Authorization path: Identity → Role → Permission → Resource — what an identity can do and where. Example: Service Principal
sp-hr-onboarding→ has rolehr_admin→ which grantsDataWriteonhr_casetable. For cross-system scenarios the path extends throughAUTHENTICATES_TO: SP in Entra ID → authenticates as integration user in ServiceNow → holds roles → reaches resources. -
Execution chain path: Workload → Connection → Credential → Identity — how executable code flows from trigger through outbound integration to a destination identity. Example: Business Rule "Auto-close incidents" → calls Script Include
RestApiUtils→ invokes REST MessageMicrosoft Graph API→ uses OAuth Profilegraph-oauth→ authenticates as Service Principalsp-graph-integrationin Entra ID → which holds roles → that grant permissions → on Azure AD resources.
These two paths converge at the identity node. A workload's blast radius is determined by following the execution chain to the identity it authenticates as, then following that identity's authorization path to the resources it can reach.
Core model
The main spine — execution chain through authorization path, plus ownership:
Runtime + engineering view
Cycles, self-referential edges, execution evidence, and cross-system links:
Entity Types
Entity Types (Internal)
NormalizedNodeType vs Internal entity_type: Connectors emit
human_identityas the NormalizedNodeType (per connector contract, 05-connectors.md). The platform normalizer maps this to internalentity_type: "owner". All other types are 1:1 between NormalizedNodeType and entity_type. The legacyautonomous_identitytype is accepted for backward compatibility and remapped during ingestion: subtypesservice_principal,oauth_app,machine_account,integration_user→identity; subtypesbusiness_rule,script_include,flow_designer_flow,scheduled_job,event_script,transform_map→workload; subtypesoauth_provider,oauth_profile→credential. The Entra-ServiceNow connector has been migrated to emit canonical types directly (Phase A2). New connectors should use the 9-type model. Legacyautonomous_identityacceptance will be removed in a future version.
| Type | Purpose | Examples | NormalizedNodeType | Subtypes |
|---|---|---|---|---|
identity | Authenticates and acts in systems | Service Principal, OAuth App, Machine Account | identity | IdentitySubtype |
workload | Defines execution logic | Business Rule, Script Include, Flow, Scheduled Job | workload (deprecated: automation) | WorkloadSubtype |
connection | Outbound integration configuration | REST Message, SOAP Message, HTTP Connection | connection | ConnectionSubtype |
credential | Authentication material | OAuth Provider, OAuth Profile, API Key, Certificate | credential | CredentialSubtype |
owner | Human user or group who owns/creates entities | Entra user, ServiceNow sys_user | human_identity (normalizer maps) | --- |
role | Permission grouping | Entra role, ServiceNow role | role | --- |
permission | Individual capability | ACL entry, Graph API scope | permission | --- |
resource | Data object being acted upon | Table, API endpoint, Repository | resource | --- |
execution_evidence | Proof of execution | Sign-in log, transaction log | execution_evidence | --- |
Identity
What it is: An autonomous (non-human) identity that authenticates and acts in a system without a human being present at the keyboard. This is the primary subject of SecurityV0 analysis. Identities hold credentials and authenticate to systems. They are distinct from workloads (which define executable logic but do not themselves authenticate).
Real-world examples:
| Example | Source System | Why it matters |
|---|---|---|
Azure Service Principal sp-payroll-sync that runs nightly Workday→ServiceNow payroll exports | Entra ID | If its owner leaves, payroll data still flows through an unaccountable pipe |
OAuth App snow-integration-prod that reads/writes HR cases in ServiceNow via client credentials | Entra ID + ServiceNow | No human session — acts autonomously with whatever roles it has |
GitHub App deploy-bot that triggers production deployments via org-level secrets | GitHub | Has write access to production infrastructure, may outlive the team that created it |
Machine account svc-monitoring that scrapes CMDB data every 5 minutes | ServiceNow | Accumulated 12 roles over 3 years, nobody remembers why |
| CI/CD OIDC identity that assumes an AWS IAM role for deployments | GitHub Actions + AWS | Transitive privilege chain: GitHub → AWS → S3/RDS access |
Key properties:
identity_subtype— service_principal, oauth_app, machine_account, integration_userexecution_mode—autonomous(no human trigger),operator_assisted(human initiates, automation executes independently),human_triggered(requires active human session),unknown. Replaces the olderexecution_typefield. See Glossary for canonical definitions.security_relevance—active_external(executing with external egress),dormant_authority(has authority but no recent execution),internal_inventory(internal-only, no binding, no execution). Computed from egress + execution evidence + identity binding. See Glossary.status— active, disabled, deletedlast_activity_at— when it last actually executed something (from logs)source_system— which platform this identity lives in
Prior to v2, all automation artifacts were classified as identity. See ADR-006 for the reclassification rationale.
Workload (formerly Automation)
What it is: An entity that defines executable behavior in a source system. Workloads do NOT authenticate — they use RUNS_AS to delegate execution to an identity. They can call other workloads (CALLS), invoke outbound connections (INVOKES), and are triggered by resources or events (TRIGGERS_ON).
Migration note: This entity type was previously named
automation. Theentity_typevalue in the database is"workload". The platform ingestion normalizer accepts"automation"as a deprecated alias during the migration period and remaps it to"workload". See ADR-010.
Real-world examples:
| Example | Source System | Why it matters |
|---|---|---|
| Business Rule that fires on incident insert to notify on-call team | ServiceNow | Executes automatically on every incident — if it RUNS_AS a privileged identity, that identity's authority is exercised without human intervention |
| Flow Designer automation for HR onboarding that creates cases and assigns tasks | ServiceNow | Complex multi-step workflow that touches HR, IT, and security domains — blast radius spans multiple tables |
| Scheduled Job that syncs CMDB data nightly from external source | ServiceNow | Runs on a schedule with no human trigger — if the run-as identity has drifted roles, the job inherits all of them |
| Script Include called by multiple Business Rules for REST API calls | ServiceNow | Shared code module — a single change affects all callers, multiplying blast radius |
Key properties:
workload_subtype— business_rule, script_include, flow_designer_flow, scheduled_job, event_script, transform_mapexecution_mode—autonomous,operator_assisted,human_triggered,unknownsecurity_relevance—active_external,dormant_authority,internal_inventorystatus— active, disabled, deletedlast_activity_at— when it last actually executed (from logs)source_system— which platform this workload lives in
Workload subtypes:
| Subtype | What it represents | Example |
|---|---|---|
business_rule | ServiceNow Business Rule | Script that fires on incident insert to notify on-call |
script_include | ServiceNow Script Include | Reusable server-side code called by other workloads |
flow_designer_flow | ServiceNow Flow Designer automation | HR Onboarding workflow that creates cases and assigns tasks |
scheduled_job | ServiceNow Scheduled Job / cron-style execution | Nightly CMDB sync that runs as system |
event_script | ServiceNow Event Script | Script triggered by a platform event |
transform_map | ServiceNow Transform Map | Data transformation during import set processing |
Connection
What it is: An outbound integration endpoint configuration. Connections define WHERE a workload sends data — the target URL, protocol, and authentication method. Connections use credentials (USES) to authenticate to external systems.
Real-world examples:
| Example | Source System | Why it matters |
|---|---|---|
| REST Message for Microsoft Graph API calls | ServiceNow | Defines the outbound HTTP endpoint — if misconfigured, data leaks to wrong target |
| SOAP Message for legacy ERP integration | ServiceNow | Legacy protocol with weaker security defaults — often overlooked in audits |
| HTTP Connection profile for monitoring webhook | ServiceNow | Outbound data flow — credentials embedded in connection config |
Key properties:
connection_subtype— rest_message, rest_method, soap_message, http_connectiontarget_url— where the connection sends dataauth_method— how the connection authenticates (oauth2, basic, api_key, certificate)status— active, disabled, deleted
Credential (updated)
What it is: Authentication material that an identity or connection uses to prove itself to a target system — an OAuth client secret, certificate, personal access token, API key, OAuth profile, or federation trust. Credentials have lifecycles (creation, expiry, rotation) and their state affects security posture.
Credentials in SecurityV0 carry cross-system context: they record which system issued the credential, which system it authenticates to, and what grant type is used. This enables tracking execution authority that flows across system boundaries.
With the entity reclassification, OAuth Providers and OAuth Profiles are now credential type entities (previously modeled as identity subtypes). They represent authentication material, not authenticating entities.
Key properties:
credential_subtype— oauth_provider, oauth_profile, api_key, certificate, client_secretcredential_type— oauth_client_secret, certificate, pat, api_key, oidc_token, federation_trust, role_assumptionstatus— active, expired, revoked, rotatedexpires_at— when the credential becomes invalidlast_used_at— when it was last used for authenticationcreated_at— age of the credential
For full cross-system properties and real-world examples, see the Credential detail section below.
Owner
What it is: An entity accountable for one or more autonomous identities. Owners don't execute — they authorize. SecurityV0 tracks owners to determine ownership state (is someone or some team still accountable for this identity?).
Owners are polymorphic — discriminated by owner_type:
| Owner Type | What it represents | Decay signal |
|---|---|---|
human | An individual person (employee, contractor) | Account disabled/deleted/departed |
team | A named group responsible for a set of integrations | Team disbanded or emptied |
business_unit | A department or organizational division | BU restructured or dissolved |
organization | A top-level org (for cross-org scenarios) | Org removed |
Real-world examples:
| Example | Owner Type | Status | Implication |
|---|---|---|---|
Jane Smith, Senior DevOps Engineer who created sp-payroll-sync 18 months ago | human | departed | The SP is now orphaned — no individual is accountable |
| IT-Automation-Team, a 4-person group that manages all ServiceNow integrations | team | active | Team exists and is accountable, even if the original creator left |
| Bob Chen, IT Admin who approved 5 ServiceNow integration roles for the OAuth app | human | active | He's still here, but did he know about the 3 additional roles added after his approval? |
| Data-Platform-BU, the business unit that owns all data pipeline automations | business_unit | active | BU-level accountability — less specific than a team, but still a valid escalation path |
| Alex Kim, Contractor whose AD account was disabled when contract ended | human | disabled | Account disabled but never deleted — SP still references this owner |
| Integration-Ops-Team, disbanded during a reorg 3 months ago | team | disbanded | All integrations this team owned are now orphaned at the team level |
Common properties (all owner types):
owner_type— human, team, business_unit, organizationdisplay_name— human-readable namestatus— active, disabled, deleted, departed, disbanded, restructuredsource_system— where this owner entity is defined (Entra ID, ServiceNow, etc.)dissolved_at— when the owner entity ceased to be valid
Type-specific properties:
For owner_type: "human":
email— for ownership attributionorg_unit— for context (does this person still work in the relevant team?)job_title— role contextdisabled_at/deleted_at/departed_at— when ownership effectively decayed
For owner_type: "team":
team_id— source system identifier (e.g., Entra group ID, ServiceNow assignment group sys_id)member_count— current active members (at last sync)lead_email— team lead for escalationdisbanded_at— when team was dissolved
For owner_type: "business_unit":
bu_id— source system identifierhead_email— BU head for escalationparent_bu_id— for hierarchy (nullable)restructured_at— when BU was merged or dissolved
For owner_type: "organization":
org_id— source system identifierprimary_contact— escalation path
Role
What it is: A named grouping of permissions in a source system. Roles are the mechanism by which identities receive capabilities. One identity can have many roles; one role can grant many permissions.
Real-world examples:
| Example | Source System | What it grants |
|---|---|---|
itil role in ServiceNow | ServiceNow | Incident/problem/change management access |
hr_admin role in ServiceNow | ServiceNow | Full CRUD on HR tables (cases, employee records) |
Application.ReadWrite.All in Entra ID | Entra ID | Can read and modify any application registration |
org-admin in GitHub | GitHub | Full administrative control over all repositories |
AmazonS3FullAccess IAM policy attached to a role | AWS | Read/write/delete any S3 bucket in the account |
Key properties:
role_name— human-readable identifier from the source systemrole_type— application, directory, cloud_iam, customis_privileged— whether this is an elevated/admin-level rolesource_system— which platform defines this role
Why roles matter for SecurityV0: Roles are where scope drift happens. An identity starts with one role, and over months accumulates more. Each role addition is legitimate in isolation, but the aggregate creates unexpected access.
Permission
What it is: A single, normalized capability — the ability to perform one action on one scope. Permissions are the atomic unit of access in SecurityV0. Every source system's permission model gets normalized to SecurityV0's 7 standard actions.
Normalized actions:
| Action | Meaning | Examples from source systems |
|---|---|---|
create | Make new records/resources | ServiceNow: insert on table; GitHub: create repo; AWS: CreateBucket |
read | View/query existing data | ServiceNow: read on table; Entra: User.Read.All; AWS: GetObject |
update | Modify existing records | ServiceNow: write on table; GitHub: push to branch; AWS: PutObject |
delete | Remove records/resources | ServiceNow: delete on table; GitHub: delete repo; AWS: DeleteBucket |
execute | Run/trigger actions | GitHub: trigger workflow; AWS: InvokeFunction; ServiceNow: run script |
admin | System-level configuration | Entra: manage app registrations; ServiceNow: modify ACLs |
delegate | Grant access to others | Entra: add role assignments; AWS: iam:PassRole; GitHub: manage teams |
Real-world examples:
| Permission | Normalized | What it means in practice |
|---|---|---|
ServiceNow ACL: incident.write | update on scope incident | Can modify incident tickets |
Entra: Mail.Send | execute on scope mail | Can send email as any user in the org |
AWS: s3:GetObject on arn:aws:s3:::payroll-data/* | read on scope s3/payroll-data | Can read every file in the payroll bucket |
GitHub: contents:write on repo infra-deploy | update on scope repo/infra-deploy | Can push code to infrastructure repo |
Key properties:
normalized_action— one of the 7 standard actionsscope— what the permission applies to (table name, resource ARN, repo name)source_system— where this permission is definedpermission_name— original name in the source system
Resource
What it is: Something that an identity can act upon — a table, API, repository, secret store, cloud resource, or workflow. Resources are classified by business domain and sensitivity to help assess blast radius.
Real-world examples:
| Resource | Type | Business Domain | Sensitivity | Why it matters |
|---|---|---|---|---|
ServiceNow hr_case table | table | hr | confidential | Contains employee grievances, termination records |
ServiceNow incident table | table | it_ops | internal | IT tickets — lower sensitivity but high volume |
ServiceNow customer_contact table | table | customer | confidential | Customer PII — regulated data |
GitHub repo infrastructure-as-code | repository | engineering | restricted | Contains cloud provisioning; write = deploy |
AWS S3 bucket payroll-exports-prod | cloud_storage | finance | restricted | Salary data for entire company |
ServiceNow sys_properties | table | security | restricted | System configuration — admin-level impact |
Business domain classification:
| Domain | Contains | Example tables/resources |
|---|---|---|
hr | Employee records, cases, PII | hr_case, hr_profile, sn_hr_core_case |
finance | Financial data, payroll, billing | cost_center, payroll_export, billing_account |
customer | Customer PII, contracts, cases | customer_contact, csm_case, contract |
it_ops | Incidents, changes, CMDB | incident, change_request, cmdb_ci |
security | Security config, audit, access control | sys_security_acl, sys_audit, sn_sec_* |
engineering | Code, deployments, infrastructure | repositories, workflows, cloud resources |
Key properties:
resource_type— table, module, api_endpoint, repository, secret, workflow, cloud_storagebusiness_domain— hr, finance, customer, it_ops, security, engineering, unknownsensitivity— public, internal, confidential, restrictedcontains_pii— boolean flag for regulated data
Credential (detail)
What it is: Authentication material that an identity or connection uses to prove itself to a target system — an OAuth client secret, certificate, personal access token, API key, OAuth profile, or federation trust. Credentials have lifecycles (creation, expiry, rotation) and their state affects security posture.
Credentials in SecurityV0 carry cross-system context: they record which system issued the credential, which system it authenticates to, and what grant type is used. This enables tracking execution authority that flows across system boundaries.
With the entity reclassification (v2), OAuth Providers and OAuth Profiles are now credential type entities. They represent authentication material — not authenticating entities. Connections USES credentials; credentials AUTHENTICATES_AS identities.
Real-world examples:
| Credential | Type | Issuing → Target | Risk signal |
|---|---|---|---|
OAuth 2.0 client secret for sp-payroll-sync | oauth_client_secret | Entra ID → ServiceNow | Owner departed but secret still valid and authenticating to ServiceNow every 15 minutes |
| OAuth Profile for Graph API integration | oauth_profile | ServiceNow → Entra ID | Stores client_id and token endpoint — the bridge between connection and identity |
| X.509 certificate for mutual TLS to ServiceNow | certificate | Corporate CA → ServiceNow | Expired but identity still active — is it using a different auth method? |
GitHub PAT created by a former employee, scoped to org:admin | pat | GitHub → GitHub | Token outlived the person's employment — still active? |
| OIDC federation trust: GitHub Actions → AWS | federation_trust | GitHub → AWS | Any workflow in the repo can assume an AWS role with S3 admin access |
| API key for monitoring integration, last rotated 2 years ago | api_key | ServiceNow → ServiceNow | Long-lived credentials with no rotation indicate operational neglect |
| AWS IAM role assumed by an Entra-federated identity | role_assumption | Entra ID → AWS | Cross-cloud privilege chain: Entra SP → AWS role → production resources |
Key properties:
credential_subtype— oauth_provider, oauth_profile, api_key, certificate, client_secretcredential_type— oauth_client_secret, certificate, pat, api_key, oidc_token, federation_trust, role_assumptionstatus— active, expired, revoked, rotatedexpires_at— when the credential becomes invalidlast_used_at— when it was last used for authenticationcreated_at— age of the credential
Cross-system properties:
issuing_system— which system issued/manages this credential (e.g., "entra_id", "github", "okta")issuing_entity_id— the specific entity in the issuing system (e.g., app registration ID)target_system— which system this credential authenticates TO (e.g., "servicenow", "aws")target_endpoint— specific endpoint URI (e.g., "https://instance.service-now.com")grant_type— protocol/grant used (e.g., "client_credentials", "oidc_federation", "saml_assertion", "role_assumption")scopes_granted— what scopes/permissions the credential itself carries (e.g., ["Mail.Read", "User.Read.All"])
Execution Evidence
What it is: An immutable, first-class record proving that an autonomous identity actually executed an action in a target system. Execution evidence distinguishes "this identity can do X" (authority, from the role/permission graph) from "this identity did do X" (execution, from source system logs). Without execution evidence, findings about active risk are claims without proof.
Real-world examples:
| Evidence | Source Table | What It Proves |
|---|---|---|
ServiceNow transaction log entry showing sn-integration-user called POST /api/now/table/incident at 14:00 | syslog_transaction | The identity actively executes against the incident table |
Flow Designer context record for flow HR Onboarding Workflow that ran at 09:00 | sys_flow_context | The automation is actively executing, not just configured |
Azure sign-in log entry for sp-hr-onboarding authenticating via client_credentials | signIns | The service principal actively authenticates |
| ECC queue entry showing MID Server task processed for discovery scan | ecc_queue | The MID Server identity executed a discovery action |
Key properties:
source_table— where the evidence was fetched from (e.g.,syslog_transaction,signIns)source_record_id— ID in the source system (for verification and audit trail)source_timestamp— when the execution actually occurredevidence_type— api_call, flow_execution, scheduled_job, sign_inaction— what was done (e.g., "POST /api/now/table/incident")target_resource— what was acted upon (e.g., "incident")outcome— success, failure, unknownpayload_hash— SHA256 of the source record content (integrity verification without storing raw data)
Relationship to other entities: Execution evidence records are linked to identities via entity_id. They provide the factual basis for EXECUTES_ON relationships and for findings that claim active execution (dormant authority detection relies on the absence of execution evidence).
Entity Classification Decision Tree
When a new entity is discovered by a connector, use this decision tree to determine the correct entity type:
Key distinctions:
- Identity vs Workload: If it authenticates (has credentials, appears in sign-in logs), it is an identity. If it defines executable logic but delegates authentication via RUNS_AS, it is a workload.
- Connection vs Resource: If it defines an outbound integration endpoint (URL, protocol, auth config), it is a connection. If it is data that gets read/written, it is a resource.
- Credential vs Identity: If it stores/manages authentication material (tokens, keys, certificates, OAuth profiles), it is a credential. If it uses that material to authenticate, it is an identity.
Subtype Definitions
export type IdentitySubtype =
| "service_principal" | "oauth_app" | "machine_account"
| "integration_user";
export type WorkloadSubtype =
| "business_rule" | "script_include" | "flow_designer_flow"
| "scheduled_job" | "event_script" | "transform_map";
/** @deprecated Use WorkloadSubtype */
export type AutomationSubtype = WorkloadSubtype;
export type ConnectionSubtype =
| "rest_message" | "rest_method" | "soap_message" | "http_connection";
export type CredentialSubtype =
| "oauth_provider" | "oauth_profile" | "api_key"
| "certificate" | "client_secret";
These subtypes are used in the identity_subtype, workload_subtype, connection_subtype, and credential_subtype properties respectively. They allow fine-grained classification within each entity type while keeping the top-level type system clean.
Finding
What it is: A deterministic detection — a rule-based trigger that fired because a specific condition is true in the graph. Findings are not predictions or risk scores. They are facts: "this condition exists."
Finding types:
| Type | Condition | Plain-language explanation |
|---|---|---|
orphaned_ownership | ALL owners (primary + secondary) have decayed status | "Nobody is accountable for what this identity does — not a person, not a team, not a business unit" |
ownership_degraded | Primary owner decayed, but secondary/inherited owner still active | "Direct accountability lost — only inherited team/BU ownership remains" |
dormant_authority | Identity has elevated permissions but no recent activity | "This identity can do powerful things but hasn't done anything in months — why does it still have access?" |
scope_drift | Roles/permissions expanded over time without re-approval | "This identity started with 2 roles and now has 7 — each was approved individually but nobody approved the aggregate" |
privilege_justification_gap | Elevated permissions with no evidence of need | "This identity has admin access but only ever reads data — why the elevated privilege?" |
unproven_execution | Workload can execute autonomously but no execution evidence can be deterministically linked | "This workload is configured to run autonomously, but we cannot prove it actually executes — evidence is absent or linkage fails" |
unknown_identity_binding | Workload has no deterministic RUNS_AS relationship, or the identity is not uniquely identifiable | "This workload has no deterministic identity binding — we cannot establish which identity it executes as" |
reachable_sensitive_domain | Identity/workload can reach a resource classified as confidential or restricted | "This identity can reach confidential HR data through its role assignments — a sensitive data domain is reachable" |
llm_egress | Workload has LLM egress classification (sends data to an AI/LLM endpoint) | "This workload sends data to an LLM gateway — autonomous AI-connected execution with external egress" |
external_egress | Workload has external egress classification (sends data outside the organization) | "This workload sends data to an external endpoint — autonomous execution with outbound data flow" |
ownership_ambiguous | Entity has only group/team owners; no individual has ever been assigned | "This workload is owned only by 'Platform-Team' — no specific person is accountable for its behavior" |
ownership_unknown | Insufficient deterministic metadata to determine ownership state | "Ownership edges exist but the target entities lack sufficient identifying information to validate accountability" |
W1 finding types: The types
unproven_execution,unknown_identity_binding,reachable_sensitive_domain,llm_egress,external_egress,ownership_ambiguous, andownership_unknownare W1-scope findings focused on autonomous execution exposure. The existing types (orphaned_ownership,ownership_degraded,dormant_authority,scope_drift,privilege_justification_gap) continue to operate across all wedges. W1 UX views filter to the W1-scope subset.Note on
ownership_ambiguous: This is a distinct finding type fromownership_degraded, not an extension.ownership_degraded= an individual owner was once assigned but decayed.ownership_ambiguous= the entity has only group/team owners and has never had an individual owner assigned (no specific person is accountable). See W1 logic.md section 5 and the ownership finding rules below.
Real-world examples:
Finding: Ownership Degraded
Service principal
sp-hr-onboarding(Entra ID) lost its primary owner. Sarah Chen (sarah.chen@contoso.com) departed on 2025-07-15. The secondary owner (IT-Automation-Team) is still active with 4 members. The SP continues executing every 15 minutes, authenticating to ServiceNow Production via OAuth client credentials.Remediation: Assign a new primary owner from IT-Automation-Team (suggested: team lead). New owner reviews current permissions vs. business need.
Finding: Orphaned Ownership
Service principal
sp-hr-onboarding(Entra ID) has no active owner at any level. Primary owner Sarah Chen departed 2025-07-15. Secondary owner IT-Automation-Team was disbanded 2026-01-15 during a reorg. The SP continues executing daily, accessing 4 ServiceNow tables across HR and IT domains through thesn-integration-useraccount. Last execution: 2026-01-22T14:00:00Z.Remediation: Assign new owner from IT-Operations-BU (parent business unit); review whether hr_agent_workspace and personalize roles are still needed; rotate client secret.
Key properties:
finding_type— which trigger firedstatus— active, acknowledged, remediated, false_positivedeterministic_explanation— plain-language, no interpretation neededentity_id— the identity this finding is aboutaffected_resources— resources in the blast radiusevidence_completeness— declares what evidence was available vs unavailable for this finding (see below)
Evidence Completeness
Every finding MUST declare what evidence was available and what was unavailable. This is core to the "deterministic, no inference" constraint — a finding that honestly declares its evidence gaps is more credible than one that silently omits them.
EvidenceAvailability values:
| Value | Meaning |
|---|---|
available | Evidence source was accessible and data was retrieved |
unavailable_not_enabled | Source exists but feature is not enabled (e.g., sys_audit_role requires glide.role_management.v2.audit_roles) |
unavailable_no_access | Source exists but connector lacks permissions to read it |
unavailable_not_applicable | Evidence category does not apply to this source system |
partial | Some data available but incomplete (e.g., retention window expired, only recent records retrieved) |
Evidence categories:
| Category | What it covers | Example source |
|---|---|---|
current_roles | Current role assignments | sys_user_has_role |
role_history | Historical role changes | sys_audit_role |
execution_evidence | Proof of actual execution | syslog_transaction, sys_flow_context |
ownership_records | Owner assignment evidence | Entra owners, group membership |
approval_records | Change approval evidence | CHG tickets, Jira issues |
credential_state | Credential lifecycle evidence | oauth_entity, app registrations |
Each category carries a notes field with a human-readable explanation when evidence is not fully available.
Impact on finding text: When evidence is unavailable, the finding's deterministic_explanation MUST state this explicitly:
- "Role change history unavailable (sys_audit_role not enabled); current role state from sys_user_has_role shows 4 roles assigned."
- NOT: "4 roles were added without approval" (implies approval check was possible when it may not have been)
Example:
{
"evidence_completeness": {
"current_roles": "available",
"role_history": "unavailable_not_enabled",
"execution_evidence": "available",
"ownership_records": "available",
"approval_records": "unavailable_no_access",
"credential_state": "available",
"notes": {
"role_history": "sys_audit_role not enabled in target instance (glide.role_management.v2.audit_roles = false)",
"approval_records": "ServiceNow change_request table not accessible with current connector permissions"
}
}
}
Source Metadata Policy (P0 — Pre-Live Enforcement)
Entity documents store allowlisted source system metadata rather than full API responses. This prevents persisting secrets (tokens in headers), regulated data (PII in user records), or large payloads. This is a data exposure control, not a scale optimization — it must be enforced before the first live connector ingest.
Policy:
- The field
source_metadatareplaces the previousraw_data/raw_api_responsefield on all entity documents - Each connector defines an allowlist of fields to persist per entity type
- Fields NOT on the allowlist are excluded before storage — connectors MUST NOT be able to persist entity data without passing through the allowlist filter
- A
source_metadata_hash(SHA256 of the full original response) is stored for integrity verification — proves the original data hasn't changed without storing it
Allowlist examples:
| Connector | Entity Type | Allowed Fields |
|---|---|---|
| Entra ID | service_principal | appId, displayName, appOwnerOrganizationId, servicePrincipalType, signInAudience, createdDateTime, accountEnabled |
| Entra ID | credential | keyId, type, startDateTime, endDateTime, displayName |
| ServiceNow | sys_user | user_name, active, locked_out, last_login_time, sys_created_on, sys_updated_on |
| ServiceNow | oauth_entity | client_id, name, active, user (reference), type |
Excluded by default: Authorization headers, tokens, password hashes, full request/response bodies, PII fields not needed for evidence.
Evidence Pack
What it is: A sealed, immutable artifact that packages all evidence supporting a finding. Evidence packs are the primary delivery mechanism — they are what gets shared with security teams, auditors, and operators.
Structure:
| Section | Contents |
|---|---|
| Identity Summary | Name, type, source system, creation date, current status |
| Cross-System Auth | Authentication chain: issuing system → credential → target system → target identity |
| Authority Snapshot | Exact roles, permissions, reachable resources at detection time (across all systems in the auth chain) |
| Ownership Timeline | All owners (primary/secondary/inherited), their types (human/team/BU), when assigned, what happened to each |
| Temporal Context | Age of identity, last activity, drift events over time |
| Blast Radius | Graph of all reachable resources with domain/sensitivity classification (spanning system boundaries) |
| Deterministic Explanation | Plain-language statement of why this finding exists |
| Remediation | Concrete actions: reassign owner, revoke role, rotate credential |
| Integrity Marker | SHA256 hash + timestamp — proves the pack hasn't been tampered with |
Key properties:
integrity_hash— SHA256 of content (immutability guarantee)sealed_at— when the pack was finalizedschema_version— for forward compatibilitycontent— the full evidence JSON
Relationships
Core Relationships
| Relationship | From → To | Meaning | Key Properties |
|---|---|---|---|
OWNED_BY | any → owner | "This owner is accountable for this entity" | since, until, status, ownership_level |
CREATED_BY | any → owner | "This entity was created by this human" | created_at, creation_context |
BELONGS_TO | owner → owner | "This owner is part of a parent owner" | since, until, role_in_parent, status |
HAS_ROLE | identity → role | "This identity holds this role" | granted_at, granted_by, revoked_at |
GRANTS | role → permission | "This role provides this capability" | source evidence |
APPLIES_TO | permission → resource | "This permission acts on this resource" | --- |
AUTHENTICATES_TO | identity → identity | "This identity authenticates to a target-system identity" | via_credential_id, auth_protocol, target_system, trust_chain_position, evidence_references |
RUNS_AS | workload → identity | owner | "This workload executes as this identity or human user" | run_as_type |
TRIGGERS_ON | workload → resource | "This workload is triggered by this resource or event" | trigger_type, schedule |
EXECUTES_ON | workload → resource | "This workload reads/writes this resource" | last_execution, execution_count_30d |
CALLS | workload → workload | "This workload invokes this code" | call_type (direct, include, delegate) |
INVOKES | workload → connection | "This code uses this outbound connection" | invocation_type |
USES | connection → credential | "This connection uses this auth material" | auth_method |
AUTHENTICATES_AS | credential → identity | "This credential represents this identity" | credential_type, binding_method |
OWNED_BY details
Any entity can have multiple owners with different accountability levels:
ownership_level | Meaning |
|---|---|
primary | The directly accountable entity — gets the 2 AM phone call |
secondary | Shared responsibility (e.g., a team that co-manages this integration) |
inherited | Derived from hierarchy (team → BU) — fallback escalation path |
Ownership finding rules:
orphaned_ownership— triggers when ALL ownership levels have decayed (no active owner at any level).ownership_degraded— triggers when the primary (individual) owner has decayed but a secondary owner (team/BU) is still active.ownership_ambiguous— triggers when the entity has ONLY group/team owners and has never had an individual owner assigned. Per the W1 Exposure Definition, groups-only ownership is inherently ambiguous because no specific individual is accountable. This is distinct fromdegraded(which implies an individual was once assigned and lost).
BELONGS_TO details
Captures ownership hierarchy. Enables inherited accountability:
- Human BELONGS_TO Team
- Team BELONGS_TO Business Unit
- Business Unit BELONGS_TO Organization
| Property | Type | Description |
|---|---|---|
since | datetime | When membership started |
until | datetime | When membership ended (null = current) |
role_in_parent | string | "lead", "member", "admin" |
status | enum | active, removed, transferred |
AUTHENTICATES_TO details
Makes cross-system execution paths explicitly traversable. This is how SecurityV0 connects an Entra service principal to its ServiceNow integration user (or a GitHub Actions identity to its AWS IAM role).
| Property | Type | Description |
|---|---|---|
via_credential_id | string | Which credential enables this authentication |
auth_protocol | enum | oauth2, saml, oidc, api_key, certificate, role_assumption |
target_system | string | Which platform the target identity lives in |
trust_chain_position | number | Position in multi-hop chain (0 = direct, 1 = first hop, etc.) |
established_at | datetime | When this auth binding was created |
last_used_at | datetime | Last successful authentication via this binding |
evidence_references | object | Cross-system linkage proof (see below) |
evidence_references stores the deterministic matching proof for cross-system identity linkage. This is what makes AUTHENTICATES_TO an evidence-grade claim rather than an assertion. Client_id matching alone is insufficient — in multi-tenant or multi-instance setups, the issuing tenant and target instance must be captured to prevent ambiguous links.
| Field | Required | Description |
|---|---|---|
issuing_system_id | Yes | The identifier in the issuing system (e.g., Entra SP appId / client_id) |
issuing_tenant_id | Yes | Tenant/directory context in the issuing system (e.g., Entra tenant ID 72f988bf-...). Disambiguates multi-tenant deployments. |
target_system_id | Yes | The identifier in the target system (e.g., ServiceNow oauth_entity.client_id) |
target_instance_id | Yes | Instance/environment context in the target system (e.g., https://corp.service-now.com or ServiceNow instance sys_id). Disambiguates multi-instance deployments. |
target_record_sys_id | No | Specific record ID in target system for direct verification (e.g., oauth_entity.sys_id = abc123) |
matching_field | Yes | Which field was used for deterministic matching (e.g., "client_id") |
matching_value | Yes | The actual value that matched (e.g., "a1b2c3d4-...") |
target_user_binding | No | If applicable, how the OAuth identity maps to a user (e.g., "oauth_entity.user -> sys_user.user_name") |
Example (Entra SP → ServiceNow):
{
"issuing_system_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"issuing_tenant_id": "72f988bf-86f1-41af-91ab-2d7cd011db47",
"target_system_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"target_instance_id": "https://corp.service-now.com",
"target_record_sys_id": "oauth-entity-sys-id-xyz",
"matching_field": "client_id",
"matching_value": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"target_user_binding": "oauth_entity.user -> sys_user.user_name = sn-integration-user"
}
Edge Migration Mapping
During the migration window, the platform ingestion normalizer accepts legacy edge types and remaps them to new types based on source/target entity types.
| Legacy Edge | Legacy Usage | New Edge | When |
|---|---|---|---|
EXECUTES_ON | automation → REST message | INVOKES | When target is connection type |
EXECUTES_ON | automation → table/resource | EXECUTES_ON (kept) | When target is resource type |
AUTHENTICATES_VIA | connection → credential (e.g., REST → OAuth profile) | USES | Always (connection → credential) |
AUTHENTICATES_VIA | identity → credential (legacy usage) | (removed) | Identity-to-credential path now goes through AUTHENTICATES_AS (credential → identity, reverse direction) |
| (none) | --- | CALLS | New: automation → automation (BR → SI) |
| (none) | --- | AUTHENTICATES_AS | New: credential → identity |
Note on similar-sounding relationships: AUTHENTICATES_TO is a cross-system hop between two identities (e.g., Entra SP authenticates to ServiceNow Integration User). AUTHENTICATES_AS is an intra-chain binding from a credential to the identity it represents (e.g., OAuth Profile represents Service Principal). These serve different purposes and appear at different points in the execution chain.
Workload and Execution Chain Relationships
| Relationship | From → To | Meaning | Key Properties |
|---|---|---|---|
RUNS_AS | workload → identity | owner | "This workload executes as this identity or human user" | run_as_type (configured, inherited, system) |
TRIGGERS_ON | workload → resource | "This workload is triggered by this resource or event" | trigger_type (schedule, event, manual, api_call), schedule |
EXECUTES_ON | workload → resource | "This workload reads/writes this resource" | last_execution, execution_count_30d |
CALLS | workload → workload | "This workload invokes this code (e.g., BR calls SI)" | call_type (direct, include, delegate) |
INVOKES | workload → connection | "This code uses this outbound connection" | invocation_type |
USES | connection → credential | "This connection uses this auth material" | auth_method |
AUTHENTICATES_AS | credential → identity | "This credential represents this identity" | credential_type, binding_method |
CREATED_BY | any → owner | "This entity was created by this human" (distinct from OWNED_BY) | created_at, creation_context |
RUNS_AS enables execution path traversal through workloads. The path materializer follows RUNS_AS edges to "borrow" the target identity's execution paths:
Workload (workload) -[RUNS_AS]-> SP (identity) -[HAS_ROLE]-> Role -[GRANTS]-> Permission -[APPLIES_TO]-> Resource
Critical design decision: RUNS_AS does not consume the auth chain depth budget. It is identity binding (which identity does this workload run as), not auth delegation. The target identity starts with a fresh depth=0 for its own AUTHENTICATES_TO traversal. This means a workload can follow RUNS_AS → SP → AUTHENTICATES_TO → OAuth without being blocked by depth limits. The visited set prevents cycles.
CALLS enables tracing execution flow through code dependencies. When a Business Rule calls a Script Include, the CALLS edge captures this relationship. This is critical for blast radius analysis — a single Script Include change can affect all calling workloads.
INVOKES → USES → AUTHENTICATES_AS forms the execution chain from automation logic through outbound connection to authenticating identity. This three-hop chain replaces the previous pattern of modeling REST messages and OAuth profiles as identity subtypes.
CREATED_BY is distinct from OWNED_BY. Creation is a historical fact (who created this thing); ownership is an ongoing accountability relationship. sys_created_by maps to CREATED_BY, not OWNED_BY. Ownership should be explicit and may be reassigned.
For ServiceNow specifically, maintainer hints such as sys_updated_by, update sets, or app scope are attribution signals only; they do not create authoritative OWNED_BY relationships by themselves.
Derived Relationships
| Relationship | From → To | Meaning | How Computed |
|---|---|---|---|
EXECUTION_PATH | identity → resource | "This identity can reach this resource" | Single-system: HAS_ROLE → GRANTS → APPLIES_TO. Cross-system: AUTHENTICATES_TO → HAS_ROLE → GRANTS → APPLIES_TO. Automation: RUNS_AS → (target identity paths). Execution chain: CALLS → INVOKES → USES → AUTHENTICATES_AS → (identity paths) |
CONCERNS | Finding → entity | "This finding is about this entity" | Set when finding is created. Can target any entity type — identity, automation, credential, etc. |
AFFECTS | Finding → resource | "This finding affects this resource" | Computed from blast radius at finding time |
Relationship Lifecycle Example
Timeline for identity "sp-hr-onboarding" (Entra SP → ServiceNow):
2025-03-10: OWNED_BY → Sarah Chen (ownership_level: primary, status: active)
2025-03-10: OWNED_BY → IT-Automation-Team (ownership_level: secondary, status: active)
2025-03-10: AUTHENTICATES_TO → sn-integration-user (auth_protocol: oauth2, target_system: servicenow)
2025-03-10: (sn-integration-user) HAS_ROLE → sn_incident_write
2025-03-10: (sn-integration-user) HAS_ROLE → sn_task_assign
2025-04-22: (sn-integration-user) HAS_ROLE → sn_incident_close (approved: JIRA-IT-4521)
2025-07-15: OWNED_BY → Sarah Chen (status: decayed, until: 2025-07-15)
↑ Sarah's account disabled — primary ownership decays
→ Finding: ownership_degraded (secondary owner IT-Automation-Team still active)
2025-09-03: (sn-integration-user) HAS_ROLE → itil, hr_agent_workspace, personalize, task_editor
↑ Roles replaced during platform migration — no approval record
→ Finding: scope_drift
2026-01-15: IT-Automation-Team disbanded during reorg (status: disbanded)
↑ Secondary owner also decayed
→ Finding: orphaned_ownership (no active owner at any level)
Execution Chains
An execution chain is an ordered set of entities from a trigger event to a destination resource, capturing the full provenance of how autonomous execution flows through a system. Execution chains are the primary unit of analysis for understanding blast radius and accountability.
W1 note: W1 (Exposure wedge) does NOT depend on the
execution_chainscollection. W1 computes Authority Paths and Exposures from entity relationships andexecution_paths[]directly (see W1 Derived Concepts). Theexecution_chainscollection continues to serve operational tracking, temporal analysis, and future wedges (drift detection, chain fingerprinting).
Chain Composition
An execution chain connects entities in this order:
Resource (trigger) → Automation → [Automation]* → Connection → Credential → Identity → Role → Permission → Resource (target)
Not all chains include every entity type. A simple chain might be:
Automation (scheduled job) → Identity (runs-as SP) → Role → Permission → Resource
A complex cross-system chain includes all entity types:
incident table → Business Rule → Script Include → REST Message → OAuth Profile → Service Principal → Role → Permission → Graph API
Chain Identity
Each execution chain is anchored to its entry-point automation (e.g., a Business Rule's sys_id). The chain identity survives entity rotation — if a credential is rotated or a role is reassigned, the chain persists as long as the entry-point automation exists. If the entry-point automation is deleted, the chain is marked as no longer seen (last_seen_at stops updating). This enables temporal tracking of how a chain's composition changes over time. See ADR-008 for the full chain identity design.
Chain Composition Fingerprint
Each chain carries a composition fingerprint: SHA256 of the sorted entity_id:role pairs in the chain. This fingerprint changes when the structural composition of the chain changes (entity added/removed, role changed) but NOT when properties change (e.g., last_activity_at). This enables efficient detection of structural drift.
Chain Assembly
Execution chains are assembled platform-side via BFS from entry points (automations with TRIGGERS_ON edges or scheduled execution). The connector provides the individual entities and relationships; the platform assembles them into chains during ingestion.
Chain Lifecycle
- Created on first sync when the entry point and at least one downstream entity exist
- Versioned on each subsequent sync where the composition fingerprint changes
- Marked inactive when the entry point is deleted or disabled
- Preserved in history for temporal comparison and drift detection
For the MongoDB schema of the execution_chains collection, see 03-database.md.
Execution Flow Provenance Diagram
The following diagram shows a complete execution chain from trigger through automation, connection, credential, and identity to the target resource authorization path:
This diagram illustrates the key insight of the 9-type entity model: each entity in the chain has a distinct purpose (logic, transport, authentication, authorization), and the relationships between them are typed to reflect that purpose. The chain is fully traversable — from a trigger event on the incident table, you can walk the graph to determine exactly which Graph API permissions are exercised and through which intermediaries.
Execution Paths
The execution path is the core concept in SecurityV0. It answers: "Given this identity, what can it actually do, and through which chain of authority?"
Simple Path (2 hops)
Identity: sp-payroll-sync
→ Role: hr_admin
→ Permission: hr_case.write (normalized: update, scope: hr_case)
→ Resource: hr_case table (domain: hr, sensitivity: confidential)
Reading this: The service principal sp-payroll-sync holds the hr_admin role, which grants update access to the hr_case table — a confidential HR resource.
Cross-System Path (via AUTHENTICATES_TO)
Identity: sp-hr-onboarding (Entra SP, source: entra_id)
→ AUTHENTICATES_TO → Identity: sn-integration-user (source: servicenow)
via_credential: oauth_client_secret, auth_protocol: oauth2, trust_chain_position: 0
→ Role: hr_agent_workspace (ServiceNow)
→ Permission: hr_case.write (normalized: update, scope: hr_case)
→ Resource: hr_case table (domain: hr, sensitivity: confidential)
Reading this: The Entra service principal sp-hr-onboarding authenticates to ServiceNow via OAuth client credentials, operating as the ServiceNow integration user sn-integration-user. That integration user holds the hr_agent_workspace role, which grants write access to the confidential hr_case table.
Transitive Path (multi-hop, cross-system)
Identity: github-actions-deploy (GitHub, source: github)
→ AUTHENTICATES_TO → Identity: aws-deploy-role (AWS IAM Role, source: aws)
via_credential: oidc_federation_trust, auth_protocol: oidc, trust_chain_position: 0
→ Role: S3FullAccess (AWS)
→ Permission: s3:* (normalized: admin, scope: s3)
→ Resource: s3://production-database-backups (sensitivity: restricted)
Reading this: The GitHub Actions identity authenticates to AWS via OIDC federation, assuming the aws-deploy-role IAM role. That role has S3 full access to production database backups. This is a transitive privilege chain — compromising the GitHub workflow grants access to production database backups.
W1 Derived Concepts
W1 (Exposure wedge) introduces three derived concepts that are computed from the existing entity graph. None of these are new MongoDB collections or persisted entities. They are views, projections, and aggregations over existing data.
Exposure
What it is: A derived assessment unit representing one Authority Path from a workload to a data domain, enriched with its associated findings, execution activity, ownership state, and evidence completeness.
Key properties:
- One exposure = one authority path = one (workload, identity, destination, data_domain) tuple
- A single workload can produce multiple exposures if it reaches multiple data domains through different paths
- Exposures carry associated findings (e.g.,
llm_egress,ownership_unknown), execution evidence state, and evidence completeness - The EXP-NNN display ID is deterministic:
hash(tenant_id, workload_id, identity_id, destination, data_domain)
What an Exposure is NOT:
- NOT an execution chain. One
ExecutionChainDocis a BFS tree that can span multiple branches and resources. An Exposure is a single linear path through that tree. - NOT a persisted entity. Exposures are computed from entity relationships at query time (or materialized during sync for performance).
- NOT a finding. A finding is an issue-level alert. An Exposure is the workload-level authority summary to which findings are attached.
Computation: Exposures are derived from:
- Workload entities (
entity_type: "workload") - Their
RUNS_ASidentity targets - The identity's
execution_paths[]array (already materialized on entity documents) - Associated findings from the evaluator
W1 scope explicitly does NOT require execution_chains persistence (logic.md section 9). Exposures are computed from entity relationships directly, not from the execution_chains collection.
Authority Path
What it is: A 4-node linear projection representing the structural path from a workload to a data domain:
Workload → Identity → Destination → Data Domain
Mapping to existing data:
| Authority Path Node | Data Source |
|---|---|
| Workload (entry point) | The workload entity itself |
| Identity (service principal) | RUNS_AS relationship target |
| Destination (API gateway, system) | Derived from INVOKES → connection target, or execution_paths[].source_system |
| Data Domain | execution_paths[].business_domain + execution_paths[].sensitivity |
Authority Paths are NOT a new collection. They are a projection computed from entity relationships and the already-materialized execution_paths[] array. The existing chain-builder.ts BFS already collects exactly this data — the Authority Path is a simplified 4-node summary.
Risk Cluster
What it is: A compound-condition grouping of exposures that share the same combination of risk attributes. Risk Clusters are computed ephemerally via MongoDB aggregation — they are NOT persisted.
Compound condition dimensions (4D):
- Data domain sensitivity — sensitive (confidential/restricted) vs non-sensitive (internal/public)
- Egress classification — llm, external, internal, unknown
- Execution status — active (execution_count_30d > 0) vs dormant (execution_count_30d = 0)
- Ownership status — valid, invalid, ambiguous, unknown
Example cluster labels:
- "Sensitive + LLM + Active + Invalid Owner" (highest risk)
- "External + Active + Valid Owner"
- "Internal + Dormant + Unknown Owner"
Relationship to existing RG1-RG5: The connector currently assigns RG1-RG5 based on a 2D matrix (egress × data_domains sensitivity). W1 Risk Clusters extend this to 4 dimensions by adding execution status and ownership status. RG1-RG5 remains available as a connector-computed property; Risk Clusters are platform-computed aggregations.
Key constraint: Grouping does not replace canonical findings and does not introduce new risk semantics (logic.md section 7). Risk Clusters are a triage grouping, not a finding type.
W1 Posture Summary
What it is: An aggregate view of the tenant's autonomous execution landscape. Four stat cards showing identity-level and workload-level counts.
Metrics and their counting units:
| Metric | Unit | How Computed |
|---|---|---|
| Execution Identities | Distinct identities | Count distinct identities that are RUNS_AS targets of autonomous workloads with execution_count_30d > 0. Deduped — multiple workloads sharing one identity count as 1. |
| Dormant Authority Identities | Distinct identities | Count distinct identities that are RUNS_AS targets of autonomous workloads with execution_count_30d = 0. Deduped. |
| Operator-Assisted Workloads | Workloads | Count workload entities where execution_mode = operator_assisted. |
| Human-Triggered Workloads | Workloads | Count workload entities where execution_mode = human_triggered. |
Identity-first counting for cards 1-2 is critical. If 12 workloads all RUNS_AS the same Service Principal, that's 1 execution identity, not 12. The overcount risk was identified during plan review — posture cards must count the unit specified above, not raw entity counts.
Scenario: A ServiceNow integration (Entra service principal) with layered ownership and cross-system execution.
BEFORE (ownership active, narrow scope):
┌──────────────────┐ OWNED_BY (primary) ┌───────────────────┐
│ sp-hr-onboarding │─────────────────────▶│ Sarah Chen │
│ (Entra SP) │ │ (human, active) │
│ │ OWNED_BY (secondary) └───────────────────┘
│ │─────────────────────▶┌───────────────────┐
│ │ │IT-Automation-Team │
└────────┬─────────┘ │(team, active) │
│ └────────┬──────────┘
│ │ BELONGS_TO
AUTHENTICATES_TO ▼
(oauth2, pos: 0) ┌───────────────────┐
│ │IT-Operations-BU │
▼ │(business_unit) │
┌──────────────────┐ └───────────────────┘
│sn-integration-usr│
│(ServiceNow) │
└────────┬─────────┘
│
HAS_ROLE
│
┌────▼────────────┐
│ sn_incident_write│── GRANTS ──▶ incident.create ── APPLIES_TO ──▶ incident table
└─────────────────┘
┌─────────────────┐
│ sn_task_assign │── GRANTS ──▶ task.create ────── APPLIES_TO ──▶ task table
└─────────────────┘
AFTER (primary ownership decayed, scope drifted, then team disbanded):
┌──────────────────┐ OWNED_BY (primary, DECAYED) ┌───────────────────┐
│ sp-hr-onboarding │────────────────────────────────▶│ Sarah Chen │
│ (Entra SP) │ │ (human, departed) │
│ │ OWNED_BY (secondary, DECAYED) └───────────────────┘
│ │────────────────────────────────▶┌───────────────────┐
│ │ │IT-Automation-Team │
└────────┬─────────┘ │(team, disbanded) │
│ └───────────────────┘
AUTHENTICATES_TO
(oauth2, pos: 0)
│
▼
┌──────────────────┐
│sn-integration-usr│
│(ServiceNow) │
└────────┬─────────┘
│
HAS_ROLE (scope drifted — roles replaced + expanded without approval)
│
┌────▼────┐
│ itil │──── GRANTS ──▶ incident.* ────── APPLIES_TO ──▶ incident table
└─────────┘
┌─────────────────────┐
│ hr_agent_workspace │── GRANTS ──▶ hr_case.* ──── APPLIES_TO ──▶ hr_case (⚠️ confidential)
└─────────────────────┘
┌─────────────────┐
│ personalize │── GRANTS ──▶ sys_user.write ── APPLIES_TO ──▶ sys_user (⚠️ confidential)
└─────────────────┘
┌─────────────────┐
│ task_editor │── GRANTS ──▶ task.* ────────── APPLIES_TO ──▶ all task tables
└─────────────────┘
Findings:
1. orphaned_ownership — no active owner at any level
(primary: Sarah departed; secondary: team disbanded)
2. scope_drift — 4 broad roles replaced 2 narrow ones without approval
3. blast_radius — HR cases, user profiles, all task types reachable
from an unowned, unmonitored identity executing every 15 minutes
OAA Mapping Reference
SecurityV0's internal data model is a superset of the Veza Open Authorization API (OAA) format. The following table shows how internal entity types map to OAA export entities:
| SecurityV0 Type | OAA Export Entity | Mapping Fidelity |
|---|---|---|
identity | local_user | HIGH |
workload | resource (type: workload) | HIGH |
connection | resource (type: connection) | HIGH |
credential | custom property on local_user | MEDIUM |
owner | local_user (type: human) | HIGH |
role | local_role | HIGH |
permission | custom_permission | HIGH |
resource | resource | HIGH |
OAA is an export format, not the internal data model. SecurityV0's model is a superset of OAA — it captures execution chains, temporal drift, and evidence provenance that OAA does not represent. See ADR-009 for the projection design.
Property Reference
For full MongoDB document schemas, indexes, and query patterns, see 03-database.md.
For the normalized schema vocabulary (how source system fields map to these entities), see 05-connectors.md. The connector interface uses 9 NormalizedNodeType values: identity, workload (deprecated: automation), connection, credential, human_identity, role, permission, resource, execution_evidence. The platform normalizer maps human_identity to internal entity_type: "owner" and maps the deprecated automation to workload — see the Entity Types table above for the full mapping.