AWS Security Hub · ECS
ECS.20: Linux containers should run as non-root users
Written and reviewed by Emnode · Last reviewed
What does AWS Security Hub ECS.20 check?
ECS.20 checks the latest active revision of each Linux task definition and fails when any container has user unset or set to the default root identity ("root" or "0"). It only evaluates definitions whose operatingSystemFamily is LINUX or unset.
Why does ECS.20 matter?
A process running as root inside a container can read and write anything in it, install tools, and probe the runtime. Combined with a kernel or runtime vulnerability, that elevated identity is what makes container-escape practical — the difference between an attacker getting one container and getting the host that runs all your containers. Running as non-root removes that head start.
How do I fix ECS.20?
- Add a non-root USER to the image or set the user field in the container definition to a non-zero UID.
- Make sure files and ports the app needs are accessible to that user (avoid binding privileged ports below 1024).
- Register the new revision and redeploy.
Remediation script · bash
# Inventory: flag containers running as root or with a writable root filesystem.
for fam in $(aws ecs list-task-definition-families --status ACTIVE \
--query 'families[]' --output text); do
aws ecs describe-task-definition --task-definition "$fam" \
--query "taskDefinition.containerDefinitions[?user==null || user=='root' || user=='0' || readonlyRootFilesystem!=\`true\`].{Family:'$fam',Name:name,User:user,ReadOnly:readonlyRootFilesystem}" \
--output text
done
# Harden at the source. Dockerfile:
# RUN addgroup -S app && adduser -S -G app appuser
# USER appuser
# Task definition: non-root user, read-only root with one narrow tmpfs, secrets via ARN.
# "user": "1000:1000",
# "readonlyRootFilesystem": true,
# "mountPoints": [{ "sourceVolume": "scratch", "containerPath": "/tmp", "readOnly": false }],
# "secrets": [{ "name": "DB_PASSWORD",
# "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/checkout/db-AbCdEf" }]
# Register the hardened revision and roll it out (tasks only update on redeploy).
aws ecs register-task-definition --cli-input-json file://checkout-api-hardened.json
aws ecs update-service --cluster prod --service checkout-api \
--task-definition checkout-api --force-new-deployment Full walkthrough (console steps, edge cases and verification) in the lesson Harden ECS container workloads.
Is ECS.20 a false positive?
ECS.20 is the Linux control; the Windows containeradministrator case is handled separately by ECS.21. It also differs from ECS.4 — dropping to a non-root user does not address the host-level privileged flag.
More ECS controls
- ECS.2 An ECS service auto-assigns public IPs to tasks
- ECS.3 A task definition shares the host PID namespace
- ECS.4 A container runs in privileged mode
- ECS.5 A container has a writable root filesystem
- ECS.8 Secrets are passed as plaintext container env vars
- ECS.9 A task definition has no logging configuration
- ECS.10 Fargate services should run latest platform version
- ECS.12 ECS clusters should use Container Insights
- ECS.16 An ECS task set auto-assigns public IPs
- ECS.18 ECS task defs should encrypt EFS volumes in transit
- ECS.19 Capacity providers managed termination protection
- ECS.21 Windows containers should run as non-admin users