Skip to main content
HIGHIAMReliability

ECS Container Secrets Without Execution Role Access

TL-PERM-004

What this rule checks

An ECS container definition pulls Secrets[].ValueFrom from an in-template Secrets Manager secret or SSM parameter, but the task EXECUTION role (which the ECS agent uses to resolve secrets) lacks secretsmanager:GetSecretValue / ssm:GetParameters. Containers fail to start with ResourceInitializationError.

How to fix it

  1. 1Use ecs.Secret.fromSecretsManager() / ecs.Secret.fromSsmParameter() in the container definition β€” CDK grants the execution role automatically
  2. 2Or grant manually: secret.grantRead(taskDefinition.executionRole)
  3. 3Note it is the EXECUTION role that resolves container secrets, not the task role
FlaggedThe container declares a secret via ValueFrom, but the execution role β€” which is what the ECS agent uses to resolve secrets at container start β€” has no secretsmanager:GetSecretValue on it. The container fails to start with ResourceInitializationError.
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';

const dbSecret = new secretsmanager.Secret(this, 'DbSecret');

const executionRole = new iam.Role(this, 'ExecRole', {
  assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});

new ecs.CfnTaskDefinition(this, 'Task', {
  family: 'app',
  requiresCompatibilities: ['FARGATE'],
  cpu: '256',
  memory: '512',
  networkMode: 'awsvpc',
  executionRoleArn: executionRole.roleArn,
  containerDefinitions: [
    {
      name: 'app',
      image: 'public.ecr.aws/docker/library/busybox:latest',
      secrets: [{ name: 'DB_PASSWORD', valueFrom: dbSecret.secretArn }],
    },
  ],
});
Fixedecs.Secret.fromSecretsManager() on the L2 task definition wires the same ValueFrom AND grants the execution role read access to the secret automatically, so the container can start.
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';

const dbSecret = new secretsmanager.Secret(this, 'DbSecret');

const taskDefinition = new ecs.FargateTaskDefinition(this, 'Task', {
  cpu: 256,
  memoryLimitMiB: 512,
});

taskDefinition.addContainer('app', {
  image: ecs.ContainerImage.fromRegistry(
    'public.ecr.aws/docker/library/busybox:latest'
  ),
  secrets: { DB_PASSWORD: ecs.Secret.fromSecretsManager(dbSecret) },
});

CDK Insights pinpoints the exact file and line in your CDK source for every finding, so you can jump straight to the fix.

Affected resource types

AWS::ECS::TaskDefinitionAWS::SecretsManager::SecretAWS::SSM::ParameterAWS::IAM::Role

AWS documentation

Read the AWS guidance

Intentional? Suppress this finding

Sometimes a flag is deliberate β€” a genuinely public endpoint, say. You can dismiss TL-PERM-004 and the reason is kept in the report, not silently hidden.

In .cdk-insights.json:

{
  "ignoreRules": [
    { "id": "TL-PERM-004", "reason": "Why this is intentional" }
  ]
}

Or inline in your CDK code:

Validations.of(scope).acknowledge({
  id: 'cdk-insights::TL-PERM-004',
  reason: 'Why this is intentional',
});

Use the rule ID TL-PERM-004 shown above β€” not the CDK-* ID from SARIF / GitHub code scanning. To dismiss every finding on one construct instead, use ignorePaths. Suppression docs β†’

Catch this in your stack

$ npx cdk-insights scan

CDK Insights runs this and 131+ other rules locally against your synthesised CDK app β€” free, no account, your code never leaves your machine.

More IAM rules