feat(stepfunctions): add check for secrets in state machine definition (#10570)

Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
Avula Jeevan Yadav
2026-04-09 15:56:29 +02:00
committed by GitHub
co-authored by Andoni A.
parent cccb3a4b94
commit b898f257f1
6 changed files with 270 additions and 0 deletions
+1
View File
@@ -21,6 +21,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222)
- `entra_conditional_access_policy_block_elevated_insider_risk` check for M365 provider [(#10234)](https://github.com/prowler-cloud/prowler/pull/10234)
- `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189)
- `stepfunctions_statemachine_no_secrets_in_definition` check for hardcoded secrets in AWS Step Functions state machine definitions [(#10570)](https://github.com/prowler-cloud/prowler/pull/10570)
- CCC improvements with the latest checks and new mappings [(#10625)](https://github.com/prowler-cloud/prowler/pull/10625)
### 🔄 Changed
@@ -0,0 +1,44 @@
{
"Provider": "aws",
"CheckID": "stepfunctions_statemachine_no_secrets_in_definition",
"CheckTitle": "Step Functions state machine has no sensitive credentials in its definition",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices",
"TTPs/Credential Access",
"Effects/Data Exposure",
"Sensitive Data Identifications/Security"
],
"ServiceName": "stepfunctions",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "AwsStepFunctionStateMachine",
"ResourceGroup": "serverless",
"Description": "**AWS Step Functions state machines** are inspected for **hardcoded secrets** (keys, tokens, passwords) embedded directly in the state machine **definition** (Amazon States Language JSON).\n\nSuch 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.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/step-functions/latest/dg/concepts-amazon-states-language.html",
"https://docs.aws.amazon.com/step-functions/latest/dg/security-best-practices.html",
"https://docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_how-services-use-secrets_step-functions.html",
"https://docs.aws.amazon.com/systems-manager/latest/userguide/integration-ps-secretsmanager.html"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "```yaml\nResources:\n <example_resource_name>:\n Type: AWS::StepFunctions::StateMachine\n Properties:\n StateMachineName: <example_resource_name>\n RoleArn: <example_resource_arn>\n DefinitionString: |\n {\n \"Comment\": \"Example state machine\",\n \"StartAt\": \"MyTask\",\n \"States\": {\n \"MyTask\": {\n \"Type\": \"Task\",\n \"Resource\": \"arn:aws:states:::aws-sdk:secretsmanager:getSecretValue\",\n \"Parameters\": {\n \"SecretId\": \"<example_secret_name>\"\n },\n \"End\": true\n }\n }\n }\n```",
"Other": "1. In AWS Console, go to Step Functions and open your state machine\n2. Click Edit\n3. Remove any hardcoded secrets from the definition\n4. Use AWS Secrets Manager or Parameter Store to retrieve secrets at runtime\n5. Grant the state machine IAM role permission to access the secret\n6. Save the updated definition",
"Terraform": "```hcl\nresource \"aws_sfn_state_machine\" \"<example_resource_name>\" {\n name = \"<example_resource_name>\"\n role_arn = \"<example_resource_arn>\"\n\n definition = jsonencode({\n Comment = \"Example state machine\"\n StartAt = \"MyTask\"\n States = {\n MyTask = {\n Type = \"Task\"\n Resource = \"arn:aws:states:::aws-sdk:secretsmanager:getSecretValue\"\n Parameters = {\n SecretId = \"<example_secret_name>\" # Reference secret by name, never hardcode value\n }\n End = true\n }\n }\n })\n}\n```"
},
"Recommendation": {
"Text": "Store secrets outside the state machine definition and retrieve them securely at runtime using **AWS Secrets Manager** or **AWS Systems Manager Parameter Store**.\n- Use the `aws-sdk:secretsmanager:getSecretValue` integration to fetch secrets dynamically\n- Enforce **least privilege** on the state machine IAM role\n- Rotate secrets regularly and never embed them in the definition",
"Url": "https://hub.prowler.com/check/stepfunctions_statemachine_no_secrets_in_definition"
}
},
"Categories": [
"secrets"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,45 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.utils.utils import detect_secrets_scan
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", []
)
for state_machine in stepfunctions_client.state_machines.values():
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:
detect_secrets_output = detect_secrets_scan(
data=state_machine.definition,
excluded_secrets=secrets_ignore_patterns,
detect_secrets_plugins=stepfunctions_client.audit_config.get(
"detect_secrets_plugins",
),
)
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}."
)
findings.append(report)
return findings
@@ -0,0 +1,180 @@
from datetime import datetime
from unittest import mock
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1
class Test_stepfunctions_statemachine_no_secrets_in_definition:
def test_no_statemachines(self):
stepfunctions_client = mock.MagicMock()
stepfunctions_client.state_machines = {}
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
with (
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
stepfunctions_client,
),
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
stepfunctions_client,
),
):
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
stepfunctions_statemachine_no_secrets_in_definition,
)
check = stepfunctions_statemachine_no_secrets_in_definition()
result = check.execute()
assert len(result) == 0
def test_statemachine_with_no_definition(self):
stepfunctions_client = mock.MagicMock()
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
StateMachine,
StateMachineStatus,
StateMachineType,
)
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
stepfunctions_client.state_machines = {
statemachine_arn: StateMachine(
id="TestStateMachine",
arn=statemachine_arn,
name="TestStateMachine",
status=StateMachineStatus.ACTIVE,
definition=None,
region=AWS_REGION_US_EAST_1,
type=StateMachineType.STANDARD,
creation_date=datetime.now(),
)
}
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
with (
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
stepfunctions_client,
),
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
stepfunctions_client,
),
):
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
stepfunctions_statemachine_no_secrets_in_definition,
)
check = stepfunctions_statemachine_no_secrets_in_definition()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No secrets found in Step Functions state machine TestStateMachine definition."
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == "TestStateMachine"
assert result[0].resource_arn == statemachine_arn
def test_statemachine_with_no_secrets_in_definition(self):
stepfunctions_client = mock.MagicMock()
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
StateMachine,
StateMachineStatus,
StateMachineType,
)
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
stepfunctions_client.state_machines = {
statemachine_arn: StateMachine(
id="TestStateMachine",
arn=statemachine_arn,
name="TestStateMachine",
status=StateMachineStatus.ACTIVE,
definition='{"Comment": "A simple example", "StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Pass", "End": true}}}',
region=AWS_REGION_US_EAST_1,
type=StateMachineType.STANDARD,
creation_date=datetime.now(),
)
}
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
with (
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
stepfunctions_client,
),
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
stepfunctions_client,
),
):
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
stepfunctions_statemachine_no_secrets_in_definition,
)
check = stepfunctions_statemachine_no_secrets_in_definition()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No secrets found in Step Functions state machine TestStateMachine definition."
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == "TestStateMachine"
assert result[0].resource_arn == statemachine_arn
def test_statemachine_with_secrets_in_definition(self):
stepfunctions_client = mock.MagicMock()
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
StateMachine,
StateMachineStatus,
StateMachineType,
)
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
stepfunctions_client.state_machines = {
statemachine_arn: StateMachine(
id="TestStateMachine",
arn=statemachine_arn,
name="TestStateMachine",
status=StateMachineStatus.ACTIVE,
definition='{"Comment": "Example with secret", "StartAt": "MyTask", "States": {"MyTask": {"Type": "Task", "Parameters": {"api_key": "AKIAIOSFODNN7EXAMPLE"}, "End": true}}}',
region=AWS_REGION_US_EAST_1,
type=StateMachineType.STANDARD,
creation_date=datetime.now(),
)
}
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
with (
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
stepfunctions_client,
),
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
stepfunctions_client,
),
):
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
stepfunctions_statemachine_no_secrets_in_definition,
)
check = stepfunctions_statemachine_no_secrets_in_definition()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "TestStateMachine" in result[0].status_extended
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == "TestStateMachine"
assert result[0].resource_arn == statemachine_arn