Back to Checks
aws

Step Functions state machine has no sensitive credentials in its definition

stepfunctions_statemachine_no_secrets_in_definition
Service: stepfunctionsSeverity: critical
OfficialBuilt-inVerified
Categories:secrets
Resource Type:AwsStepFunctionStateMachine

AWS Step Functions state machines are inspected for hardcoded secrets (keys, tokens, passwords) embedded directly in the state machine definition (Amazon States Language JSON).

Such values indicate sensitive data is stored directly in task parameters instead of being sourced securely.

Risk

Plaintext secrets in state machine definitions reduce confidentiality: values can be viewed in the AWS Console, CLI, and may leak into execution logs or public outputs. Compromised credentials enable unauthorized AWS actions, lateral movement, and data exfiltration.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks stepfunctions_statemachine_no_secrets_in_definition

Recommendation

Store secrets outside the state machine definition and retrieve them securely at runtime using AWS Secrets Manager or AWS Systems Manager Parameter Store.

  • Use the aws-sdk:secretsmanager:getSecretValue integration to fetch secrets dynamically
  • Enforce least privilege on the state machine IAM role
  • Rotate secrets regularly and never embed them in the definition

Remediation

Terraform

resource "aws_sfn_state_machine" "<example_resource_name>" {
  name     = "<example_resource_name>"
  role_arn = "<example_resource_arn>"

  definition = jsonencode({
    Comment = "Example state machine"
    StartAt = "MyTask"
    States = {
      MyTask = {
        Type     = "Task"
        Resource = "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue"
        Parameters = {
          SecretId = "<example_secret_name>"  # Reference secret by name, never hardcode value
        }
        End = true
      }
    }
  })
}

NativeIaC

Resources:
  <example_resource_name>:
    Type: AWS::StepFunctions::StateMachine
    Properties:
      StateMachineName: <example_resource_name>
      RoleArn: <example_resource_arn>
      DefinitionString: |
        {
          "Comment": "Example state machine",
          "StartAt": "MyTask",
          "States": {
            "MyTask": {
              "Type": "Task",
              "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue",
              "Parameters": {
                "SecretId": "<example_secret_name>"
              },
              "End": true
            }
          }
        }

Other

1In AWS Console, go to Step Functions and open your state machine
2Click Edit
3Remove any hardcoded secrets from the definition
4Use AWS Secrets Manager or Parameter Store to retrieve secrets at runtime
5Grant the state machine IAM role permission to access the secret
6Save the updated definition

Check Code

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.stepfunctions.stepfunctions_client import (
    stepfunctions_client,
)


class stepfunctions_statemachine_no_secrets_in_definition(Check):
    """Check that AWS Step Functions state machine definitions contain no hardcoded secrets."""

    def execute(self) -> list[Check_Report_AWS]:
        findings = []
        secrets_ignore_patterns = stepfunctions_client.audit_config.get(
            "secrets_ignore_patterns", []
        )
        validate = stepfunctions_client.audit_config.get("secrets_validate", False)
        state_machines = list(stepfunctions_client.state_machines.values())

        # Collect one payload per state machine (its definition) and scan them
        # all in batched Kingfisher invocations instead of one subprocess each.
        def payloads():
            for index, state_machine in enumerate(state_machines):
                if state_machine.definition:
                    yield index, state_machine.definition

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

        for index, state_machine in enumerate(state_machines):
            report = Check_Report_AWS(metadata=self.metadata(), resource=state_machine)
            report.status = "PASS"
            report.status_extended = f"No secrets found in Step Functions state machine {state_machine.name} definition."

            if state_machine.definition:
                if scan_error:
                    report.status = "MANUAL"
                    report.status_extended = (
                        f"Could not scan Step Functions state machine "
                        f"{state_machine.name} definition for secrets: {scan_error}; "
                        "manual review is required."
                    )
                    findings.append(report)
                    continue
                detect_secrets_output = batch_results.get(index)
                if detect_secrets_output:
                    secrets_string = ", ".join(
                        [
                            f"{secret['type']} on line {secret['line_number']}"
                            for secret in detect_secrets_output
                        ]
                    )
                    report.status = "FAIL"
                    report.status_extended = (
                        f"Potential {'secrets' if len(detect_secrets_output) > 1 else 'secret'} "
                        f"found in Step Functions state machine {state_machine.name} definition "
                        f"-> {secrets_string}."
                    )
                    annotate_verified_secrets(report, detect_secrets_output)

            findings.append(report)
        return findings