Back to Checks
aws

ECS task definition has no secrets in environment variables

ecs_task_definitions_no_environment_secrets
Service: ecsSeverity: critical
OfficialBuilt-inVerified
Categories:secrets
Resource Type:AwsEcsTaskDefinition

ECS task definitions are analyzed for plaintext secrets placed in container environment variables. It identifies values that resemble credentials (keys, tokens, passwords) within container definitions.

Risk

Exposed secrets in env vars undermine confidentiality via logs, task metadata, and introspection.

With container or read-only API access, attackers can reuse credentials to read databases, modify records (integrity), pivot to other services, and trigger outages or unauthorized costs (availability).

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks ecs_task_definitions_no_environment_secrets

Recommendation

Store secrets in AWS Secrets Manager or SSM Parameter Store and inject them at runtime instead of plaintext env vars.

Apply least privilege via task roles, enable regular rotation, avoid logging secret values, and prefer ephemeral credentials for downstream services.

Remediation

Terraform

resource "aws_ecs_task_definition" "<example_resource_name>" {
  family                = "<example_resource_name>"
  # Critical: define container secrets instead of plaintext env vars
  container_definitions = jsonencode([
    {
      name    = "app"
      image   = "<image>"
      secrets = [
        { name = "DB_PASSWORD", valueFrom = "<secret_arn_or_parameter_arn>" } # Critical: inject secret at runtime
      ]
    }
  ])
}

NativeIaC

Resources:
  <example_resource_name>:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: <example_resource_name>
      ContainerDefinitions:
        - Name: app
          Image: <image>
          Secrets:                      # Critical: use Secrets instead of plaintext env vars
            - Name: DB_PASSWORD         # Critical: inject secret at runtime
              ValueFrom: <secret_arn_or_parameter_arn>  # Critical: reference Secrets Manager/SSM parameter

Other

1In the AWS Console, go to ECS > Task Definitions and open your task definition
2Create a new revision
3For each container, remove any sensitive values from Environment variables
4Under Environment variables, add a new entry in the Secrets section with Name (e.g., DB_PASSWORD) and ValueFrom pointing to your Secrets Manager/SSM parameter
5Save to create the new revision
6If using a service, update the service to use the new task definition revision and deploy

Check Code

from json import dumps

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.utils.utils import (
    SecretsScanError,
    annotate_verified_secrets,
    detect_secrets_scan_batch,
)
from prowler.providers.aws.services.ecs.ecs_client import ecs_client


class ecs_task_definitions_no_environment_secrets(Check):
    def execute(self):
        findings = []
        secrets_ignore_patterns = ecs_client.audit_config.get(
            "secrets_ignore_patterns", []
        )
        validate = ecs_client.audit_config.get("secrets_validate", False)
        task_definitions = [
            task_definition
            for task_definition in ecs_client.task_definitions.values()
            if task_definition.container_definitions is not None
        ]

        # Scan every (task definition, container) environment in batched
        # Kingfisher invocations instead of one subprocess per container.
        # Payloads are yielded lazily so only a chunk is held/written at a time.
        def environment_payloads():
            for td_index, task_definition in enumerate(task_definitions):
                for c_index, container in enumerate(
                    task_definition.container_definitions
                ):
                    if container.environment:
                        dump_env_vars = {
                            env_var.name: env_var.value
                            for env_var in container.environment
                        }
                        yield (td_index, c_index), dumps(dump_env_vars, indent=2)

        scan_error = None
        try:
            batch_results = detect_secrets_scan_batch(
                environment_payloads(),
                excluded_secrets=secrets_ignore_patterns,
                validate=validate,
            )
        except SecretsScanError as error:
            batch_results = {}
            scan_error = error

        for td_index, task_definition in enumerate(task_definitions):
            report = Check_Report_AWS(
                metadata=self.metadata(), resource=task_definition
            )
            report.resource_id = f"{task_definition.name}:{task_definition.revision}"
            report.status = "PASS"
            extended_status_parts = []
            all_secrets = []

            if scan_error and any(
                container.environment
                for container in task_definition.container_definitions
            ):
                report.status = "MANUAL"
                report.status_extended = (
                    f"Could not scan ECS task definition {task_definition.name} with "
                    f"revision {task_definition.revision} for secrets: {scan_error}; "
                    "manual review is required."
                )
                findings.append(report)
                continue

            for c_index, container in enumerate(task_definition.container_definitions):
                container_secrets_found = []

                if container.environment:
                    original_env_vars = [
                        env_var.name for env_var in container.environment
                    ]
                    detect_secrets_output = batch_results.get((td_index, c_index))
                    if detect_secrets_output:
                        all_secrets.extend(detect_secrets_output)
                        secrets_string = ", ".join(
                            [
                                f"{secret['type']} on the environment variable {original_env_vars[secret['line_number'] - 2]}"
                                for secret in detect_secrets_output
                            ]
                        )
                        container_secrets_found.append(
                            f"Secrets in container {container.name} -> {secrets_string}"
                        )
                if container_secrets_found:
                    report.status = "FAIL"
                    extended_status_parts.extend(container_secrets_found)
            if report.status == "FAIL":
                report.status_extended = (
                    f"Potential secrets found in ECS task definition {task_definition.name} with revision {task_definition.revision}: "
                    + "; ".join(extended_status_parts)
                    + "."
                )
                annotate_verified_secrets(report, all_secrets)
            else:
                report.status_extended = f"No secrets found in variables of ECS task definition {task_definition.name} with revision {task_definition.revision}."
            findings.append(report)

        return findings