Least Privilege #

In the world of cybersecurity, the Principle of Least Privilege (PoLP) is the golden rule that cannot be negotiated. This principle states that every identity — whether a human user, automated system, CI/CD pipeline, or application service — may only be given the minimum access permissions genuinely required to perform its specific task, and nothing more. Although conceptually it sounds very simple, in practice this principle is one of the most frequently violated security rules. Many organizations take shortcuts by granting broad administrative access for practicality and to speed up development processes. The consequences of this laziness are fatal: one leaked access key pair belonging to a developer with excessive access rights can become a gateway for hackers to take over and destroy our entire company cloud infrastructure. This article dissects the blast radius concept, access rights anti-patterns, proper permission analysis methodology, and privilege escalation mitigation.

The Blast Radius Concept and Why Least Privilege Matters #

To understand the urgency of the least privilege principle, we must understand the Blast Radius concept (the radius of damage impact). Blast radius is defined as the maximum damage level that can occur if a component in our system suffers a security compromise (e.g., a server is hacked or credentials leak).

flowchart TD
    subgraph NoPoLP ["Scenario A: WITHOUT Least Privilege"]
        VulnerabilityA["1. Vulnerability in Web App"] --> CompromiseA["2. Hacker Steals VM Instance Token"]
        CompromiseA --> AdminAccess["3. Token Has AdministratorAccess"]
        AdminAccess --> DamageA1["- Delete All Production Databases"]
        AdminAccess --> DamageA2["- Encrypt File Storage via Ransomware"]
        AdminAccess --> DamageA3["- Exfiltrate Customer Data"]
        AdminAccess --> DamageA4["- Launch Hundreds of Crypto Miner VMs"]
    end

    subgraph WithPoLP ["Scenario B: WITH Least Privilege"]
        VulnerabilityB["1. Vulnerability in Web App"] --> CompromiseB["2. Hacker Steals VM Instance Token"]
        CompromiseB --> MinimalAccess["3. Token Can Only Read One S3 Bucket & Write DynamoDB"]
        MinimalAccess --> DamageB1["- Read limited data in only one bucket"]
        MinimalAccess --> DamageB2["- Write junk data to one table (Can be rolled back)"]
        MinimalAccess --> PreventB1["✗ Denied when trying to access the Main Database"]
        MinimalAccess --> PreventB2["✗ Denied when trying to create a New User"]
    end
    
    style AdminAccess stroke:#d32f2f,stroke-width:2px
    style MinimalAccess stroke:#388e3c,stroke-width:2px

Scenario Comparison Analysis #

  • Scenario A (Without Least Privilege): A developer is too lazy to analyze the specific permissions needed by their Node.js web application. They attach the AdministratorAccess policy (Allow All) to the VM instance so the web app runs without permission errors. When hackers find a Remote Code Execution (RCE) security hole in that web app, they immediately steal the instance’s temporary credential token. With administrative rights, hackers can delete all production VMs, steal company secrets, and lock down our entire cloud system for ransom. The blast radius in this scenario is our entire cloud account.
  • Scenario B (With Least Privilege): The security team applies the least privilege principle from the start. The Node.js app is only given permission to read objects from one asset bucket and write to one database session table. When hackers exploit the same RCE hole and steal the instance token, they’re surprised because their requests to launch new VMs or delete databases are denied by the IAM evaluation engine. Damage is fully isolated to that one asset bucket and one session table. The blast radius is successfully minimized as much as possible.

Common Anti-Patterns #

In cloud development, there are several bad permission-writing patterns we must avoid from the start:

1. Brutal Wildcard (*) Usage on Actions and Resources #

Using the asterisk character (*) to allow all actions on all resources is the fastest way to trigger disaster.

// ANTI-PATTERN: A wildcard giving full access to all S3 buckets
// DON'T: An app that only needs to read profile pictures could delete entire finance buckets!
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

// --- Separator ---

// CORRECT: Strict restriction at the Action level and physical Resource location
// ✓ CORRECT: Only allow read actions (GetObject) on the profile folder in a specific bucket
{
  "Effect": "Allow",
  "Action": [
    "s3:GetObject"
  ],
  "Resource": "arn:aws:s3:::corporate-user-assets/profiles/*"
}

2. Borrowing Built-in Administrator Policies for Experiments #

Often when developing new features (like testing serverless function integration with databases), developers temporarily attach AdministratorAccess with the intention: “I’ll use admin for testing first, then restrict it once the app is working”.

  • Field Reality: “Later” almost never comes. Code successfully stacked with admin permissions gets deployed straight to production to meet release deadlines, leaving dangerous security technical debt.

3. One Shared Permanent Credential (Shared Access Keys) #

Creating a single IAM user named developer-team-prod with full access rights, then creating one Access Key and sharing it via Slack to the whole team.

  • Danger: If one developer’s laptop is lost or infected with malware, hackers get full control. Worse, we can never trace who the actual actor was behind the system deletion activities recorded in audit logs.

How to Analyze and Find the Right Access Rights #

The biggest challenge in applying least privilege is: How do we know exactly what minimal permissions an application needs without causing “Access Denied” errors?

To solve this problem, we can apply the following 4 systematic approaches:

flowchart TD
    Build["1. Write Application Code"] --> Static["2. Static Code Analysis<br>(Scan SDK Calls)"]
    Static --> DevDeploy["3. Deploy to Dev Environment with Audit Log Active"]
    DevDeploy --> TestSuite["4. Run Comprehensive Integration Test Suite"]
    TestSuite --> AccessAnalyzer["5. Run IAM Access Analyzer / CloudTrail Analysis"]
    AccessAnalyzer --> Generate["6. Generate Minimal Access Policy"]
    Generate --> ProdDeploy["7. Deploy Policy to Production"]

1. Static Code Analysis #

Before deploying the application, we can scan the application source code using automated tools to detect which SDK functions are called. If our code only calls s3Client.getObject() and dynamoDB.putItem(), then we only need to register the s3:GetObject and dynamodb:PutItem permissions in the policy document.

2. IAM Access Analyzer (Dynamic Log Analysis) #

Built-in cloud provider services (like AWS IAM Access Analyzer or GCP Policy Analyzer) can analyze an identity’s historical API activity logs over a certain period (e.g., 30 days in a staging environment).

  • The system detects: “This identity has permission for 100 actions, but in the last 30 days it only ever executed 3 actions”.
  • The system then automatically generates a new policy document draft trimming those 97 unused actions for us to use in production.

3. Policy Simulator Testing #

Use the IAM Policy Simulator to virtually test whether our new policy draft would block the application’s main features. We can test simulation scenarios like: “Is role A allowed to GetObject on bucket B?” before the policy is actually applied to active users.


Implementing Least Privilege Across Resources #

PoLP implementation must be adapted based on the accessor category:

1. For Humans (Developers and Administrators) #

  • Environment Separation: Developers may have full permissions (Administrator) in their private personal sandbox environments, limited permissions (Power User) in development environments, but must have Read-Only permissions in production.
  • Just-in-Time (JIT) / Elevated Access: Developers must not have permanent write permissions to production. If a critical incident requires direct bug fixes in production, developers must request temporary access through a security portal. That access automatically expires and is revoked by the system after a certain time limit (e.g., 2 hours).

2. For CI/CD Pipelines (GitHub Actions / GitLab CI) #

  • Avoid Permanent Keys: Don’t store long-lived IAM Access Keys in our GitHub repository secrets. If our GitHub account is hacked or the repo is made public, those keys leak instantly.
  • Use OIDC Federation: Configure our CI/CD cluster to connect using OpenID Connect (OIDC). GitHub Actions pipelines request a unique short-lived JWT token for each run. This token is converted into a temporary IAM Role in the cloud, automatically expiring once the deployment process finishes.
# Example secure authorization configuration using OIDC in GitHub Actions
name: Deploy to Cloud Production
on:
  push:
    branches:
      - main
permissions:
  id-token: write  # ✓ CORRECT: Required to request temporary OIDC JWT tokens
  contents: read

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Git Checkout
        uses: actions/checkout@v4

      - name: Authenticate to Cloud via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-role
          aws-region: ap-southeast-3
          audience: sts.amazonaws.com # Secure temporary token

The Danger of Privilege Escalation #

When designing least privilege policies, we must be wary of Privilege Escalation loopholes. This is a scenario where hackers exploit a set of individually harmless-looking small permissions that, when combined, can be abused to gain full administrative access.

Example Escalation Attack Chain (PassRole & RunInstances): #

Imagine a hacker stole the credentials of a junior developer who only has the following two limited permissions:

  1. ec2:RunInstances (May only launch new Virtual Machines).
  2. iam:PassRole (May only pass/attach existing IAM Roles to new VM instances).

Hacker Exploitation Steps: #

  1. The hacker scans the IAM Role list in our account and finds a role named cloud-admin-role (which has AdministratorAccess permission).
  2. The hacker triggers the RunInstances API to launch a new VM in a public subnet.
  3. Simultaneously, the hacker includes the PassRole parameter to attach cloud-admin-role to that new VM.
  4. The hacker enters the new VM (or injects a user-data start-up script executing a reverse shell command).
  5. From inside the new VM, the hacker queries the instance metadata service (http://169.254.169.254/latest/meta-data/iam/security-credentials/) to retrieve the temporary credential token belonging to cloud-admin-role.
  6. The hacker copies that token to their computer. The hacker now officially has full Administrator rights to destroy our entire cloud account!

How to Mitigate Escalation Loopholes: #

  • Restrict PassRole Permissions: Never grant iam:PassRole with the * resource wildcard. We must restrict so that user can only attach specific roles equivalent to their own access level.
  • Use Permission Boundaries: As discussed in the previous article, boundaries immediately lock a new role’s maximum capability, so hackers can’t use this technique to bypass permission limits set by the central security team.

Summary #

  • Least privilege significantly reduces the blast radius — If application or developer credentials leak, hackers are isolated from other critical systems.
  • Avoid aggressive wildcard (*) usage — Restrict permissions to specific action levels (like read-only GetObject) and specific target resources.
  • Leverage IAM Access Analyzers and CloudTrail — To monitor real transaction activity in staging and trim never-used permissions before promoting to production.
  • Use OIDC federation for CI/CD pipelines — To eliminate the need to store long-lived permanent access keys in repositories.
  • Beware of Privilege Escalation loopholes — Restrict sensitive permissions like iam:PassRole so they can’t be abused to attach admin roles to new VM machines.
  • Apply Just-in-Time (JIT) access — For developers needing production modification rights, limit with automatic expiration periods.

← Previous: IAM   Next: Authentication vs Authorization →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact