Safety

Principle of Least Privilege

Granting only the minimum permissions required for a component or user to do its job.

Overview

The Principle of Least Privilege (PoLP) states that every process, user, and system component should have only the permissions needed to perform its function and nothing more. Violating it maximises blast radius when a component is compromised. It applies to database users, IAM roles, OS processes, microservice-to-microservice permissions, and human access to production systems.

Origin

Jerome Saltzer and Michael Schroeder formalised the principle in "The Protection of Information in Computer Systems" (1975). It became a cornerstone of security architecture in NIST standards and the Orange Book (DoD, 1985). Cloud IAM systems (AWS IAM, 2011) operationalised it at scale. Zero-trust network models (John Kindervag, 2010) extended it to network access.

Examples

Scoped IAM policy for an application role in TypeScript (CDK)

import * as iam from 'aws-cdk-lib/aws-iam';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Stack } from 'aws-cdk-lib';

export function createAppRole(stack: Stack, uploadBucket: s3.Bucket): iam.Role {
  const role = new iam.Role(stack, 'AppRole', {
    assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
    description: 'Role for the web application; upload-only access to S3',
  });

  // Only PutObject to a specific bucket prefix; no GetObject, no DeleteObject
  role.addToPolicy(new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: ['s3:PutObject'],
    resources: [uploadBucket.arnForObjects('uploads/*')],
  }));

  // Read from Secrets Manager for specific secrets only, not *
  role.addToPolicy(new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: ['secretsmanager:GetSecretValue'],
    resources: [
      'arn:aws:secretsmanager:us-east-1:123456789:secret:prod/app/db-*',
      'arn:aws:secretsmanager:us-east-1:123456789:secret:prod/app/stripe-*',
    ],
  }));

  return role;
}

Scoping S3 access to uploads/* means a compromised application cannot read or delete files outside that prefix. Scoping Secrets Manager to specific secret ARN patterns prevents the application from reading secrets intended for other services.

Database user with minimal privileges in Ruby migration

# db/migrate/20250601_create_app_database_user.rb
# Run manually as a superuser; not part of schema migrations
class CreateAppDatabaseUser < ActiveRecord::Migration[7.1]
  def up
    # Application user: cannot create tables, drop tables, or access other schemas
    execute "CREATE USER app_user WITH PASSWORD 'use-secrets-manager-not-this';"
    execute "GRANT CONNECT ON DATABASE app_production TO app_user;"
    execute "GRANT USAGE ON SCHEMA public TO app_user;"

    # Grant DML only on specific tables, not TRUNCATE or DDL
    execute "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;"
    execute "GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;"

    # Separate read-only replica user for reporting
    execute "CREATE USER reporting_user WITH PASSWORD 'use-secrets-manager';"
    execute "GRANT CONNECT ON DATABASE app_production TO reporting_user;"
    execute "GRANT USAGE ON SCHEMA public TO reporting_user;"
    execute "GRANT SELECT ON ALL TABLES IN SCHEMA public TO reporting_user;"
  end
end

The application user cannot DROP TABLE, TRUNCATE, or CREATE TABLE. A SQL injection vulnerability through the application user cannot destroy schema or exfiltrate data from other schemas. The reporting user is read-only; connecting it to the replica prevents reporting queries from impacting primary write performance.

Use Cases

  • 01Database users with DML-only permissions for application code; schema migrations run as a separate privileged user in CI
  • 02AWS Lambda functions with IAM roles scoped to the specific S3 bucket, DynamoDB table, and Secrets Manager paths they need
  • 03CI/CD pipeline tokens with only the permissions needed to deploy (push to ECR, update ECS service) and nothing else
  • 04Developer access to production systems via break-glass IAM roles requiring approval, not standing access

When Not to Use

  • //Do not apply least privilege so granularly that operational work (debugging, querying logs) requires an hour of IAM configuration; balance security with operational practicality
  • //Do not implement least privilege only at the IAM layer while using a single superuser database connection for all application operations
  • //Do not grant permissions permissively and plan to tighten later; AWS Access Analyzer and similar tools help identify unused permissions after the fact but scoping from the start is always easier

Technical Notes

  • AWS IAM Access Analyzer generates least-privilege policies from CloudTrail logs: it analyses 90 days of API calls and generates a policy granting only the actions that were actually used
  • PostgreSQL Row-Level Security (RLS) policies restrict which rows a database user can see or modify; this enforces multi-tenancy at the database layer even if the application layer has a bug
  • Kubernetes RBAC (Role-Based Access Control) via ClusterRole/Role and RoleBinding restricts which API objects pods and service accounts can access; many Kubernetes misconfigurations grant default service accounts cluster-admin rights
  • The confused deputy problem occurs when a privileged component is tricked into performing actions on behalf of a less-privileged caller; AWS IAM's condition keys (aws:SourceAccount, aws:SourceArn) prevent cross-service confused deputy attacks