Back to Checks
aws

SSM document contains no secrets

ssm_document_secrets
Service: ssmSeverity: high
OfficialBuilt-inVerified
Categories:secrets
Resource Type:AwsSsmPatchCompliance

AWS Systems Manager documents are inspected for embedded secrets within their content. Patterns resembling passwords, access keys, tokens, or private keys in document steps are flagged when values appear hardcoded rather than referenced securely.

Risk

Hardcoded secrets in SSM documents weaken CIA:

  • Confidentiality: readers of the document can exfiltrate credentials.
  • Integrity: stolen keys enable privilege escalation and automation tampering.
  • Availability: abused credentials can disrupt systems and impede recovery.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks ssm_document_secrets

Recommendation

Avoid embedding secrets. Store them in Secrets Manager or Parameter Store as SecureString (KMS-encrypted) and reference at runtime.

Apply least privilege to documents and secrets, prefer short-lived role credentials, rotate credentials, continuously scan/audit documents, and enforce separation of duties for authoring and approval.

Remediation

CLI

aws ssm update-document --name <example_resource_name> --content file://<SANITIZED_DOCUMENT_FILE>.json

Terraform

resource "aws_ssm_document" "<example_resource_name>" {
  name          = "<example_resource_name>"
  document_type = "Command"

  content = jsonencode({
    schemaVersion = "2.2"
    mainSteps = [{
      action = "aws:runShellScript"
      name   = "run"
      inputs = {
        runCommand = [
          // Critical: use ssm-secure dynamic reference to avoid hardcoded secrets
          "export PASSWORD='{{ssm-secure:/path/to/secret}}'"
        ]
      }
    }]
  })
}

NativeIaC

Resources:
  <example_resource_name>:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Command
      Content:
        schemaVersion: '2.2'
        mainSteps:
          - action: aws:runShellScript
            inputs:
              runCommand:
                # Critical: reference a SecureString parameter instead of hardcoding a secret
                # This avoids embedding secrets in the document content
                - "export PASSWORD='{{ssm-secure:/path/to/secret}}'"

Other

1In the AWS Console, go to Systems Manager > Parameter Store > Create parameter
2Set Name to /path/to/secret, Type to SecureString, enter the secret value, and click Create parameter
3Go to Systems Manager > Documents, select the document, then Actions > Edit content
4Remove any hardcoded secrets and reference the SecureString parameter, e.g.: {{ssm-secure:/path/to/secret}}
5Save to create a new version and set it as Default
6Re-run the check to confirm it passes

Check Code

import json

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.ssm.ssm_client import ssm_client


class ssm_document_secrets(Check):
    def execute(self):
        findings = []
        secrets_ignore_patterns = ssm_client.audit_config.get(
            "secrets_ignore_patterns", []
        )
        validate = ssm_client.audit_config.get("secrets_validate", False)
        documents = list(ssm_client.documents.values())

        # Collect one payload per document (its content) and scan them all in
        # batched Kingfisher invocations instead of one subprocess per document.
        def payloads():
            for index, document in enumerate(documents):
                if document.content:
                    yield index, json.dumps(document.content, indent=2)

        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, document in enumerate(documents):
            report = Check_Report_AWS(metadata=self.metadata(), resource=document)
            report.status = "PASS"
            report.status_extended = (
                f"No secrets found in SSM Document {document.name}."
            )

            if document.content:
                if scan_error:
                    report.status = "MANUAL"
                    report.status_extended = (
                        f"Could not scan SSM Document {document.name} for secrets: "
                        f"{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 secret found in SSM Document {document.name} -> {secrets_string}."
                    annotate_verified_secrets(report, detect_secrets_output)

            findings.append(report)

        return findings