Home › Security › Operational security
guide low

Least privilege and isolation

Problem Teams deploying software that touches user data, money or other systems must decide how much access each component, service and person gets, so one compromised part does not expose everything. This guide covers that decision.

Assume every component will be compromised and limit what it can then reach: narrow identities, separate accounts, sandboxed processes.

least-privilegeisolationsandboxingiamcontainerssystemdblast-radius
Overview

Least privilege decides whether a bug in an image resizer is an annoyance or a full database dump. Give each process, service and human only what its job needs, put strong boundaries (accounts, VMs, sandboxes) between different trust levels, and default to "nothing allowed". Cloud platforms, browsers and federal systems all rely on it. Cheapest wins are config: non-root, read-only filesystems, dropped capabilities, scoped IAM.

Key facts

Use when

  • Deploying anything with access to user data, money, or other systems, in any field: cloud platforms (Lambda microVMs), desktop and mobile software (browser sandboxes), government systems (NIST SP 800-53 control AC-6).

Avoid when

  • Never; scale the effort with the blast radius.

Deep dive

Decision table

WorkloadBoundaryWhy
Your own services, trusted codeContainer, non-root, read-only rootfs, dropped caps, seccomp defaultCheap, contains most exploits
Parsing untrusted files (images, PDFs, video)Separate process/container with no network, low memory limit, seccompParsers are the classic RCE entry point
Running customer or AI-generated codeFirecracker/Kata microVM or gVisor; no metadata, egress allowlistKernel exploits escape containers
Plugins inside your processWebAssembly (Wasmtime) with explicit host functionsCapability-based, memory isolated
Prod vs staging vs devSeparate cloud accounts/projects, separate credentialsA dev mistake can't touch prod
Multi-tenant dataPer-tenant keys or RLS + per-tenant authz checksBugs leak one tenant, not all
HumansSSO, short sessions, just-in-time elevation, break-glass account auditedNo standing admin rights
Database accessRole per service with only needed tables/verbs; migrations under a separate roleSQL injection can't DROP or read other data

Code

systemd service hardening (check with systemd-analyze security app.service):

[Service]
ExecStart=/usr/local/bin/app
DynamicUser=yes                 # ephemeral unprivileged user
LoadCredential=db_password:/etc/app/db_password
NoNewPrivileges=yes
ProtectSystem=strict            # / read-only
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
StateDirectory=app              # the only writable path: /var/lib/app
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes      # remove for JIT runtimes (JVM, V8, .NET)
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
CapabilityBoundingSet=          # no capabilities; add CAP_NET_BIND_SERVICE for ports < 1024

Kubernetes pod security context:

securityContext:                # pod level
  runAsNonRoot: true
  runAsUser: 10001
  fsGroup: 10001
  seccompProfile: { type: RuntimeDefault }
containers:
  - name: app
    image: ghcr.io/org/app@sha256:<digest>
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities: { drop: ["ALL"] }
    resources:
      limits: { memory: "512Mi" }
automountServiceAccountToken: false   # unless the pod calls the K8s API

Enforce cluster-wide with Pod Security Admission restricted, plus a default-deny NetworkPolicy per namespace.

Scoped AWS IAM policy (one bucket prefix, read/write, nothing else):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:PutObject"],
    "Resource": "arn:aws:s3:::acme-uploads/tenant-data/*"
  }]
}

Postgres role per service:

CREATE ROLE orders_svc LOGIN;
GRANT SELECT, INSERT, UPDATE ON orders, order_items TO orders_svc;
GRANT USAGE ON SEQUENCE orders_id_seq TO orders_svc;
-- no DELETE, no DDL, no access to users table

Efficiency

Cost: namespaces, cgroups, capability drops, read-only filesystems, scoped IAM policies and per-service database roles cost nothing at runtime; the price of isolation is paid at the boundary (syscalls, VM exits, startup time, memory per sandbox).

Levers:

  • Cheapest boundary for the threat — hardened containers for trusted code, VM-class sandbox only for untrusted code (decision table) — always choose first.
  • Pool or snapshot sandboxes — Firecracker states boot under 125 ms, under 5 MiB overhead per microVM, up to 150 microVMs created per second per host (project site; Agache et al.) — avoids a boot per request.
  • Wasm in-process — Wasmtime avoids a process or VM crossing per call; its pooling allocator speeds instance creation — plugins.
  • gVisor — intercepts syscalls in user space, so syscall- and network-heavy workloads pay most; measure first — untrusted code with light I/O.
  • Short seccomp filters — group allowlists such as @system-service — any container.

Naive → standard → state of the art:

  • Naive: shared root account, all-powerful containers · zero overhead, no isolation
  • Standard: one account per environment plus non-root, read-only, dropped-capability containers · negligible overhead
  • State of the art: pooled or snapshotted microVMs or Wasm for untrusted code · order of ms to start, MiB per sandbox

Implementation notes

  • Start from deny-all and add permissions from observed failures in staging; tools like IAM Access Analyzer generate policies from real usage logs.
  • Egress allowlists matter as much as ingress: most exfiltration and command-and-control needs outbound connections.
  • Separate the deploy identity (can change infrastructure) from the runtime identity (can only use it).
  • Break-glass admin accounts: stored offline, MFA, alert on every use.
  • Re-check privileges regularly; permissions only accumulate unless someone removes them.

Pitfalls

  • Mounting /var/run/docker.sock into a container: equivalent to root on the host.
  • privileged: true or hostNetwork/hostPID "to make it work".
  • Container runs as root because the base image defaults to it; set USER in the Dockerfile.
  • IAM Action: "*" or Resource: "*" granted during development and never narrowed. Use access analyzers to generate policies from real usage.
  • SSRF to 169.254.169.254 stealing the node's cloud credentials; require IMDSv2 with hop limit 1, or block metadata via network policy.
  • One shared service account for all services: no blast-radius boundary.
  • Kubernetes default service account tokens mounted in every pod.
  • CI runners with production admin credentials shared across all repos.
  • Separate VMs that share the same SSH key or the same database superuser.
  • Standing SSH access to production for debugging instead of audited, time-boxed sessions (SSM Session Manager, Teleport).
  • Analytics and BI tools reusing the application's database credentials, with write access they never need.

In production

  • Cloud infrastructure: AWS Lambda runs on Firecracker microVMs (project site).
  • Consumer software: the Chromium sandbox applies least privilege to sandboxed and to controlling code (design doc).
  • Government: NIST SP 800-53 Rev. 5 control AC-6 requires least privilege for federal information systems (NIST).

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
FirecrackerRustfirecracker-microvm/firecrackerdownload a release binary from GitHub releases————microVMs on KVM; AWS Lambda and Fargate
WasmtimeRustbytecodealliance/wasmtimecargo add wasmtime / brew install wasmtime————WebAssembly runtime, capability-based WASI
landlockRustlandlock-lsm/rust-landlockcargo add landlock————unprivileged self-sandboxing of filesystem access (Linux)
gVisorGogoogle/gvisorrunsc, see install guide————user-space kernel; OCI runtime for Docker and Kubernetes
systemdCsystemd/systemdn/a (ships with most Linux distributions)————systemd-analyze security scores unit hardening

Sources

  • Saltzer, Schroeder, The Protection of Information in Computer Systems, Proceedings of the IEEE, 1975. paper
  • Agache et al., Firecracker: Lightweight Virtualization for Serverless Applications, NSDI 2020. paper · code
  • systemd, systemd.exec(5) manual. doc
  • Kubernetes documentation, Pod Security Standards. doc
  • AWS, IAM User Guide: Security best practices in IAM. doc

Used by

Systems (15):

Alternatives and related