Skip to main content
emnode / learn
Compliance Medium severity

AWS Security Hub · ECS

ECS.21: Windows containers should run as non-admin users

Written and reviewed by Emnode · Last reviewed

What does AWS Security Hub ECS.21 check?

ECS.21 evaluates task definitions whose operatingSystemFamily is WINDOWS_SERVER (or unset) and fails when any container omits the user field or sets it to the default containeradministrator account — the built-in Windows identity with full admin rights inside the container.

Why does ECS.21 matter?

A container running as containeradministrator that gets compromised — through an app vulnerability, a poisoned dependency, or an exposed endpoint — hands the attacker full administrative rights inside it. Container-escape techniques then become materially easier and more damaging, because the attacker already holds the highest privilege the container offers rather than having to escalate to it.

How do I fix ECS.21?

  1. Create or use a lower-privilege Windows account in the image and set the container definition's user field to it.
  2. Confirm the application's files, registry keys, and ports are accessible to that account.
  3. Register the new revision and redeploy the Windows service.

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.21 a false positive?

ECS.21 is Windows-specific; the Linux root case is the separate ECS.20 control. A backlog of these accumulates quietly because nothing forces teams to set user on Windows tasks — most never do.

Part of the learning path Lock down access
  • 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.20 Linux containers should run as non-root users