Credential hygiene: the basics
What does "stale or exposed credential" actually mean across AWS?
A credential is anything that proves an identity: an IAM user's console password or access key, a long-lived key baked into a build pipeline, the password rules that govern human logins. These are static secrets. They do not expire on their own, they do not rotate on their own, and they keep working until somebody explicitly deactivates, deletes or rotates them. Credential hygiene is the capability of keeping that pool of static secrets small, fresh and out of clear text.
AWS Security Hub turns each failure mode into its own control. IAM.8 flags credentials unused for 90 days or more; IAM.22 is the stricter, CIS-aligned 45-day version of the same. IAM.7 and IAM.10 check that the account-wide IAM password policy meets a strong baseline (length, complexity, reuse history). Cognito.3 checks that customer-facing user pools enforce a strong password policy. CodeBuild.1 and CodeBuild.2 catch credentials embedded in build projects, a Bitbucket token in a source URL or a plain-text AWS_ACCESS_KEY_ID in the environment.
They look like separate problems on the report, but they are one capability: a credential that is stale, weak or sitting in clear text is free attack surface with no business benefit. The job is to find every dormant credential, every weak policy, and every embedded secret, then rotate, remove or relocate them, and finally make short-lived credentials the default so the problem stops re-accumulating.
In this lesson you will learn how AWS expresses credential hygiene across dormant IAM credentials, password policy, and secrets embedded in build pipelines, how to find every stale or exposed credential in an account, and how to rotate, remove or relocate them without breaking a workload you had forgotten about. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.
The contractor who left in 2019
In a well-known incident, an attacker found an AWS access key in a public commit from years earlier, pushed by a contractor who had since left the company. The key was still active and held broad S3 read permissions. The attacker exfiltrated over a terabyte of customer data before anomaly detection caught the unfamiliar address, and the remediation, investigation, disclosure, fines and credit monitoring, ran into seven figures. The cost of running delete-access-key at the time would have been zero. The same pattern repeats with embedded keys: researchers who plant canary keys in public repositories measure the time from commit to first malicious API call in single-digit minutes.
Finding stale and exposed credentials across an estate
Priya inherited the IAM hygiene backlog at a fintech. Security Hub shows a mix of credential failures: dozens of IAM.8 and IAM.22 findings for dormant keys, a failing password policy, and a Critical CodeBuild.2 finding flagging a plain-text key in a build project.
Rather than work the findings one by one, she drives the cleanup off the IAM credential report, which lists every user's last-use timestamps in one place, so she can separate genuinely dormant credentials from active ones before deactivating anything.
Generate the credential report and pull the last-use timestamps that both IAM.8 (90 days) and the stricter IAM.22 (45 days) evaluate.
Dormant credentials on departed users and old pipelines are the bulk of the work. Disable first, watch for breakage, then delete.
How AWS tracks credential statedeep dive
The dormancy controls run off the IAM credential report. Every signed API call updates the AccessKeyLastUsed field, and console logins stamp PasswordLastUsed. IAM.8 fails any active credential idle for 90 days or more; IAM.22 is the same evaluation pinned to 45 days via the Config rule iam-user-unused-credentials-check and its maxCredentialUsageAge parameter. The report regenerates at most every four hours, so a fix does not clear the finding instantly. A never-used credential reads as N/A but its age is measured from creation, so a key created 60 days ago and never touched still fails.
The password-policy controls read a single global object per account. IAM.7 and IAM.10 fail when the account policy falls short of the CIS baseline (minimum length 14, all four character classes, reuse prevention of the last 24 passwords). The policy is enforced at password-set time, so tightening it does not force existing users to rotate, it just blocks weak new passwords. Cognito.3 applies the same idea to a customer user pool's password policy. Note that the IAM policy only governs IAM users with console passwords; if your humans sign in via IAM Identity Center, your real password policy lives in your identity provider.
The CodeBuild controls inspect the project definition. CodeBuild.1 flags credentials embedded in a source repository URL; CodeBuild.2 flags AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY stored as PLAINTEXT environment variables. The key insight is that a build running inside AWS never needs a static key: CodeBuild hands the container the project's service-role credentials automatically. The right fix is usually to delete the variables entirely, and for genuine third-party secrets to reference them by the SECRETS_MANAGER or PARAMETER_STORE variable type so the value resolves at build time and never lands in the project, in IaC state, or in batch-get-projects output.
What is the impact of leaving credentials stale or exposed?
The direct impact is leakage turning into access. A long-lived key in a Git history, a clear-text variable in a build project, or a weak password on an admin user becomes account access at whatever permission level the credential holds. For a broad or administrative credential this is total compromise: the attacker can create new identities, disable logging, exfiltrate every bucket, and spin up mining fleets. Because dormant and embedded credentials are rarely watched, the breach often runs for days before anyone notices.
The second-order impact is privilege creep on forgotten identities. Dormant users typically still hold the permissions they had when they were active, often broad, often the residue of "just give them admin while we figure out what they need". An attacker who finds those credentials does not get yesterday's narrow scope; they get the whole accumulated history. The CodeBuild keys are the same risk in clear text: a static key in a project is a standing master credential anyone with read access can copy.
On the compliance side, SOC 2, ISO 27001, PCI DSS and NIST 800-53 all expect documented credential lifecycle controls, a strong password policy and protection of stored credentials. A wall of dormant-credential findings, a weak password policy, or a Critical plain-text-key finding is exactly the evidence an auditor pulls in the first ten minutes, and it is the kind of thing that holds up a SOC 2 or PCI attestation and the enterprise deals that depend on it.
How do you clean up credentials safely?
Work the capability as one loop rather than chasing individual findings. The order matters: inventory before you delete, disable before you delete, rotate exposed keys rather than just hiding them, and fix the source so the findings do not return.
1. Inventory every stale and exposed credential
Generate the IAM credential report and flag any active key or password idle past your threshold (90 days for IAM.8, the stricter 45 days for IAM.22, including never-used credentials older than the threshold). Audit the account-wide password policy against the CIS baseline for IAM.7/IAM.10, and check Cognito pools for Cognito.3. Scan every CodeBuild project across every region for credentials in source URLs (CodeBuild.1) and PLAINTEXT AWS keys (CodeBuild.2). Treat this inventory as the source of truth, not the raw finding count.
2. Disable before you delete; prefer deletion over indefinite disabling
For dormant access keys, run update-access-key --status Inactive and watch CloudTrail for 7 to 30 days; an inactive key rejects all calls, so any forgotten workload screams loudly within the window. Delete the keys that stayed silent. For dormant console passwords, remove the login profile. Reversible-first is the entire point, because the one key you cannot trace is the one a nightly job depends on.
3. Tighten the password policy and rotate exposed keys
Apply the CIS-aligned IAM password policy in a single idempotent call (length 14, all four character classes, reuse prevention 24) and set a matching strong policy on Cognito pools. For any key that was ever stored in clear text in a build project, treat it as compromised: rotate and delete it rather than just relocating it, because moving a still-valid key into a secret store closes the finding but leaves the leaked value usable forever.
4. Make short-lived credentials the default and enforce continuously
The durable answer is not better cleanup, it is not having long-lived credentials at all. Replace IAM users for humans with IAM Identity Center sessions, replace service-account keys with IAM roles the workload assumes, and in CodeBuild delete static keys in favour of the service role (referencing genuine third-party secrets via the SECRETS_MANAGER or PARAMETER_STORE type). Back it with AWS Config rules, an SCP that blocks creating long-lived keys, and credential revocation built into offboarding so the count stays down.
# Find active credentials idle past 45 days and disable them (review before deleting).
CUTOFF=$(date -u -d '45 days ago' +%Y-%m-%d)
aws iam generate-credential-report >/dev/null
aws iam get-credential-report --query Content --output text | base64 -d \
| awk -F, -v c="$CUTOFF" 'NR>1 && $9=="true" && $11<c {print $1, $10}'
aws iam update-access-key --user-name old-contractor \
--access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive
# Apply the CIS-aligned IAM password policy in one idempotent call.
aws iam update-account-password-policy --minimum-password-length 14 \
--require-uppercase-characters --require-lowercase-characters \
--require-numbers --require-symbols --password-reuse-prevention 24
# A clear-text key in a build project is compromised: rotate and delete, never just relocate.
aws iam delete-access-key --user-name ci-deploy --access-key-id AKIAIOSFODNN7EXAMPLE Quick quiz
Question 1 of 5Security Hub shows dormant-credential findings, a weak password policy, and a plain-text key in a CodeBuild project. What is the most efficient way to think about them?
You scored
0 / 5
Keep learning
Go deeper on the credential lifecycle across the services in this capability.
- IAM credential reports Generating, downloading and interpreting the per-user last-use CSV that IAM.8 and IAM.22 evaluate.
- AWS Config managed rule: iam-user-unused-credentials-check Continuous detection with the maxCredentialUsageAge threshold you set to 45 for IAM.22 or 90 for IAM.8.
- Security Hub CSPM controls for CodeBuild The exact checks, severities and remediation for the embedded-credential controls CodeBuild.1 and CodeBuild.2.
You can now treat credential hygiene as one capability rather than a scatter of findings: inventory every dormant credential, weak policy and embedded secret, disable before you delete, rotate any key that was ever exposed rather than just relocating it, set a strong password baseline, and make short-lived credentials the default so the surface stops re-accumulating. The Controls this lesson covers section below links every control in this group to its deep page and fix.
Back to the library