Skip to main content
emnode
Compliance Medium severity

AWS Security Hub · SecretsManager

SecretsManager.4: Secrets lack a rotation schedule

Written and reviewed by Emnode · Last reviewed

What does AWS Security Hub SecretsManager.4 check?

SecretsManager.4 checks how long it has actually been since each secret was last rotated, compared against the maxDaysSinceRotation parameter (default 90 days, configurable from 1 to 180). It fails if a secret has not been rotated within that window. The control keys off real days-since-rotation, not on whether automatic rotation is configured: a secret rotated inside the window passes regardless of how it was rotated, including a manual update.

Why does SecretsManager.4 matter?

Rotation exists to bound how long a leaked credential stays useful. A secret that technically has rotation enabled but on a 365-day schedule still leaves a compromised password valid for a year. Every workload that ever held the value keeps a copy, so a stale credential's blast radius grows with its age, and PCI DSS 8.3.9, SOC 2, and ISO 27001 all expect a documented, enforced rotation lifecycle.

How do I fix SecretsManager.4?

  1. Enable automatic rotation and set the interval at or below the maxDaysSinceRotation threshold (default 90 days) so each secret rotates before the window elapses.
  2. Tighten the interval further for your highest-value secrets: production databases, signing keys.
  3. Confirm rotations are completing so the most recent rotation actually stays within the window; a secret manually rotated within the period also passes.

Remediation script · bash

# Enable rotation on an RDS-backed secret with the AWS-managed Lambda, 30-day cadence.
aws secretsmanager rotate-secret \
  --secret-id prod/payments/db-master \
  --rotation-lambda-arn arn:aws:lambda:eu-west-1:123456789012:function:SecretsManagerRDSPostgreSQLRotationSingleUser \
  --rotation-rules AutomaticallyAfterDays=30

# Schedule deletion of stale secrets behind a recovery window (never force-delete in prod).
NOW=$(date -u +%FT%TZ)
for arn in $(aws secretsmanager list-secrets \
  --query "SecretList[?LastAccessedDate<='$(date -u -d '90 days ago' +%FT%TZ)'].ARN" \
  --output text); do
  aws secretsmanager delete-secret --secret-id "$arn" --recovery-window-in-days 7
done

# Alarm so the next failed rotation pages a human, not the next audit.
aws cloudwatch put-metric-alarm \
  --alarm-name secrets-rotation-failed --namespace AWS/Lambda --metric-name Errors \
  --dimensions Name=FunctionName,Value=RotatePaymentsDbMaster \
  --statistic Sum --period 3600 --evaluation-periods 1 \
  --threshold 1 --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:security-oncall

Full walkthrough (console steps, edge cases and verification) in the lesson Manage secrets (rotation and hygiene).

Is SecretsManager.4 a false positive?

Enabling rotation is not enough on its own. If a secret hasn't actually been rotated within maxDaysSinceRotation days (for example because the configured schedule is longer than the threshold, or a rotation is overdue) SecretsManager.4 still fails even though SecretsManager.1 passes on rotation simply being enabled.

Part of the learning path Lock down access