AWS Non-Human Identity and Workload Identity Surface
Purpose
This document catalogs every AWS identity type and mechanism through which autonomous, non-human workloads authenticate, assume authority, and access resources. For each identity type it provides:
- What it is and how it authenticates.
- How it maps to SecurityV0's 9-type entity model (
identity,workload,connection,credential,owner,role,permission,resource,execution_evidence). - Concrete security risks and ownership decay scenarios.
- AWS APIs that expose the data for connector ingestion.
This is the reference catalog for building the AWS connector's identity graph.
SecurityV0 Entity Model Reference
For mapping clarity, here are the 9 entity types and their purpose in the authority graph:
| Entity Type | Purpose |
|---|---|
identity | An authenticatable principal — human or non-human. The "who" in an execution path. |
workload | An automation artifact that executes autonomously — Lambda function, Step Functions state machine, ECS task, Bedrock agent, etc. |
connection | A configured link between systems, carrying auth context — EventBridge connection, API destination, VPC endpoint. |
credential | A stored secret or key used for authentication — access key, secret in Secrets Manager, SSM SecureString. |
owner | A person or team accountable for a workload, identity, or resource. |
role | A permission assignment object — an IAM managed policy, permission set, or role assignment that sits between identity and permission. |
permission | A specific action-resource grant — s3:GetObject on arn:aws:s3:::prod-data/*. |
resource | A destination being accessed — S3 bucket, DynamoDB table, RDS cluster, Secrets Manager secret. |
execution_evidence | A temporal proof that an execution path was exercised — CloudTrail event, invocation log. |
1. IAM Users — Programmatic Access Keys and Service Accounts
What It Is
An IAM User is a persistent identity within an AWS account. Each user can have up to two long-lived access key pairs (AccessKeyId + SecretAccessKey) for programmatic API access. Despite AWS best-practice guidance to use IAM Roles instead, IAM Users remain widespread as de-facto service accounts, especially in legacy environments and third-party integrations that cannot assume roles.
How It Authenticates
- Access key pairs: The
AccessKeyIdandSecretAccessKeyare used to sign every AWS API request via SigV4. These credentials are static — they do not expire unless manually rotated or deactivated. - Console password (less relevant for NHI): Some IAM Users created for humans also have console passwords. For NHI discovery, console access is a signal that the "service account" may actually be a shared human account.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| IAM User (when used as NHI) | identity | machine_account |
| Access Key ID + Secret | credential | aws_access_key |
| IAM Managed Policy attached to user | permission | iam_policy |
| IAM Inline Policy on user | permission | iam_inline_policy |
| IAM Group (container for users + policies) | identity | iam_group |
| Group membership | Relationship: MEMBER_OF |
Security Risks and Ownership Decay
- Orphaned service accounts: Engineer creates an IAM User for a CI/CD pipeline or third-party tool, leaves the company. The access keys remain active. Nobody rotates them. The user has
AdministratorAccessbecause it was "temporary." This is the most common AWS NHI ownership decay pattern. - Key sprawl: A single IAM User's access key is embedded in multiple systems — Jenkins, Terraform state, Lambda environment variables, developer laptops. Rotating the key breaks unknown dependencies.
- Dormant keys with standing access: IAM User has access keys that have not been used in 180+ days but retain broad permissions. AWS IAM Credential Report exposes this directly.
- Shared service accounts: Multiple teams share one IAM User. When something breaks, nobody knows which system made the call. CloudTrail shows the IAM User ARN but not the upstream application.
Realistic scenario: A DevOps engineer creates svc-terraform-deploy IAM User with PowerUserAccess plus iam:* for Terraform state management. The engineer leaves. The Terraform pipeline still runs nightly, creating and modifying IAM roles. The access keys are 400 days old. Nobody in the org knows the user exists or that it has IAM write access. SecurityV0 discovers this as: orphaned identity, stale credential, overprivileged authority path, active execution evidence.
Discovery APIs
| API | Data Returned |
|---|---|
iam:ListUsers | All IAM users in the account |
iam:GetUser | User details, creation date, last activity |
iam:ListAccessKeys | Access key IDs and their status (Active/Inactive) per user |
iam:GetAccessKeyLastUsed | Last used date, region, and service for each access key |
iam:ListUserPolicies | Inline policy names on a user |
iam:GetUserPolicy | Inline policy document |
iam:ListAttachedUserPolicies | Managed policies attached to a user |
iam:ListGroupsForUser | Group memberships |
iam:ListMFADevices | MFA status (presence/absence is a risk signal for NHI) |
iam:GenerateCredentialReport + iam:GetCredentialReport | CSV report with all users, key ages, last used dates, MFA, password status |
2. IAM Roles — The Primary AWS NHI Mechanism
What It Is
An IAM Role is an identity that has no long-lived credentials. Instead, it is assumed by another principal (user, service, or another role) via AWS STS. The role has two policy components:
- Trust policy (AssumeRolePolicyDocument): Defines WHO can assume this role — a list of principals (AWS accounts, services, federated identity providers).
- Permission policies (identity-based): Define WHAT the role can do once assumed — attached managed policies and inline policies.
Roles are the backbone of all modern AWS NHI patterns. Every service-specific identity pattern below is a specialized form of role assumption.
How It Authenticates
The assuming principal calls sts:AssumeRole (or sts:AssumeRoleWithWebIdentity for OIDC, or sts:AssumeRoleWithSAML for SAML). STS validates the request against the role's trust policy and returns temporary credentials (access key, secret key, session token) valid for 1-12 hours.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| IAM Role | identity | iam_role |
| Trust policy | Relationship: TRUSTS (from role to the allowed principals) | |
| Managed policy attachment | Relationship: HAS_ROLE (identity to role entity) | |
| Inline policy | role | iam_inline_policy |
| Managed policy | role | iam_policy |
| Permission boundary | Relationship: CONSTRAINED_BY (ceiling on effective permissions) | |
| AssumeRole session (STS) | execution_evidence | sts_assume_role |
Security Risks and Ownership Decay
- Trust policy drift: A role's trust policy is broadened to add a new account or service principal, then never tightened back. Over time, principals that should not assume the role can.
- Wildcard trust: Trust policies with
"Principal": {"AWS": "*"}(any AWS account) or"Principal": "*"(any principal including anonymous) are critical misconfigurations. These make the role publicly assumable, constrained only by conditions (which may be absent). - Permission accumulation: Policies are attached to roles incrementally to fix bugs or enable new features. Nobody removes the old permissions. The role grows monotonically.
- Role purpose decay: A role was created for a specific workload. That workload is decommissioned. The role remains, attached to a different workload that does not need the same permissions.
Discovery APIs
| API | Data Returned |
|---|---|
iam:ListRoles | All roles in the account, including path, ARN, trust policy |
iam:GetRole | Role detail including trust policy document |
iam:ListRolePolicies | Inline policies on a role |
iam:GetRolePolicy | Inline policy document |
iam:ListAttachedRolePolicies | Managed policies attached to a role |
iam:GetPolicy + iam:GetPolicyVersion | Managed policy document (the actual permission statements) |
iam:ListRoleTags | Tags — may contain owner, team, or purpose metadata |
iam:GetServiceLastAccessedDetails | Services the role has accessed and when (Access Advisor) |
2.1 EC2 Instance Profiles
What It Is
An instance profile is a container that wraps exactly one IAM Role and attaches it to an EC2 instance. Applications running on the instance retrieve temporary credentials from the Instance Metadata Service (IMDS) at http://169.254.169.254/latest/meta-data/iam/security-credentials/{role-name}.
How It Authenticates
The EC2 instance's hypervisor manages the credentials. The AWS SDK on the instance automatically retrieves and refreshes temporary credentials from IMDS. No access keys are stored on the instance. IMDSv2 (token-required) mitigates SSRF-based credential theft.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Notes |
|---|---|---|
| EC2 Instance | workload (subtype: ec2_instance) | The running compute |
| Instance Profile | Edge annotation on RUNS_AS | Not a separate entity — it is the binding mechanism |
| IAM Role (the one in the profile) | identity (subtype: iam_role) | The role the instance assumes |
Security Risks
- Overprivileged instance roles: EC2 instances often run multiple applications. The instance role must be broad enough for all of them. This violates least privilege.
- SSRF to IMDS: If an application on the instance has an SSRF vulnerability and IMDSv1 is enabled (no hop limit, no token requirement), an attacker can steal the role's temporary credentials from the metadata endpoint.
- Long-running instances with stale roles: EC2 instances run for months or years. The role's permissions grow, the owning team changes, but the instance keeps running.
- Shared instance profiles: Multiple instances share the same instance profile/role. Compromise of one instance grants the attacker the same authority as all others.
Realistic scenario: A data engineering team launches an EC2 instance in 2024 for a batch ETL job. The instance profile role has s3:* on arn:aws:s3:::* and dynamodb:* on the production tables. The ETL job is migrated to Glue in 2025, but the EC2 instance keeps running because "something might still use it." IMDSv1 is enabled. The role has not been touched in 18 months. SecurityV0 discovers: workload with orphaned ownership, overprivileged identity, no recent execution evidence on the original ETL path but the instance is still running.
Discovery APIs
| API | Data Returned |
|---|---|
iam:ListInstanceProfiles | All instance profiles and their associated roles |
iam:GetInstanceProfile | Instance profile detail with role ARN |
ec2:DescribeInstances | Running instances with IamInstanceProfile field |
ec2:DescribeInstanceAttribute (disableApiTermination) | Instance protection state |
ec2:DescribeInstances (MetadataOptions) | IMDSv1/v2 configuration |
2.2 ECS Task Roles
What It Is
Amazon ECS has two distinct IAM roles per task definition:
- Task Role (
taskRoleArn): The identity used by the application code inside the container. This is the workload's runtime identity — the authority surface SecurityV0 cares about most. - Task Execution Role (
executionRoleArn): The identity used by the ECS agent to pull container images from ECR, push logs to CloudWatch, and retrieve secrets for injection. This is infrastructure-level authority.
How It Authenticates
The ECS agent injects temporary credentials for the task role into the container via the credential endpoint (http://169.254.170.2/...). The container's AWS SDK automatically retrieves them. Each task gets isolated credentials — one task cannot access another task's role credentials.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| ECS Service | workload | ecs_service |
| ECS Task Definition | workload | ecs_task |
| Task Role | identity (subtype: iam_role) | Runtime identity |
| Execution Role | identity (subtype: iam_role) | Infrastructure identity |
| Container Image (ECR) | resource | ecr_image |
Relationships:
ECS Task → RUNS_AS → Task Role(runtime authority)ECS Task → PULLS_VIA → Execution Role(infrastructure authority)ECS Task → DEPLOYED_FROM → ECR Repository(supply chain lineage)
Security Risks
- Task role vs execution role confusion: Teams use the execution role for application permissions because "it worked." This means the ECS agent and the application share a single, overprivileged role.
- Missing task role: A task definition has no
taskRoleArn— the application inherits the host's (EC2 launch type) or gets no role (Fargate). On EC2 launch type, this means the container gets the EC2 instance role, which may be broadly privileged. - Execution role with secret access: The execution role can be configured to retrieve secrets from Secrets Manager for injection into environment variables. If the execution role has broader secret access than needed, it can read secrets meant for other services.
Realistic scenario: An ECS service runs a payment processing application. The task definition was copied from a template that included both taskRoleArn and executionRoleArn pointing to the same role — ecsPaymentServiceRole. This role has permissions for ECR pull, CloudWatch Logs, Secrets Manager (for DB credentials), AND s3:* on the data lake bucket (because someone debugging added it temporarily). The application only needs the DB credentials and one S3 prefix. The task has been running for 14 months. The original developer left 9 months ago. SecurityV0 discovers: scope drift on the shared role, overprivileged authority path to the data lake, orphaned ownership.
Discovery APIs
| API | Data Returned |
|---|---|
ecs:ListTaskDefinitions | All task definition ARNs |
ecs:DescribeTaskDefinition | Task role ARN, execution role ARN, container definitions, image URIs |
ecs:ListServices | Services per cluster |
ecs:DescribeServices | Service details, task definition reference, desired count |
ecs:ListClusters + ecs:DescribeClusters | Cluster metadata |
2.3 EKS Pod Identity and IRSA
What It Is
Amazon EKS provides two mechanisms for pods to assume IAM roles:
IRSA (IAM Roles for Service Accounts) — the older mechanism:
- Creates an OIDC identity provider in IAM for the EKS cluster.
- The IAM role's trust policy trusts the cluster's OIDC provider with conditions on the Kubernetes service account name and namespace.
- The pod's service account is annotated with the IAM role ARN.
- The pod receives a projected JWT token that it exchanges for temporary AWS credentials via
sts:AssumeRoleWithWebIdentity.
EKS Pod Identity — the newer mechanism (GA since late 2023):
- Uses the EKS Pod Identity Agent (a DaemonSet) running on each node.
- An explicit
PodIdentityAssociationresource binds a Kubernetes service account to an IAM role. - The pod gets credentials from a local agent endpoint — no OIDC provider configuration required.
- The trust policy trusts the
pods.eks.amazonaws.comservice principal.
How It Authenticates
- IRSA: Pod → projected service account token (JWT signed by cluster OIDC provider) →
sts:AssumeRoleWithWebIdentity→ temporary AWS credentials. - Pod Identity: Pod → local agent endpoint → EKS Pod Identity Agent →
sts:AssumeRoleForPodIdentity→ temporary AWS credentials.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| EKS Cluster | workload | eks_cluster |
| Kubernetes Deployment/Pod | workload | eks_workload |
| Kubernetes Service Account | identity | k8s_service_account |
| IAM Role (assumed by pod) | identity | iam_role |
| OIDC Provider (IRSA) | connection | oidc_provider |
| PodIdentityAssociation | Relationship: ASSUMES_VIA |
Relationships:
EKS Workload → RUNS_AS → K8s Service Account → ASSUMES → IAM RoleK8s Service Account → TRUSTS_VIA → OIDC Provider(IRSA)PodIdentityAssociationbinds service account + namespace to role (Pod Identity)
Security Risks
- Overly broad OIDC trust conditions: The IAM role's trust policy condition for IRSA uses
StringLikeinstead ofStringEquals, or uses a wildcard for the service account name. This allows any service account in any namespace to assume the role. - Namespace-scoped but not service-account-scoped: Trust policy condition checks
system:serviceaccount:*:*in a namespace pattern but does not pin the specific service account name. - Stale pod identity associations: A PodIdentityAssociation remains after the workload is removed. The IAM role stays assumable by any pod using that service account name in that namespace.
- Cross-namespace impersonation: If RBAC in the EKS cluster allows creating service accounts in the target namespace, an attacker can create a service account with the right annotation and assume the role.
Realistic scenario: A data science team sets up IRSA for a Spark workload in namespace analytics. The trust policy condition is StringLike: system:serviceaccount:analytics:*. Six months later, the team deploys a debugging pod in the same namespace with a different service account. Because the condition is a wildcard on the service account name, the debugging pod can assume the same IAM role that has s3:* on the data lake. SecurityV0 discovers: overly permissive trust condition, multiple workloads sharing the same authority path, the debugging pod was never meant to access the data lake.
Discovery APIs
| API | Data Returned |
|---|---|
eks:ListClusters | All EKS clusters |
eks:DescribeCluster | Cluster OIDC issuer URL, endpoint, version |
eks:ListPodIdentityAssociations | Pod identity bindings per cluster |
eks:DescribePodIdentityAssociation | Service account name, namespace, role ARN |
iam:ListOpenIDConnectProviders | OIDC providers (includes EKS cluster OIDC) |
iam:GetOpenIDConnectProvider | Provider URL, client IDs, thumbprints |
| IAM Role trust policy analysis | Roles trusting the cluster's OIDC provider URL (parsed from GetRole) |
2.4 Lambda Execution Roles
What It Is
Every Lambda function has exactly one execution role. This role is assumed by the Lambda service when the function is invoked. The role's permissions define what the function code can access — S3 buckets, DynamoDB tables, Secrets Manager secrets, other AWS services, and external APIs (if network egress is allowed).
How It Authenticates
The Lambda service calls sts:AssumeRole on behalf of the function. The function code receives temporary credentials via the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN environment variables. These are auto-refreshed by the Lambda runtime.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| Lambda Function | workload | lambda_function |
| Execution Role | identity | iam_role |
| Event Source Mapping | Relationship: TRIGGERED_BY | |
| Lambda Resource Policy | Authority edge from invoker to function | |
| Environment variable (SECRET_ARN, DB_URL) | Inferred relationship to resource |
Security Risks
- Shared execution roles: Multiple Lambda functions share one execution role. The role must be broad enough for all functions. Each function gets more authority than it needs.
- Execution role with admin access: During development, someone attaches
AdministratorAccessto the Lambda role. It ships to production. - Orphaned functions: The business process the function served is decommissioned, but the function remains deployed and the CloudWatch Events rule still triggers it nightly.
- Environment variable credential leakage: The function's environment variables contain hardcoded credentials (database passwords, API keys) rather than using Secrets Manager with fine-grained role permissions. Anyone with
lambda:GetFunctionorlambda:GetFunctionConfigurationcan read them.
Realistic scenario: A Lambda function process-claims was created 2 years ago by a contractor. The execution role claims-processor-role has been modified 14 times since creation. It now has s3:*, dynamodb:*, secretsmanager:*, and sqs:* on Resource: *. The function runs daily via an EventBridge schedule. The contractor left 18 months ago. The function's code has not been updated in 11 months. SecurityV0 discovers: orphaned ownership (departed contractor), scope drift (14 policy modifications), overprivileged authority paths to every S3 bucket and DynamoDB table in the account, active execution evidence (daily invocations).
Discovery APIs
| API | Data Returned |
|---|---|
lambda:ListFunctions | All functions with execution role ARN, runtime, handler |
lambda:GetFunction | Full function config including VPC, layers, environment vars (keys only for security) |
lambda:GetFunctionConfiguration | Config detail including execution role ARN |
lambda:GetPolicy | Resource-based policy (who can invoke) |
lambda:ListEventSourceMappings | Event source mappings (SQS, DynamoDB Streams, Kinesis triggers) |
lambda:ListTags | Tags on the function |
2.5 Step Functions Execution Roles
What It Is
Every Step Functions state machine has an execution role that defines what actions the state machine can perform. Step Functions state machines are orchestrators — they invoke Lambda functions, start ECS tasks, call DynamoDB, send messages to SQS/SNS, make HTTP calls, and chain to other state machines.
How It Authenticates
The Step Functions service assumes the execution role via STS when the state machine execution starts. Each state in the machine uses these credentials to invoke its target service.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| Step Functions State Machine | workload | step_function |
| Execution Role | identity | iam_role |
| Each state's target (Lambda, ECS, DynamoDB, HTTP) | Relationship: INVOKES |
Security Risks
- Orchestrator privilege escalation: The state machine's execution role must have permissions for every service it orchestrates. A state machine that invokes 10 Lambda functions, writes to 3 DynamoDB tables, and sends messages to 2 SQS queues needs a role covering all of them. If one step is removed, the permission is rarely cleaned up.
- HTTP task egress: Step Functions can call arbitrary HTTPS endpoints via HTTP Tasks. The execution role needs
states:InvokeHTTPEndpointand the state machine definition includes the external URL. This is an egress path that bypasses typical network controls. - Cross-account invocation: State machines can invoke resources in other accounts if the execution role has cross-account permissions. The state machine definition reveals the target ARN, but the cross-account trust is in the IAM role.
Discovery APIs
| API | Data Returned |
|---|---|
states:ListStateMachines | All state machines in the account |
states:DescribeStateMachine | Definition (JSON), execution role ARN, type, logging config |
states:ListExecutions | Recent executions with status and timestamps |
states:ListTagsForResource | Tags |
2.6 Glue Job Roles
What It Is
AWS Glue jobs (ETL, Spark, Python shell) run under a specified IAM role. This role grants the job access to data sources (S3, JDBC, DynamoDB), data targets, and the Glue Data Catalog.
How It Authenticates
The Glue service assumes the specified role when the job runs. The job's code receives temporary credentials.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| Glue Job | workload | glue_job |
| Glue Execution Role | identity | iam_role |
| Glue Data Catalog | resource | glue_catalog |
| S3 source/target | resource | s3_bucket |
Security Risks
- Overprivileged Glue roles: Glue roles often get
s3:*andglue:*because the job needs to read from and write to multiple locations. The AWS-managed policyAWSGlueServiceRoleis broad. - JDBC credential exposure: Glue connections store JDBC passwords. The Glue role needs
glue:GetConnectionto retrieve them. If the role is shared across jobs, all jobs can read all connection passwords. - Dormant Glue jobs: ETL pipelines are created for one-time migrations, never decommissioned. The role retains access to production data sources.
Discovery APIs
| API | Data Returned |
|---|---|
glue:GetJobs | All Glue jobs with role ARN, command, connections |
glue:GetJob | Job detail |
glue:GetConnections | Connections (JDBC, Kafka, etc.) with metadata |
glue:GetCrawlers | Crawlers with roles |
glue:GetJobRuns | Recent execution history |
2.7 SageMaker Execution Roles
What It Is
SageMaker notebooks, training jobs, and endpoints run under IAM roles. A SageMaker execution role grants access to S3 training data, ECR images, model artifacts, and other AWS services.
How It Authenticates
The SageMaker service assumes the execution role. Notebook instances use the role via IMDS (similar to EC2). Training jobs and endpoints receive temporary credentials injected by the service.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| SageMaker Notebook Instance | workload | sagemaker_notebook |
| SageMaker Training Job | workload | sagemaker_training_job |
| SageMaker Endpoint | workload | sagemaker_endpoint |
| Execution Role | identity | iam_role |
Security Risks
- Notebook instance with broad role: SageMaker notebooks are interactive compute environments. The execution role often has
s3:*andsagemaker:*because data scientists need to experiment. This role persists after the notebook is shared or the scientist leaves. - Model endpoint with production data access: An inference endpoint's role has access to production databases or S3 buckets for real-time inference. The model may be outdated, but the endpoint and its role persist.
- Training data exfiltration path: The training job role can read sensitive S3 data and write to an output bucket. If the output bucket has a permissive policy, the training data can be exfiltrated.
Discovery APIs
| API | Data Returned |
|---|---|
sagemaker:ListNotebookInstances | Notebook instances with role ARN, status |
sagemaker:DescribeNotebookInstance | Full config including role, network, lifecycle |
sagemaker:ListTrainingJobs | Training jobs with role ARN |
sagemaker:ListEndpoints | Inference endpoints |
sagemaker:DescribeEndpoint | Endpoint config including model and role |
2.8 CodeBuild / CodePipeline Service Roles
What It Is
- CodeBuild projects have a service role that defines what the build environment can do — pull from CodeCommit/GitHub, push to ECR, deploy to Lambda/ECS, store artifacts in S3.
- CodePipeline pipelines have a service role that orchestrates the pipeline stages — triggering CodeBuild, deploying to ECS, invoking Lambda functions.
How It Authenticates
Both services assume their specified IAM role via STS. CodeBuild containers receive credentials as environment variables. CodePipeline uses the role for all stage transitions.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| CodeBuild Project | workload | codebuild_project |
| CodePipeline Pipeline | workload | codepipeline_pipeline |
| CodeBuild Service Role | identity | iam_role |
| CodePipeline Service Role | identity | iam_role |
| Artifact S3 Bucket | resource | s3_bucket |
| ECR Repository (push target) | resource | ecr_repository |
Security Risks
- CI/CD to production authority chain: A CodeBuild role that can push to ECR and update ECS services has transitive authority over production workloads. A compromised build (e.g., via dependency injection) becomes a production compromise.
- Source credential exposure: CodeBuild projects may have environment variables with GitHub tokens or other source credentials. These are visible to anyone with
codebuild:BatchGetProjects. - Pipeline role drift: CodePipeline's service role accumulates permissions as new stages are added. Old stage permissions are never removed.
Discovery APIs
| API | Data Returned |
|---|---|
codebuild:ListProjects + codebuild:BatchGetProjects | Projects with service role, environment, source config |
codepipeline:ListPipelines + codepipeline:GetPipeline | Pipeline stages, actions, service role |
codebuild:ListBuildsForProject | Recent build history |
2.9 EMR Roles
What It Is
Amazon EMR clusters use multiple IAM roles:
- EMR Service Role (
EMR_DefaultRole): Used by the EMR service to provision EC2 instances and manage the cluster. - EC2 Instance Profile Role (
EMR_EC2_DefaultRole): Used by the EC2 instances in the cluster — this is the runtime identity for Spark/Hadoop jobs. - EMR Notebooks Execution Role: Used by EMR notebooks for interactive analysis.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| EMR Cluster | workload | emr_cluster |
| EC2 Instance Profile Role | identity | iam_role |
| Service Role | identity | iam_role (infrastructure) |
Security Risks
- Default roles with broad permissions: The AWS-managed default EMR roles (
EMR_DefaultRole,EMR_EC2_DefaultRole) have broad permissions. Many customers use these defaults without customization. - Long-running clusters with stale roles: EMR clusters for data processing may run for months. The instance profile role retains all permissions, even after the processing need changes.
- EMRFS encryption key access: EMR clusters accessing encrypted S3 data need KMS permissions. The instance profile role's KMS grants may extend beyond the specific keys needed.
Discovery APIs
| API | Data Returned |
|---|---|
emr:ListClusters | All clusters with state |
emr:DescribeCluster | Service role, instance profile, security config |
emr:ListSteps | Job steps and execution history |
2.10 Redshift Service Roles
What It Is
Amazon Redshift clusters can be associated with one or more IAM roles. These roles are used for:
COPYandUNLOADcommands (reading from/writing to S3)- Redshift Spectrum (querying S3 data via Glue Catalog)
- Cross-service access (Lambda UDFs, Kinesis ingestion)
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| Redshift Cluster | workload | redshift_cluster |
| Associated IAM Role(s) | identity | iam_role |
| Default IAM Role (for the cluster) | Relationship: RUNS_AS (default authority) |
Security Risks
- Multiple roles on a cluster: Redshift allows associating multiple roles. Any query can use
iam_roleparameter to select which role to assume. This means the effective authority of the cluster is the UNION of all associated roles. - COPY command data exfiltration: A role with
s3:GetObjecton broad prefixes allows any Redshift user with COPY access to read arbitrary S3 data. - Cross-account S3 access: Redshift roles frequently have cross-account S3 access for data sharing. The cross-account trust is in the role, not visible from the Redshift console.
Discovery APIs
| API | Data Returned |
|---|---|
redshift:DescribeClusters | Cluster details including associated IAM role ARNs |
redshift:DescribeLoggingStatus | Audit logging configuration |
3. Service-Linked Roles
What It Is
Service-linked roles (SLRs) are IAM roles created automatically by AWS services. They have a predefined trust policy (trusting only that service) and a predefined set of permissions. They cannot be modified by the customer — only deleted (and only if the service is not in use).
Examples:
AWSServiceRoleForElasticLoadBalancing— created by ELBAWSServiceRoleForAmazonEKS— created by EKSAWSServiceRoleForSecurityHub— created by Security HubAWSServiceRoleForConfig— created by AWS Config
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| Service-Linked Role | identity | service_linked_role |
These are low-priority for NHI risk assessment because they are AWS-managed and cannot be modified. However, they should be visible in the graph for completeness — they are real IAM identities with real permissions that can appear in CloudTrail.
Security Risks
- Low direct risk: SLRs are tightly scoped by AWS. The primary risk signal is their existence — an SLR indicates a service is enabled in the account, which informs the attack surface.
- Indirect dependency risk: A service-linked role for a service (e.g., RDS) may have permissions that are broader than expected. Since customers cannot modify the policy, they may not realize what the SLR can do.
- Inventory signal: The set of SLRs in an account tells you which services are active. This is useful for attack surface mapping.
Discovery APIs
| API | Data Returned |
|---|---|
iam:ListRoles (filter by path /aws-service-role/) | All service-linked roles |
iam:GetRole | Trust policy, description, max session duration |
iam:ListAttachedRolePolicies | AWS-managed policy attached to the SLR |
4. Cross-Account Assume-Role Chains
What It Is
Cross-account role assumption is the mechanism by which a principal in Account A assumes a role in Account B. This is the primary mechanism for:
- Multi-account workload architectures
- Shared services access
- Third-party vendor access
- Management/audit account access
Role chaining occurs when the assumed role in Account B then assumes a role in Account C. AWS allows role chaining up to the session duration limits, but chained sessions have a maximum duration of 1 hour.
How It Authenticates
- Principal in Account A calls
sts:AssumeRolewith the role ARN in Account B. - The role in Account B's trust policy must allow Account A's principal.
- STS returns temporary credentials scoped to the role in Account B.
- The role in Account B can then call
sts:AssumeRolefor a role in Account C (chaining).
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type |
|---|---|
| Cross-account trust relationship | Relationship: TRUSTS from target role to source principal |
| AssumeRole hop | Increments auth_chain_depth on the access path |
| ExternalId condition | Property on the TRUSTS relationship |
| Role chain (A→B→C) | Multiple ASSUMES edges in sequence |
Security Risks — The Confused Deputy Problem
The confused deputy attack occurs when a service (Account B) is tricked into using its cross-account access on behalf of an unauthorized principal. The classic prevention is the ExternalId condition in the trust policy. Without ExternalId:
- Customer A tells Vendor to assume role
arn:aws:iam::111111111111:role/VendorRole. - Vendor assumes the role and does its work.
- Malicious Customer B tells Vendor to assume
arn:aws:iam::111111111111:role/VendorRole(Customer A's role ARN). - If the trust policy only checks
"Principal": {"AWS": "arn:aws:iam::VENDOR:root"}, the Vendor can assume Customer A's role on behalf of Malicious Customer B.
The fix: "Condition": {"StringEquals": {"sts:ExternalId": "customer-specific-secret"}}.
Cross-account trust amplification: A role in a dev/sandbox account trusts a role in the production account. If the dev account is compromised (weaker controls), the attacker can assume the prod role and access production data.
Stale cross-account trusts: A vendor relationship ends. The trust policy in the customer's account still allows the vendor to assume the role. The vendor retains access indefinitely.
Realistic scenario: Company sets up a cross-account role for a SaaS monitoring vendor in 2023. The vendor contract ends in 2024. Nobody removes the trust policy. The vendor's AWS account (VENDOR_ACCOUNT_ID) is still listed in the trust policy of a role with ReadOnlyAccess to the production account. The vendor (or anyone who compromises the vendor account) can still assume the role and read all production data. SecurityV0 discovers: cross-account trust to external account, no recent execution evidence (the vendor stopped using it), no owner assigned to the trust relationship.
Discovery APIs
Cross-account trusts are discovered by parsing trust policies:
| API | Data Returned |
|---|---|
iam:ListRoles + iam:GetRole | Trust policy documents — parse Principal field for external account IDs |
CloudTrail AssumeRole events | Actual cross-account assumption activity with source account, role ARN, and timestamps |
iam:GetServiceLastAccessedDetails | Whether the role was recently used |
| IAM Access Analyzer | External access findings — roles accessible from outside the account/organization |
5. OIDC Federation Providers
What It Is
AWS IAM supports OIDC (OpenID Connect) identity providers as trusted identity sources. This allows external systems to exchange their identity tokens for AWS temporary credentials without long-lived access keys. The most common NHI patterns:
- GitHub Actions OIDC: GitHub Actions workflows exchange GitHub-issued JWTs for AWS credentials via
sts:AssumeRoleWithWebIdentity. - GitLab CI/CD OIDC: Same pattern, GitLab-issued JWTs.
- Terraform Cloud/Enterprise: Terraform Cloud exchanges its workload identity token for AWS credentials.
- EKS IRSA (covered in section 2.3): EKS cluster OIDC provider for pod identity.
- Any OIDC-compliant IdP: Custom identity providers can be registered.
How It Authenticates
- The external system (e.g., GitHub Actions) issues a JWT (OIDC token) with claims about the identity (repository, branch, workflow, actor).
- The workload calls
sts:AssumeRoleWithWebIdentitywith the JWT and the target role ARN. - The role's trust policy validates the JWT issuer (OIDC provider URL), audience, and optional subject/claim conditions.
- STS returns temporary credentials.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| IAM OIDC Provider | connection | oidc_provider |
| GitHub Actions workflow | workload | github_workflow (external, cross-system) |
| GitLab CI job | workload | gitlab_ci_job (external, cross-system) |
| Terraform Cloud workspace | workload | terraform_workspace (external, cross-system) |
| IAM Role (trusting the OIDC provider) | identity | iam_role |
| OIDC token exchange | execution_evidence | sts_assume_role_web_identity |
Relationships:
GitHub Workflow → AUTHENTICATES_VIA → OIDC Provider → TRUSTS → IAM Role
Security Risks
- Overly broad OIDC trust conditions: The IAM role's trust policy does not restrict which repositories, branches, or environments can assume it. Example:
"Condition": {"StringLike": {"token.actions.githubusercontent.com:sub": "repo:org-name/*"}}allows ANY repository in the organization to assume the role, including forks and new repos created by junior developers. - Missing audience restriction: The
audcondition is not checked. Any token from the OIDC provider can be used. - No branch/environment pinning: The trust policy allows any branch to assume the role, not just
mainorproduction. A feature branch build can deploy to production. - GitHub Actions fork attack: If the repository allows workflows from forks, a malicious fork can trigger a workflow that assumes the IAM role.
- Stale OIDC providers: The OIDC provider is registered but the corresponding external system no longer exists or has been reconfigured.
Realistic scenario: A team sets up GitHub Actions OIDC for deployment. The trust policy condition is "StringLike": {"token.actions.githubusercontent.com:sub": "repo:acme-corp/*:*"}. A developer creates a new repo acme-corp/test-sandbox with a workflow that assumes the deployment role and runs aws s3 ls s3://production-data-lake/. The role has s3:* on production buckets. SecurityV0 discovers: OIDC trust allows any repo in the org, authority path from any GitHub workflow to production S3, no branch/environment restriction.
Discovery APIs
| API | Data Returned |
|---|---|
iam:ListOpenIDConnectProviders | All registered OIDC providers with ARN |
iam:GetOpenIDConnectProvider | Provider URL, client IDs, thumbprints, creation date |
| IAM Role trust policy analysis | Roles whose trust policy references the OIDC provider URL |
CloudTrail AssumeRoleWithWebIdentity events | Actual OIDC-based role assumptions with source identity claims |
6. SAML Federation (Entra ID / Okta SSO into AWS)
What It Is
AWS IAM supports SAML 2.0 identity providers for federated access. This is how enterprise SSO systems (Microsoft Entra ID, Okta, Ping Identity, ADFS) grant users access to AWS accounts. While SAML federation is primarily a human identity mechanism, it is relevant to NHI for several reasons:
- Federated users can create and manage NHIs — permission sets determine who can create roles, Lambda functions, etc.
- Service accounts in the IdP can federate into AWS just like human users.
- The IdP is the ownership source — when a human leaves the IdP, their ability to manage AWS workloads should also cease.
How It Authenticates
- User authenticates to the SAML IdP (Entra ID, Okta).
- IdP issues a SAML assertion with role ARNs the user is allowed to assume.
- The user/application calls
sts:AssumeRoleWithSAMLwith the SAML assertion. - STS validates the assertion against the SAML provider registered in IAM.
- STS returns temporary credentials for the specified role.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| SAML Identity Provider | connection | saml_provider |
| Federated User/Service Account | identity | Mapped to source IdP identity |
| IAM Role (trusting SAML provider) | identity | iam_role |
Security Risks
- Stale SAML provider configuration: The SAML provider metadata (certificate, endpoint) is outdated. If the IdP rotates certificates, AWS stops validating assertions correctly — but if the old certificate is not removed, old assertions may still work.
- Broad role mapping: The SAML assertion maps all members of an IdP group to an AWS role with
AdministratorAccess. When new members join the group, they automatically get full AWS access. - Service account federation: An Okta service account or Entra ID application can obtain SAML assertions programmatically (via Okta API, Entra ID SAML bearer grant). This turns a SAML federation into an NHI access path.
Discovery APIs
| API | Data Returned |
|---|---|
iam:ListSAMLProviders | All registered SAML providers |
iam:GetSAMLProvider | SAML metadata document, creation date, expiry |
| IAM Role trust policy analysis | Roles trusting arn:aws:iam::ACCOUNT:saml-provider/PROVIDER_NAME |
CloudTrail AssumeRoleWithSAML events | Actual SAML-based role assumptions |
7. AWS IAM Identity Center (SSO)
What It Is
IAM Identity Center (formerly AWS SSO) is the recommended centralized access management service for AWS Organizations. It manages:
- Identity source: Built-in directory, or external (Entra ID, Okta, etc.) via SCIM provisioning + SAML.
- Permission sets: Named collections of IAM policies (e.g.,
AdministratorAccess,ViewOnlyAccess, custom). - Account assignments: Mappings of (user or group) → permission set → AWS account.
When a user accesses an account through Identity Center, a role is created in the target account (format: AWSReservedSSO_{PermissionSetName}_{random}) and the user assumes it.
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| Identity Center Instance | connection | identity_center |
| Permission Set | role | sso_permission_set |
| Account Assignment | Relationship: ASSIGNED_TO (permission set → account) | |
| User/Group in Identity Center | identity | sso_user / sso_group |
| Generated SSO Role in account | identity | iam_role (auto-managed) |
Security Risks — NHI Relevance
- Permission set drift: Permission sets are modified to add permissions for one team's need. Because the same permission set applies across multiple accounts, the change affects all assigned accounts.
- Orphaned account assignments: A team is dissolved but their Identity Center group still has assignments to production accounts. The generated roles persist.
- Over-assigned permission sets: A group of 50 engineers is assigned
AdministratorAccessto 10 accounts "for convenience." Each of those 50 engineers can create, modify, or delete NHIs (IAM roles, Lambda functions, etc.) in those accounts. - Delegated admin misconfiguration: Identity Center can delegate admin to a non-management account. If the delegated admin account is compromised, the attacker controls all account assignments.
- NHI management authority: Permission sets that include
iam:*,lambda:*, orbedrock:*grant the ability to create and modify non-human identities. Tracking who has this authority is critical for NHI governance.
Discovery APIs
| API | Data Returned |
|---|---|
sso-admin:ListInstances | Identity Center instance(s) |
sso-admin:ListPermissionSets | All permission sets |
sso-admin:DescribePermissionSet | Permission set detail, session duration, relay state |
sso-admin:GetInlinePolicyForPermissionSet | Inline policy on the permission set |
sso-admin:ListManagedPoliciesInPermissionSet | Managed policies in the permission set |
sso-admin:ListAccountAssignments | User/group → permission set → account mappings |
sso-admin:ListAccountsForProvisionedPermissionSet | Which accounts a permission set is deployed to |
identitystore:ListUsers | Users in the Identity Center directory |
identitystore:ListGroups | Groups in the Identity Center directory |
identitystore:ListGroupMemberships | Group memberships |
8. Resource-Based Policies
What It Is
Several AWS services support resource-based policies — policies attached directly to a resource rather than to an identity. These policies can grant access to principals in other accounts without requiring a role assumption. They are a separate authority path that bypasses identity-based policy evaluation.
Key services with resource-based policies:
| Service | Resource | Policy API |
|---|---|---|
| S3 | Bucket | s3:GetBucketPolicy |
| KMS | Key | kms:GetKeyPolicy |
| Lambda | Function | lambda:GetPolicy |
| SQS | Queue | sqs:GetQueueAttributes (Policy attribute) |
| SNS | Topic | sns:GetTopicAttributes (Policy attribute) |
| Secrets Manager | Secret | secretsmanager:GetResourcePolicy |
| ECR | Repository | ecr:GetRepositoryPolicy |
| API Gateway | REST API | apigateway:GET /restapis/{id}/policy |
| EventBridge | Event Bus | events:DescribeEventBus |
| Step Functions | State Machine (HTTP tasks) | Defined in state machine definition |
| Bedrock | Agent / Knowledge Base | Resource policy via bedrock:GetAgentPolicy etc. |
SecurityV0 Entity Mapping
Resource-based policies create authority edges that are distinct from identity-based paths:
| Pattern | SecurityV0 |
|---|---|
S3 bucket policy grants s3:GetObject to Account B | resource (S3 bucket) → GRANTS_ACCESS_TO → identity (principal in Account B) |
| Lambda resource policy allows EventBridge to invoke | resource (Lambda) → INVOCABLE_BY → workload (EventBridge rule) |
| KMS key policy grants decrypt to Role X | resource (KMS key) → DECRYPTABLE_BY → identity (Role X) |
Resource-based policies are important because they can grant access independently of the principal's identity-based policies. A principal with zero IAM permissions can still access a resource if the resource's policy allows it.
Security Risks
- Public access: Resource policies with
"Principal": "*"grant access to anyone, including unauthenticated principals (for S3 with ACLs disabled). - Cross-account grants without role assumption: A resource policy can grant access to an entire account (
"Principal": {"AWS": "arn:aws:iam::EXTERNAL_ACCOUNT:root"}), meaning any identity in that account can access the resource. - Policy sprawl: Resource policies are managed per-resource, not centrally. Auditing them requires querying every resource individually.
- KMS as implicit authority layer: Many resources are encrypted with KMS. Even if an identity has
s3:GetObjectpermission, it also needskms:Decrypton the bucket's KMS key. Conversely, a KMS key policy that grantskms:Decryptbroadly creates an implicit authority expansion.
Discovery APIs
See the table above — each service has its own API for retrieving the resource policy. The connector must query each service individually.
9. Secrets Manager and Systems Manager Parameter Store
What It Is
AWS Secrets Manager stores, rotates, and manages secrets (database credentials, API keys, OAuth tokens). Secrets are encrypted with KMS and accessed via secretsmanager:GetSecretValue.
AWS Systems Manager Parameter Store stores configuration data and secrets. Parameters of type SecureString are encrypted with KMS. Accessed via ssm:GetParameter.
Both are critical for NHI because they store the credentials that workloads use to authenticate to non-AWS systems (databases, SaaS APIs, internal services).
SecurityV0 Entity Mapping
| AWS Concept | SecurityV0 Entity Type | Subtype |
|---|---|---|
| Secrets Manager Secret | credential | secrets_manager_secret |
| SSM SecureString Parameter | credential | ssm_secure_parameter |
| SSM String/StringList Parameter | resource | ssm_parameter |
| Secret rotation configuration | Property on the credential entity | |
| KMS key used for encryption | resource | kms_key |
Note: In the earlier AWS connector research, these were mapped as resource entities. For NHI-focused analysis, secrets that contain authentication credentials should be typed as credential entities, because they are the authentication mechanism for downstream access paths. The distinction: a credential is something a workload USES to authenticate; a resource is something a workload accesses as a destination.
Relationships:
workload → READS_SECRET → credential(Lambda env var referencesSECRET_ARN)credential → AUTHENTICATES_TO → resource(the secret contains DB credentials for a specific database)credential → ENCRYPTED_BY → resource(KMS key protecting the secret)identity (IAM role) → CAN_READ → credential(viasecretsmanager:GetSecretValuepermission)
Security Risks
- Unrotated secrets: Secrets Manager supports automatic rotation, but many secrets have rotation disabled. A 2-year-old database password is a significant risk.
- Broad secret access: An IAM role has
secretsmanager:GetSecretValueonResource: *— it can read every secret in the account. - Resource policy on secrets: Secrets can have resource-based policies granting cross-account access. A secret containing production database credentials may be accessible from a dev account.
- Environment variable exposure: Lambda functions reference secrets via environment variables (
SECRET_ARN). If the Lambda role haslambda:GetFunctionConfiguration(meta-permissions), it can discover other functions' secret references. - Orphaned secrets: A secret was created for an integration that no longer exists. The secret still contains valid credentials that could be used if discovered.
Realistic scenario: A secrets rotation Lambda was set up for the production database password in Secrets Manager. The rotation Lambda's execution role was given secretsmanager:* and rds:* for convenience. The rotation Lambda failed 6 months ago (CloudWatch alarm was not set up). The secret has not been rotated in 6 months. The rotation Lambda's role still has write access to all secrets and all RDS instances. SecurityV0 discovers: stale credential (unrotated secret), overprivileged identity (rotation Lambda role), dormant workload (rotation Lambda not executing), and the production database password is 6 months old.
Discovery APIs
| API | Data Returned |
|---|---|
secretsmanager:ListSecrets | All secrets with name, ARN, rotation config, last rotated date, last accessed date |
secretsmanager:DescribeSecret | Secret metadata, versions, rotation status, KMS key ID |
secretsmanager:GetResourcePolicy | Resource-based policy on the secret |
ssm:DescribeParameters | All parameters with type, tier, last modified |
ssm:GetParametersByPath | Parameters under a path prefix with metadata |
ssm:ListTagsForResource | Tags on parameters |
Important: The connector must NEVER call secretsmanager:GetSecretValue or ssm:GetParameter (for SecureString). The connector reads metadata only, never actual secret values.
10. AWS STS — Temporary Credentials and Session Tokens
What It Is
AWS Security Token Service (STS) is the service that issues temporary credentials for all role-based access. Every role assumption, federation, and cross-account access flows through STS.
STS operations:
| Operation | Use Case |
|---|---|
AssumeRole | Cross-account access, service-to-role, role chaining |
AssumeRoleWithWebIdentity | OIDC federation (GitHub Actions, EKS IRSA) |
AssumeRoleWithSAML | SAML federation (Entra ID, Okta) |
GetSessionToken | MFA-protected temporary credentials for IAM Users |
GetFederationToken | Custom federated sessions (legacy pattern) |
GetCallerIdentity | Returns the identity of the caller — useful for debugging |
SecurityV0 Entity Mapping
STS itself is not an entity — it is the mechanism by which identities are assumed and credentials are issued. STS events appear as execution_evidence:
| STS Event | SecurityV0 Entity Type | Subtype |
|---|---|---|
AssumeRole call | execution_evidence | sts_assume_role |
AssumeRoleWithWebIdentity call | execution_evidence | sts_assume_role_web_identity |
AssumeRoleWithSAML call | execution_evidence | sts_assume_role_saml |
| Issued session | Transient — not stored as entity, but the caller → role relationship is recorded |
Security Risks
- Session token persistence: Temporary credentials are valid for 1-12 hours. If exfiltrated, they are usable until expiry. Unlike access keys, they cannot be individually revoked (must revoke the role's sessions).
- Session policy injection:
AssumeRolecan include an optional session policy that further restricts permissions. If the calling code does not use session policies, the assumed role gets its full permissions. - Role chaining duration limits: Chained role sessions are limited to 1 hour maximum, regardless of the role's MaxSessionDuration. This is a control, but it also means automation must handle credential refresh correctly.
- Regional STS endpoint exposure: STS has a global endpoint and regional endpoints. Using regional endpoints reduces latency but also means CloudTrail events may be logged in different regions.
Discovery APIs
STS is primarily discovered through CloudTrail, not through direct API calls:
| API | Data Returned |
|---|---|
CloudTrail events for sts:AssumeRole | Source principal, target role, session name, duration, source IP |
CloudTrail events for sts:AssumeRoleWithWebIdentity | OIDC provider, subject claim, audience |
CloudTrail events for sts:AssumeRoleWithSAML | SAML provider, subject, role |
sts:GetCallerIdentity | Current caller identity (useful for connector self-check) |
11. AWS NHI Security Features
11.1 IAM Access Analyzer
What it is: A service that analyzes resource policies and IAM policies to identify resources shared with external entities, unused access, and policy validation issues.
Two analyzer types:
-
External access analyzer (zone of trust = organization or account): Identifies resources accessible from outside the zone of trust. Finds S3 buckets, IAM roles, KMS keys, Lambda functions, SQS queues, and Secrets Manager secrets that allow external access.
-
Unused access analyzer: Identifies IAM roles and users with unused permissions, unused roles, and unused access keys. This is the highest-value signal for NHI scope drift detection.
SecurityV0 relevance:
- External access findings map directly to
scope_driftorreachability_driftfindings in SecurityV0. - Unused access findings map to
dormant_authorityfindings. - Access Analyzer findings can be ingested as pre-computed signals rather than re-deriving them from policy text.
Key APIs:
| API | Data Returned |
|---|---|
accessanalyzer:ListAnalyzers | Analyzers configured in the account |
accessanalyzer:ListFindings | Findings per analyzer — resource ARN, principal, condition, status |
accessanalyzer:GetFinding | Finding detail with resource policy analysis |
accessanalyzer:ListAccessPreviewFindings | Preview of what findings would result from a policy change |
accessanalyzer:GenerateAccessNotDecidable | Identifies policies that are ambiguous |
Unused access analyzer specific APIs:
| API | Data Returned |
|---|---|
accessanalyzer:ListFindingsV2 | Unused access findings — unused roles, permissions, access keys |
Finding types: UnusedIAMRole, UnusedIAMUserAccessKey, UnusedIAMUserPassword, UnusedPermission | Each with last-accessed date and specific unused actions |
11.2 IAM Credential Reports
What it is: A CSV report generated on demand containing security status for every IAM user in the account.
Fields include:
- User ARN, creation time
- Password enabled, last used, last changed, next rotation
- Access key 1 & 2: active, last rotated, last used date, last used region, last used service
- MFA active
SecurityV0 relevance: This is the single best API for discovering dormant IAM User NHIs. A user with access_key_1_last_used_date more than 90 days ago and access_key_1_active = true is a stale credential finding.
APIs:
| API | Data Returned |
|---|---|
iam:GenerateCredentialReport | Triggers report generation (asynchronous) |
iam:GetCredentialReport | Returns the CSV report |
11.3 IAM Access Advisor (Service Last Accessed)
What it is: Provides data on when an IAM entity (user, role, group) last accessed each AWS service. This data is available for up to 400 days.
SecurityV0 relevance: If a role has s3:* permissions but the S3 service was last accessed 350 days ago, that is a strong signal for permission accumulation / dormant authority.
APIs:
| API | Data Returned |
|---|---|
iam:GenerateServiceLastAccessedDetails | Triggers data generation for a specific entity |
iam:GetServiceLastAccessedDetails | Service name, last authenticated date, last authenticated entity, total authenticated entities |
iam:GetServiceLastAccessedDetailsWithEntities | Which specific entities accessed a service |
11.4 CloudTrail for Identity Activity
What it is: AWS CloudTrail logs every API call made in the account (management events by default, data events optionally). This is the primary source of execution_evidence for the SecurityV0 graph.
Priority CloudTrail events for NHI discovery:
| Event | NHI Signal |
|---|---|
AssumeRole | Role assumption — who assumed what role, when, from where |
AssumeRoleWithWebIdentity | OIDC federation activity |
AssumeRoleWithSAML | SAML federation activity |
CreateRole, AttachRolePolicy, PutRolePolicy | Role creation and permission changes |
Invoke (Lambda) | Lambda function execution |
StartExecution (Step Functions) | State machine execution |
GetSecretValue (Secrets Manager) | Secret access — highly sensitive |
GetParameter (SSM) | Parameter access |
GetObject, PutObject (S3) | Data access (data events — must be enabled) |
CreateAccessKey, DeleteAccessKey | Access key lifecycle |
CreateUser, DeleteUser | IAM user lifecycle |
ConsoleLogin | Human activity (useful for NHI context — who manages what) |
Organization trails: In multi-account setups, an organization trail logs events from all member accounts to a central S3 bucket. This is the preferred ingestion pattern for SecurityV0.
APIs:
| API | Data Returned |
|---|---|
cloudtrail:DescribeTrails | Trail configuration, S3 bucket, SNS topic, log file validation |
cloudtrail:GetTrailStatus | Whether the trail is logging, last delivery time |
cloudtrail:GetEventSelectors | Which events are being logged (management, data) |
S3 GetObject on trail bucket | Actual log files (compressed JSON) |
| Athena queries over trail S3 prefix | Aggregated event analysis |
11.5 AWS Config Rules for IAM Compliance
What it is: AWS Config continuously evaluates resource configurations against rules. Several managed rules are relevant to NHI hygiene:
| Config Rule | NHI Signal |
|---|---|
iam-user-unused-credentials-check | IAM users with unused credentials (customizable threshold) |
iam-root-access-key-check | Root account has active access keys |
access-keys-rotated | Access keys older than threshold (90 days default) |
iam-policy-no-statements-with-admin-access | Policies granting *:* |
iam-policy-no-statements-with-full-access | Policies granting service:* |
iam-role-managed-policy-check | Roles missing required managed policies |
iam-user-no-policies-check | IAM users with directly attached policies (vs group-based) |
iam-no-inline-policy-check | Entities with inline policies |
secretsmanager-secret-periodic-rotation | Secrets without rotation |
secretsmanager-secret-unused | Unused secrets |
lambda-function-settings-check | Lambda runtime, memory, timeout compliance |
SecurityV0 relevance: AWS Config findings can be ingested as supporting evidence for SecurityV0 findings. They are not a primary discovery source but provide validation and compliance context.
APIs:
| API | Data Returned |
|---|---|
config:GetComplianceDetailsByConfigRule | Compliance status per resource per rule |
config:DescribeComplianceByConfigRule | Summary compliance per rule |
config:GetResourceConfigHistory | Resource configuration changes over time |
12. Comprehensive Ownership Decay Scenarios
This section consolidates the most important ownership decay patterns across all AWS NHI types into a reference catalog for SecurityV0 finding design.
12.1 The Departed Engineer Pattern
Trigger: Human leaves the organization.
Decay chain:
- Engineer created IAM roles, Lambda functions, Step Functions state machines.
- Engineer's human IAM account or SSO access is deactivated (HR offboarding).
- The NHIs the engineer created continue operating autonomously.
- Nobody knows they exist, what they do, or why they have the permissions they have.
- Over time, automated processes attach additional policies to these NHIs to fix integration issues.
- The NHIs accumulate permissions far beyond their original scope.
SecurityV0 detection: ownership_status: orphaned + execution_30d > 0 (actively executing but no owner).
12.2 The Accumulated Permission Pattern
Trigger: Incremental policy changes over months/years.
Decay chain:
- Role is created with minimal permissions for a specific workload.
- Feature requests require new permissions — each added incrementally.
- Old permissions are never reviewed or removed.
- The role accumulates 15 managed policies and 3 inline policies over 2 years.
- The effective permission set is
s3:*,dynamodb:*,lambda:*,secretsmanager:*,sqs:*onResource: *. - The workload only uses 4 specific S3 buckets and 2 DynamoDB tables.
SecurityV0 detection: scope_drift finding. Access Advisor shows services last accessed, IAM Access Analyzer shows unused permissions.
12.3 The Decommissioned Workload Pattern
Trigger: Business process changes, workload is superseded.
Decay chain:
- A Lambda function processes daily reports.
- The team migrates to a new reporting system.
- The old Lambda function is not deleted — "just in case."
- The EventBridge rule still triggers the function daily.
- The function fails silently (the target API was decommissioned).
- The function's execution role retains broad permissions to production data.
- Two years later, nobody remembers the function exists.
SecurityV0 detection: dormant_authority finding — role has broad permissions but Access Advisor shows no service access. Or: execution evidence shows the function is invoked but returns errors (evidence of purposeless execution).
12.4 The Vendor Trust Decay Pattern
Trigger: Third-party vendor relationship changes.
Decay chain:
- Cross-account role is created for a vendor with
ReadOnlyAccess. - Vendor needs write access for a project —
PowerUserAccessis added. - Project ends. The additional permissions are not removed.
- Vendor contract is not renewed. The trust policy is not removed.
- Vendor's AWS account still has assume-role access to the customer's production account with
PowerUserAccess.
SecurityV0 detection: Cross-account trust to external account + no recent execution evidence + no owner assigned.
12.5 The Scope Creep via Shared Role Pattern
Trigger: Multiple workloads sharing one role.
Decay chain:
- A role is created for Lambda Function A.
- Lambda Function B needs similar access, so it reuses the same role.
- Function B needs additional S3 access — added to the shared role.
- Function C joins the shared role and needs DynamoDB access — added.
- Now Functions A, B, and C all have S3 + DynamoDB + original permissions.
- Function A only needs the original permissions.
SecurityV0 detection: Identity Access Surface shows 3 workloads sharing one identity with 5+ roles and permissions exceeding any individual workload's need.
12.6 The OIDC Federation Scope Expansion Pattern
Trigger: Organization adds repositories, teams, or CI/CD pipelines.
Decay chain:
- GitHub OIDC is configured with trust for
repo:org/infra-repo:ref:refs/heads/main. - A new team needs deployment access — trust is broadened to
repo:org/*:*. - Any new repository in the org can now assume the deployment role.
- A junior developer creates a test repo with a workflow that assumes the role.
- The test workflow has access to production deployment permissions.
SecurityV0 detection: OIDC trust condition analysis shows wildcard patterns. Cross-reference with the list of repos that have actually assumed the role (from CloudTrail) to show the gap between intended and actual scope.
13. AWS API Coverage Summary for Connector
This table summarizes all AWS APIs the connector needs for comprehensive NHI discovery:
Core IAM APIs (all identities)
iam:ListUsers, iam:GetUser, iam:ListAccessKeys, iam:GetAccessKeyLastUsed
iam:ListRoles, iam:GetRole, iam:ListRolePolicies, iam:GetRolePolicy
iam:ListAttachedRolePolicies, iam:ListAttachedUserPolicies
iam:GetPolicy, iam:GetPolicyVersion
iam:ListInstanceProfiles, iam:GetInstanceProfile
iam:ListGroupsForUser, iam:ListGroups, iam:GetGroup
iam:ListMFADevices
iam:ListOpenIDConnectProviders, iam:GetOpenIDConnectProvider
iam:ListSAMLProviders, iam:GetSAMLProvider
iam:ListRoleTags, iam:ListUserTags
iam:GenerateCredentialReport, iam:GetCredentialReport
iam:GenerateServiceLastAccessedDetails, iam:GetServiceLastAccessedDetails
Workload APIs
lambda:ListFunctions, lambda:GetFunction, lambda:GetPolicy, lambda:ListEventSourceMappings
ecs:ListClusters, ecs:DescribeClusters, ecs:ListServices, ecs:DescribeServices
ecs:ListTaskDefinitions, ecs:DescribeTaskDefinition
eks:ListClusters, eks:DescribeCluster, eks:ListPodIdentityAssociations, eks:DescribePodIdentityAssociation
states:ListStateMachines, states:DescribeStateMachine
events:ListRules, events:ListTargetsByRule, events:DescribeRule
glue:GetJobs, glue:GetCrawlers, glue:GetConnections
sagemaker:ListNotebookInstances, sagemaker:ListEndpoints, sagemaker:ListTrainingJobs
codebuild:ListProjects, codebuild:BatchGetProjects
codepipeline:ListPipelines, codepipeline:GetPipeline
emr:ListClusters, emr:DescribeCluster
redshift:DescribeClusters
bedrock:ListAgents, bedrock:GetAgent, bedrock:ListKnowledgeBases, bedrock:GetKnowledgeBase
bedrock:ListFlows, bedrock:GetFlow, bedrock:ListAgentActionGroups
Resource / Credential APIs
s3:ListAllMyBuckets, s3:GetBucketPolicy, s3:GetBucketAcl, s3:GetBucketLocation
s3:GetBucketEncryption
secretsmanager:ListSecrets, secretsmanager:DescribeSecret, secretsmanager:GetResourcePolicy
ssm:DescribeParameters, ssm:GetParametersByPath, ssm:ListTagsForResource
kms:ListKeys, kms:DescribeKey, kms:GetKeyPolicy, kms:ListAliases
ecr:DescribeRepositories, ecr:GetRepositoryPolicy
dynamodb:ListTables, dynamodb:DescribeTable
rds:DescribeDBInstances, rds:DescribeDBClusters
sqs:ListQueues, sqs:GetQueueAttributes
sns:ListTopics, sns:GetTopicAttributes
Organization / Identity Center APIs
organizations:ListAccounts, organizations:DescribeOrganization
organizations:ListOrganizationalUnitsForParent, organizations:DescribeOrganizationalUnit
organizations:ListPolicies, organizations:DescribePolicy
sso-admin:ListInstances, sso-admin:ListPermissionSets, sso-admin:DescribePermissionSet
sso-admin:ListAccountAssignments, sso-admin:GetInlinePolicyForPermissionSet
sso-admin:ListManagedPoliciesInPermissionSet
identitystore:ListUsers, identitystore:ListGroups, identitystore:ListGroupMemberships
Security Feature APIs
accessanalyzer:ListAnalyzers, accessanalyzer:ListFindings, accessanalyzer:GetFinding
config:GetComplianceDetailsByConfigRule, config:DescribeComplianceByConfigRule
cloudtrail:DescribeTrails, cloudtrail:GetTrailStatus, cloudtrail:GetEventSelectors
Evidence APIs (CloudTrail via S3)
s3:GetObject, s3:ListBucket (on CloudTrail bucket only)
athena:StartQueryExecution, athena:GetQueryExecution, athena:GetQueryResults
14. Entity Mapping Summary Table
This is the complete mapping from AWS NHI types to SecurityV0 entities:
| AWS NHI / Concept | SecurityV0 Entity Type | Subtype | Priority |
|---|---|---|---|
| IAM User (programmatic) | identity | machine_account | Phase 1 |
| IAM Role | identity | iam_role | Phase 1 |
| IAM Group | identity | iam_group | Phase 1 |
| Service-Linked Role | identity | service_linked_role | Phase 2 |
| IAM Managed Policy | permission | iam_policy | Phase 1 |
| IAM Inline Policy | permission | iam_inline_policy | Phase 1 |
| SSO Permission Set | role | sso_permission_set | Phase 3 |
| Permission statement | permission | iam_permission | Phase 1 |
| Access Key pair | credential | aws_access_key | Phase 1 |
| Secrets Manager Secret | credential | secrets_manager_secret | Phase 1 |
| SSM SecureString Parameter | credential | ssm_secure_parameter | Phase 1 |
| Lambda Function | workload | lambda_function | Phase 1 |
| Step Functions State Machine | workload | step_function | Phase 1 |
| ECS Task Definition / Service | workload | ecs_task / ecs_service | Phase 2 |
| EKS Workload | workload | eks_workload | Phase 2 |
| EC2 Instance | workload | ec2_instance | Phase 3 |
| Bedrock Agent | workload | bedrock_agent | Phase 1 |
| Bedrock Flow | workload | bedrock_flow | Phase 1 |
| Glue Job | workload | glue_job | Phase 3 |
| SageMaker Notebook/Endpoint | workload | sagemaker_* | Phase 3 |
| CodeBuild Project | workload | codebuild_project | Phase 2 |
| CodePipeline Pipeline | workload | codepipeline_pipeline | Phase 2 |
| EMR Cluster | workload | emr_cluster | Phase 3 |
| Redshift Cluster | workload | redshift_cluster | Phase 3 |
| OIDC Provider | connection | oidc_provider | Phase 2 |
| SAML Provider | connection | saml_provider | Phase 3 |
| Identity Center Instance | connection | identity_center | Phase 3 |
| EventBridge Connection | connection | eventbridge_connection | Phase 1 |
| S3 Bucket | resource | s3_bucket | Phase 1 |
| DynamoDB Table | resource | dynamodb_table | Phase 1 |
| RDS Instance/Cluster | resource | rds_instance | Phase 2 |
| SQS Queue | resource | sqs_queue | Phase 2 |
| SNS Topic | resource | sns_topic | Phase 2 |
| ECR Repository | resource | ecr_repository | Phase 2 |
| KMS Key | resource | kms_key | Phase 2 |
| AWS Account | Tenant context | aws_account | Phase 0 |
| Organizational Unit | Tenant context | aws_ou | Phase 0 |
| CloudTrail event | execution_evidence | cloudtrail_event | Phase 1 |
| IAM Access Analyzer finding | Connector metadata (not an entity — see note below) | access_analyzer_finding | Phase 1 |
Next Action
Status: research-complete
Decision needed from: PO / CTO (Ivan)
Recommended action: Use this catalog as the authoritative reference for AWS connector entity mapping. The phasing aligns with the AWS Integration Strategy.
Design decisions resolved:
-
Secrets Manager / SSM secrets →
credential. These are authentication material that workloads USE to authenticate downstream — they participate in the execution chain viaUSESedges. Modeling asresourcewould miss credential-chain patterns. -
OIDC / SAML providers →
connection. These are outbound integration configurations (like ServiceNow REST Messages), not identities themselves. They enable theAUTHENTICATES_TOedge between systems. -
IAM Managed Policies →
permission. A policy document contains permission statements (Allow/Deny on Actions+Resources). This maps to thepermissionentity type, notrole. IAM roles are the identity-bearing entity that HAS permissions — the policy is the permission grant itself. -
IAM Access Analyzer findings → connector metadata, not
execution_evidence. Access Analyzer output is derived posture analysis ("this role has unused permissions"), not proof of observed execution activity. It feeds the connector'sevidenceCompletenessmetadata and helps generate SecurityV0 findings, but does not become anexecution_evidenceentity. CloudTrail events and service-last-accessed data ARE execution evidence.
GitHub Issue: not yet created — create in sv0-connectors after strategy adoption.
Sources
- AWS IAM User Guide — Users
- AWS IAM User Guide — Roles
- AWS IAM User Guide — Service-linked roles
- AWS IAM User Guide — OIDC Federation
- AWS IAM User Guide — SAML Federation
- AWS IAM User Guide — Cross-account access
- AWS IAM User Guide — Confused deputy problem
- AWS IAM User Guide — Permission boundaries
- AWS IAM Access Analyzer
- AWS IAM Access Analyzer unused access
- AWS IAM Credential Report
- AWS IAM Access Advisor
- AWS STS API Reference
- Amazon EC2 Instance Metadata
- Amazon ECS Task IAM Role
- Amazon ECS Task Execution Role
- Amazon EKS Pod Identity
- Amazon EKS IRSA
- AWS Lambda Execution Role
- AWS Step Functions IAM
- AWS Glue IAM Roles
- Amazon SageMaker Execution Roles
- AWS CodeBuild Service Role
- AWS CodePipeline Service Role
- Amazon EMR IAM Roles
- Amazon Redshift IAM Roles
- AWS IAM Identity Center
- AWS Secrets Manager
- AWS Systems Manager Parameter Store
- AWS CloudTrail
- AWS Config Managed Rules
- Configuring GitHub Actions OIDC in AWS
- Resource-based policies in AWS