PushBackLog

Principle of Least Privilege

Hard enforcement Complete by PushBackLog team
Topic: security Topic: quality Skillset: backend Skillset: devops Technology: generic Stage: execution Stage: review Stage: deployment

Principle of Least Privilege

Status: Complete
Category: Security
Default enforcement: Hard
Author: PushBackLog team


Tags

  • Topic: security, quality
  • Skillset: backend, devops
  • Technology: generic
  • Stage: execution, review, deployment

Summary

Every component, user, or system should be granted only the minimum permissions required to perform its intended function. Access should be scoped as narrowly as possible and revoked when no longer needed. Over-privileged components dramatically expand the blast radius of any security incident.


Rationale

Blast radius containment

The principle of least privilege (PoLP) is a foundational security control because it limits the damage any single compromised component can cause. If a service only has read access to one database table, a successful attack on that service cannot exfiltrate the entire database, modify records, or pivot to other systems. If a CI/CD pipeline only has permission to push to one container registry path, a compromised build cannot overwrite production infrastructure.

The question is not “what permissions might this ever need?” — it is “what permissions does this need right now to do its job?”

PoLP at every layer

Least privilege applies at multiple levels in a modern system:

  • Database: application users have SELECT/INSERT/UPDATE on specific tables, not SUPERUSER
  • Cloud IAM: service roles have specific actions on specific resources, not * on *
  • API access: scoped tokens with specific capabilities, not blanket admin tokens
  • File system: application processes run as non-root, with read/write only on needed paths
  • Network: services communicate on specific ports to specific targets, not open egress
  • Human access: developers have read access to production logs, not write access to production databases

Zero standing privilege

The most rigorous interpretation of PoLP is zero standing privilege (ZSP): no persistent elevated access. When a developer needs production DB access for an incident, they request temporary access that is automatically revoked after a time window. This is increasingly achievable with tools like AWS IAM temporary credentials, Vault dynamic secrets, and JIT (just-in-time) access systems.


Guidance

Cloud IAM: write specific, not permissive

// Bad: wildcard policy
{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

// Good: scoped to exactly what's needed
{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::my-app-uploads/*"
}

Database users

Create a dedicated database user per service with only the tables and operations that service legitimately needs:

-- application user: read/write on own tables only
CREATE USER app_user WITH PASSWORD '...';
GRANT SELECT, INSERT, UPDATE ON orders, order_items TO app_user;
-- No DELETE, no access to users table, no schema modifications

-- migration user: separate, used only during deployments
CREATE USER migrations_user WITH PASSWORD '...';
GRANT ALL ON SCHEMA public TO migrations_user;

Access review cadence

Access typeReview frequency
External API keys and tokensQuarterly
CI/CD pipeline permissionsOn each pipeline change + quarterly
Cloud IAM rolesQuarterly; on every team change
Human production accessOn each role/team change; bi-annually
Service-to-service permissionsWhen service boundaries change

Examples

Lambda function with scoped IAM

# serverless.yml / CDK
iamRoleStatements:
  - Effect: Allow
    Action:
      - dynamodb:GetItem
      - dynamodb:PutItem
    Resource: !GetAtt OrdersTable.Arn
  # No access to other tables, no admin actions

This Lambda can only read/write the orders table. A compromise of this function cannot affect the users table, the payments table, or any S3 bucket.

Service account in Kubernetes

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: order-service
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["order-service-db-creds"]  # Only its own secret
    verbs: ["get"]

The service account can read exactly one secret. No cluster-admin, no wildcard resource access.


Anti-patterns

1. Admin accounts for everything

Using root AWS credentials, SUPERUSER database connections, or global admin API tokens in application code because it’s easier than scoping. This means any exploit has unlimited access.

2. Wildcard permissions in IAM

"Action": "*" or "Resource": "*" in production IAM policies. Often the result of copying a working dev policy to production without review.

3. Shared credentials across services

When multiple services share a single service account or database user, a compromise of one service exposes all of them, and access cannot be revoked per-service.

4. CI/CD pipelines with production write access they don’t use

A pipeline that only deploys to a staging environment should not have production deployment permissions. Separate pipelines, separate roles.

5. No access review

Permissions granted and never reviewed. Former employees, decommissioned services, and “temporary” admin access accumulate silently.



Part of the PushBackLog Best Practices Library. Suggest improvements →