Skip to content

AWS ECS task-definition checks fail open: unexamined resources are reported as PASS #12132

Description

@strichter

ECS task-definition checks fail open: unexamined resources are reported as PASS

Summary

When DescribeTaskDefinition fails for a task definition, EcsService swallows
the exception and leaves container_definitions empty. Every check that
iterates that list then reports PASS, because the checks initialize
report.status = "PASS" and only downgrade to FAIL inside the loop over
containers. An empty list means the loop never executes.

The result is a security check reporting a clean verdict for a resource it never
managed to examine. Because whether a given describe call fails varies between
runs, the same immutable resource can flip verdicts run to run.

Version: 5.22.0 (prowlercloud/prowler:5.22.0), AWS provider.

Reproduction

An account with a large number of ACTIVE task-definition revisions (~4,900 in
our case, accumulated because nothing deregisters old revisions). Run the AWS
provider twice, a few minutes apart, and diff the OCSF output per resource.

Observed across two consecutive runs 20 minutes apart:

Task-definition revisions scanned 4,901 (identical set both runs)
Revisions that changed verdict 2,100 (42.8%)
FAIL → PASS 1,172
PASS → FAIL 928
Checks affected out of 128 run 1

ECS task-definition revisions are immutable, so no resource actually changed.
The net difference in FAIL count was only −244, which is small enough to look
like ordinary run-to-run variance; the underlying churn is two orders of
magnitude larger.

Ground truth via aws ecs describe-task-definition: of 20 sampled revisions
that ecs_task_definitions_containers_readonly_access reported as PASS,
20 had readonlyRootFilesystem unset — which the check itself treats as
FAIL. All 20 were false passes.

Two revisions of the same family, in the same report, with identical
configuration:

revision 2   -> FAIL  "has containers with write access to the root filesystem"
revision 255 -> PASS  "does not have containers with write access to the root filesystems"

Cause

prowler/providers/aws/services/ecs/ecs_service.py:

def _describe_task_definition(self, task_definition):
    try:
        ...
        container_definitions = response["taskDefinition"]["containerDefinitions"]
        for container in container_definitions:
            task_definition.container_definitions.append(ContainerDefinition(...))
    except Exception as error:
        logger.error(...)          # swallowed -> container_definitions stays EMPTY

This is fanned out with __threading_call__ over every task definition, so on
a large account it issues thousands of concurrent DescribeTaskDefinition
calls — exactly the conditions under which some will fail.

prowler/providers/aws/services/ecs/ecs_task_definitions_containers_readonly_access/ecs_task_definitions_containers_readonly_access.py:

report.status = "PASS"                                   # optimistic default
for container in task_definition.container_definitions:  # empty -> body never runs
    if not container.readonly_rootfilesystem:
        report.status = "FAIL"

Failure to gather evidence is indistinguishable, in the output, from evidence of
compliance.

Scope

Six checks read container_definitions and share the pattern:

  • ecs_task_definitions_containers_readonly_access
  • ecs_task_definitions_host_namespace_not_shared
  • ecs_task_definitions_host_networking_mode_users
  • ecs_task_definitions_logging_enabled
  • ecs_task_definitions_no_environment_secrets
  • ecs_task_definitions_no_privileged_containers

In our account the other five return 100% PASS, so a fail-open PASS is
indistinguishable from a correct PASS and the defect is invisible there rather
than absent. ..._readonly_access is the only one whose true verdict is FAIL
for most resources, which is why it is the only one where the bug is observable
as a flip.

Suggested fix

Two independent changes, either of which prevents the false PASS:

  1. Do not let a failed gather look like a successful one. Record that the
    describe failed (e.g. a gathered: bool on the model, or leave
    container_definitions = None to distinguish "none" from "not retrieved"),
    and have the checks skip or report the resource as errored rather than PASS.

  2. Make the checks derive PASS rather than default to it. Computing
    failed = [c for c in containers if not c.readonly_rootfilesystem] and
    setting the status from that still passes an empty list, so this alone is
    insufficient — but combined with (1) it makes the intent explicit.

Retrying throttled describes with backoff would reduce the frequency, but the
fail-open would remain: any residual error still yields PASS. The reporting
behavior is the part worth fixing.

Happy to send a PR

If this looks right to you, I'm glad to open a pull request. I'd rather agree on
the approach first, since the fix touches a shared model: option (1) changes
ContainerDefinition/EcsTaskDefinition gathering semantics and so affects all
six checks, and I don't want to guess at how you'd prefer an
evidence-not-gathered resource to surface — skipped, MANUAL, or a new status.
Tell me which shape you want and I'll follow it, tests included.

Notes

Reported per SECURITY.md, which directs functional and check issues to the
public issue tracker rather than the support desk. Account identifiers and
resource names have been generalized; I'm happy to share the OCSF diff privately
if that would help.

This report was researched and written by Claude (Anthropic), working in our
repository. I reviewed it, re-ran the ground-truth describe-task-definition
checks against our own AWS account, and am filing it deliberately — please treat
it as our report rather than as automated output, and hold it to that standard.
Any errors in it are ours to correct, so do push back if something does not
reproduce on your side.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugprovider/awsIssues/PRs related with the AWS providerseverity/mediumResults in some unexpected or undesired behavior.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions