Cross-Account Access Without Regret: Patterns That Don’t Become a Security Incident

By Shayan Ghasemnezhad on December 18, 2025 · 3 min read

awscross-accountiamsecurity

Cross-account IAM gets messy fast. Trust policies, role chaining, and external IDs—done right so you do not create a lateral movement path.

The moment you move to a multi-account AWS setup, you need cross-account access. CI/CD in the tooling account deploys to production. A monitoring account reads CloudWatch metrics from every workload account. A data pipeline in the analytics account pulls from production databases. Each of these is a trust relationship—and every trust relationship is a potential lateral movement path if misconfigured.

Trust Policies: The Foundation

Cross-account access in AWS works through IAM role assumption. Account A creates a role with a trust policy that allows Account B to assume it. The trust policy is the gate. Get it wrong and you either block legitimate access or open the door too wide.

The minimum viable trust policy specifies the exact principal in the trusting account—not the account root. arn:aws:iam::123456789012:root allows any principal in that account to assume the role. This is almost never what you want. Instead, trust a specific role or user: arn:aws:iam::123456789012:role/ci-deploy.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::111111111111:role/ci-deploy"
    },
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": {
        "sts:ExternalId": "deploy-xacct-2025"
      }
    }
  }]
}

External IDs: What They Solve

The External ID condition prevents the confused deputy problem: a scenario where a third-party service that you trust uses your trust relationship to access another customer’s account. If you grant a SaaS vendor a role in your account, and that vendor also serves your competitor, the External ID ensures the vendor can only use the role when acting on your behalf.

Use External IDs for every third-party trust relationship. For internal cross-account access (your accounts, your principals), External IDs are optional but add a defence layer. They cost nothing to implement and make role assumption explicit.

Role Chaining and Session Duration

Role chaining occurs when a principal assumes role A, then uses role A’s credentials to assume role B. AWS limits chained sessions to one hour regardless of the role’s configured maximum. This catches teams off guard—a deployment that takes 90 minutes will fail mid-way when credentials expire.

The fix is to avoid unnecessary chains. If your CI system needs access to both Account A and Account B, create separate roles in each account that trust the CI principal directly, rather than chaining through an intermediate role. Fewer hops means longer sessions and simpler debugging when something fails.

Permission Boundaries

Permission boundaries cap the maximum permissions that an IAM entity can have, regardless of what policies are attached to it. Use them on cross-account roles to ensure that even if someone attaches AdministratorAccess to a cross-account role, the boundary limits the effective permissions to what you intended.

Decision Framework

For each cross-account access need, define: who needs access (specific principal ARN), what they need to do (scoped IAM policy), and for how long (session duration). Document every trust relationship in a registry—a simple spreadsheet is fine. Review quarterly. Stale trust relationships are the access patterns that become security incidents.

Failure Modes

Trusting an account root instead of a specific principal is the most common mistake. It turns one role into an access path for every user, role, and service in the trusted account. The second failure: using * in the role’s permission policy because “we will scope it later.” Later does not arrive until after the audit finding.

Cross-account access is necessary infrastructure. Treat every trust relationship as a firewall rule: document it, scope it, review it, and remove it when it is no longer needed. The access you forget about is the access an attacker finds.