Skip to content

ECS task definitions should not share the host's process namespace

Sharing the host's PID namespace removes a fundamental isolation boundary. Containers with host PID visibility can enumerate all processes running on the host, send signals to them, and read sensitive information from /proc. An attacker who compromises a single container can then inspect the environment variables of every other process on the host, including those belonging to other tenants or the ECS agent itself, and environment variables frequently carry secrets.

This setting is rarely needed in production. Most use cases that appear to require host PID mode (debugging, process monitoring) can be handled through sidecar containers sharing task-level PID namespaces or dedicated observability tooling.

Retrofit consideration

If any running tasks depend on host PID visibility for custom health checks or process monitoring, removing pid_mode = "host" will break that functionality. Identify those dependencies before making changes.

Implementation

Choose the approach that matches how you manage Terraform.

Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.

module "ecs" {
  source  = "pcidss.compliance.tf/terraform-aws-modules/ecs/aws//modules/service"
  version = ">=6.0.0"

  cluster_arn = "arn:aws:ecs:us-east-1:123456789012:cluster/example-cluster"
  container_definitions = {
    main = {
      essential = true
      image     = "public.ecr.aws/docker/library/httpd:latest"
      portMappings = [
        {
          containerPort = 80
          protocol      = "tcp"
        }
      ]
    }
  }
  cpu        = 256
  memory     = 512
  name       = "abc123"
  subnet_ids = ["subnet-12345678"]
}

module "ecs" {
  source  = "nistcsf.compliance.tf/terraform-aws-modules/ecs/aws//modules/service"
  version = ">=6.0.0"

  cluster_arn = "arn:aws:ecs:us-east-1:123456789012:cluster/example-cluster"
  container_definitions = {
    main = {
      essential = true
      image     = "public.ecr.aws/docker/library/httpd:latest"
      portMappings = [
        {
          containerPort = 80
          protocol      = "tcp"
        }
      ]
    }
  }
  cpu        = 256
  memory     = 512
  name       = "abc123"
  subnet_ids = ["subnet-12345678"]
}

module "ecs" {
  source  = "nistcsfv11.compliance.tf/terraform-aws-modules/ecs/aws//modules/service"
  version = ">=6.0.0"

  cluster_arn = "arn:aws:ecs:us-east-1:123456789012:cluster/example-cluster"
  container_definitions = {
    main = {
      essential = true
      image     = "public.ecr.aws/docker/library/httpd:latest"
      portMappings = [
        {
          containerPort = 80
          protocol      = "tcp"
        }
      ]
    }
  }
  cpu        = 256
  memory     = 512
  name       = "abc123"
  subnet_ids = ["subnet-12345678"]
}

If you use terraform-aws-modules/ecs/aws//modules/service, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.

module "ecs" {
  source  = "terraform-aws-modules/ecs/aws//modules/service"
  version = ">=6.0.0"

  cluster_arn = "arn:aws:ecs:us-east-1:123456789012:cluster/example-cluster"
  container_definitions = {
    main = {
      essential = true
      image     = "public.ecr.aws/docker/library/httpd:latest"
      portMappings = [
        {
          containerPort = 80
          protocol      = "tcp"
        }
      ]
    }
  }
  cpu        = 256
  memory     = 512
  name       = "abc123"
  subnet_ids = ["subnet-12345678"]
}

Use AWS provider resources directly. See docs for the resources involved: aws_ecs_task_definition.

resource "aws_ecs_task_definition" "this" {
  container_definitions    = jsonencode([{ name = "app", image = "nginx:latest", essential = true, portMappings = [{ containerPort = 80 }] }])
  cpu                      = "256"
  family                   = "pofix-abc123"
  memory                   = "512"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
}

What this control checks

In the aws_ecs_task_definition resource, the pid_mode argument controls which PID namespace containers use. It fails if pid_mode is set to "host". To pass, either omit pid_mode entirely (the default does not share the host namespace) or set it to "task", which shares a PID namespace only among containers within the same task. Fargate tasks do not support "host" PID mode at all and pass by default. For EC2 launch type tasks, explicitly verify that pid_mode is absent or set to "task".

Common pitfalls

  • Revision drift from console or CI/CD re-registration

    ECS task definitions are immutable; each update creates a new revision. If someone registers a new revision via the AWS Console or aws ecs register-task-definition with --pid-mode host, Terraform will not detect the drift because the old revision still matches state. Pin services to Terraform-managed revisions and monitor all active revisions, not just the latest one tracked in state.

  • Confusing pid_mode with ipc_mode

    ipc_mode also accepts "host" and exposes a different namespace: System V IPC. Fixing pid_mode alone does not address IPC namespace sharing. Review both arguments when hardening task definitions.

  • Task-level PID sharing misunderstood as host sharing

    pid_mode = "task" lets containers within the same task share a PID namespace with each other, which is useful for sidecar patterns. It is not the same as "host" and passes this control. Don't remove "task" unless you have a specific reason to fully isolate PID namespaces between containers in the same task.

Audit evidence

Auditors will look for AWS Config rule evaluation results showing all active ECS task definitions in a compliant state, or equivalent output from a Cloud Security Posture Management tool. The DescribeTaskDefinition API response for each active task definition should show pidMode as either null or "task", never "host".

A bulk export using aws ecs list-task-definitions followed by aws ecs describe-task-definition for each ARN works as point-in-time evidence. For continuous assurance, Config recording for AWS::ECS::TaskDefinition resources with an appropriate managed or custom rule provides ongoing evidence with timestamps and change history.

Framework-specific interpretation

PCI DSS v4.0: Requirements 2.2 and 6.3.1 both cover this. Req 2.2 expects system components to be configured securely; leaving pid_mode = "host" on any task in a CDE is a straightforward hardening failure. Req 6.3.1 applies to how security features are defined for software components, and process namespace isolation falls squarely within that scope.

NIST Cybersecurity Framework v2.0: PR.DS and PR.IR both apply. A container that can read /proc for every host process crosses the boundary PR.DS is designed to protect, and PR.IR concerns itself with limiting blast radius when a single workload is compromised. Keeping PID namespaces task-scoped addresses both.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: ecs_task_definition_no_host_pid_mode

  • AWS Config Managed Rule: ECS_TASK_DEFINITION_PID_MODE_CHECK

  • Checkov Check: CKV_AWS_335

  • Powerpipe Control: aws_compliance.control.ecs_task_definition_no_host_pid_mode

  • Prowler Check: ecs_task_definitions_host_namespace_not_shared

  • AWS Security Hub Control: ECS.3

Last reviewed: 2026-03-09