AWS Security Hub · ECS
ECS.8: Secrets are passed as plaintext container env vars
Written and reviewed by Emnode · Last reviewed
What does AWS Security Hub ECS.8 check?
ECS.8 fails the latest active task-definition revision when any container's environment block contains a variable named AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or ECS_ENGINE_AUTH_DATA. Those keys are stored as readable plaintext in the task definition document.
Why does ECS.8 matter?
Anything in the environment block is readable by every principal that can call ecs:DescribeTaskDefinition — usually a far broader set than you would trust with a production credential — and it leaks sideways into IaC state, CI logs, and monitoring tools. A leaked long-lived AWS access key is one of the fastest paths to full account compromise, which is why the control names those keys specifically.
How do I fix ECS.8?
- Move the value into AWS Secrets Manager or SSM Parameter Store.
- Reference it from the container's secrets field, which injects it at launch without writing plaintext into the definition.
- Rotate any credential that was previously exposed in environment, then register the cleaned revision.
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.8 a false positive?
Passing ECS.8 is a floor, not proof of safety: it only matches those three exact key names. A DB_PASSWORD or STRIPE_SECRET_KEY in the same block is just as exposed but will not trip this control — move those to secrets too.
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.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
- ECS.21 Windows containers should run as non-admin users