Step Functions Integration Without Role Permissions
TL-PERM-005
What this rule checks
A Step Functions definition declares a service integration (lambda:invoke, dynamodb:getItem, sqs:sendMessage, sns:publish, states:startExecution) targeting an in-template resource, but the state machine role lacks the required action. Task states fail at execution time. Statically unparseable definitions are skipped.
How to fix it
- 1Build the state machine with CDK L2 task constructs (sfn_tasks.LambdaInvoke, DynamoGetItem, SqsSendMessage, ...) which grant the execution role automatically
- 2Or grant the state machine role the integration's required action (e.g. lambda:InvokeFunction, dynamodb:GetItem) scoped to the target resource
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
const fn = new lambda.Function(this, 'Fn', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromInline('exports.handler = async () => {};'),
});
const role = new iam.Role(this, 'SfnRole', {
assumedBy: new iam.ServicePrincipal('states.amazonaws.com'),
});
new sfn.CfnStateMachine(this, 'Machine', {
roleArn: role.roleArn,
definitionString: cdk.Fn.sub(
JSON.stringify({
StartAt: 'Invoke',
States: {
Invoke: {
Type: 'Task',
Resource: 'arn:aws:states:::lambda:invoke',
Parameters: { FunctionName: '${FnArn}' },
End: true,
},
},
}),
{ FnArn: fn.functionArn }
),
});import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks';
const fn = new lambda.Function(this, 'Fn', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromInline('exports.handler = async () => {};'),
});
new sfn.StateMachine(this, 'Machine', {
definitionBody: sfn.DefinitionBody.fromChainable(
new tasks.LambdaInvoke(this, 'Invoke', { lambdaFunction: fn })
),
});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::StepFunctions::StateMachineAWS::IAM::RoleIntentional? Suppress this finding
Sometimes a flag is deliberate โ a genuinely public endpoint, say. You can dismiss TL-PERM-005 and the reason is kept in the report, not silently hidden.
In .cdk-insights.json:
{
"ignoreRules": [
{ "id": "TL-PERM-005", "reason": "Why this is intentional" }
]
}Or inline in your CDK code:
Validations.of(scope).acknowledge({
id: 'cdk-insights::TL-PERM-005',
reason: 'Why this is intentional',
});Use the rule ID TL-PERM-005 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 scanCDK Insights runs this and 131+ other rules locally against your synthesised CDK app โ free, no account, your code never leaves your machine.