This check flags IAM users with console access who haven't logged in for over 90 days. Dormant accounts are prime targets for attackers and clutter your access surface. Disable or delete the user, or revoke their console credentials if the account is no longer needed.
Every IAM user with a console password is a potential entry point into your AWS account. When that user stops logging in but the credentials stay active, you're left with an open door that nobody is watching. The IAM User Inactive for 90+ Days check looks for exactly this: human users who can still sign in to the AWS Management Console but haven't done so in three months or more.
These accounts tend to pile up over time. A contractor finishes a project, an engineer changes teams, someone sets up a temporary login for a one-off task and never cleans it up. The login stays valid, the password may be weak or reused, and MFA might never have been enabled. That combination is a gift to anyone trying to get a foothold.
What this check detects
Lensix inspects the IAM credential report for every user in your account and checks two things together:
- The user has an active console password (meaning they can sign in through the AWS web console).
- The
password_last_usedtimestamp is more than 90 days in the past, or the password was never used at all.
If both are true, the check fails for that user. Programmatic-only users (those with access keys but no console password) are not flagged by this particular check, since they have no login activity to measure.
Note: AWS records console sign-in activity in the IAM credential report under the password_last_used field. This field can show no_information for very old activity or N/A if the password has never been used. Lensix treats both as inactive when a password is set.
Why it matters
An unused console login is risk with no upside. The account provides nothing of value while it sits idle, yet it carries every bit of the danger that an active account would.
The attack surface stays open
Attackers buy and trade leaked credentials constantly. If a former employee reused their AWS console password on a service that later got breached, that password is now in a credential-stuffing list. Nobody is logging into the account, so nobody will notice a failed or successful sign-in attempt. The longer an account sits dormant, the less likely anyone is monitoring it.
MFA gaps go unnoticed
Dormant accounts are often the ones that never got MFA. Active users tend to enroll in MFA because they hit prompts and policies day to day. An account that hasn't logged in for 90 days may have been created before your MFA enforcement was in place, leaving it protected by a password alone.
It violates least privilege over time
People accumulate permissions. An engineer who moved to a different team three months ago may still hold admin-level access to systems they no longer touch. If that account is compromised, the blast radius is whatever permissions were never revoked.
Warning: A 90-day inactive user with administrative permissions and no MFA is one of the highest-value targets in any AWS account. Treat these findings as a priority, not a backlog item.
Compliance frameworks expect cleanup
CIS AWS Foundations Benchmark, SOC 2, PCI DSS, and most internal security policies require regular review and removal of inactive credentials. A 90-day-old console login that's still active will show up in an audit and cost you findings.
How to fix it
First, confirm the account really is inactive and figure out whether it should exist at all. Pull the credential report to see when the user last signed in.
aws iam generate-credential-report
aws iam get-credential-report \
--query 'Content' \
--output text | base64 --decode > credential-report.csv
# Look at console password usage for all users
column -s, -t credential-report.csv | \
awk -F, '{print $1, $5, $6}'
The relevant columns are user, password_enabled, and password_last_used. Any user with password_enabled=true and an old or N/A last-used date is a match.
Option 1: Remove the console password (keep the user)
If the user still needs programmatic access through their access keys but has no reason to use the console, just delete the login profile. This closes the console door without touching anything else.
aws iam delete-login-profile --user-name jdoe
Option 2: Delete the user entirely
If the account belongs to someone who has left or a project that's done, remove it. IAM users have dependencies, so you need to detach them before deletion.
Danger: Deleting an IAM user is irreversible and will break anything still authenticating as that user, including automated jobs using its access keys. Confirm the account is truly unused before running these commands in production.
USER=jdoe
# Remove console access
aws iam delete-login-profile --user-name "$USER" 2>/dev/null
# Delete access keys
for key in $(aws iam list-access-keys --user-name "$USER" \
--query 'AccessKeyMetadata[].AccessKeyId' --output text); do
aws iam delete-access-key --user-name "$USER" --access-key-id "$key"
done
# Detach managed policies
for arn in $(aws iam list-attached-user-policies --user-name "$USER" \
--query 'AttachedPolicies[].PolicyArn' --output text); do
aws iam detach-user-policy --user-name "$USER" --policy-arn "$arn"
done
# Delete inline policies
for name in $(aws iam list-user-policies --user-name "$USER" \
--query 'PolicyNames[]' --output text); do
aws iam delete-user-policy --user-name "$USER" --policy-name "$name"
done
# Remove from groups, deactivate MFA, delete signing certs, then delete the user
for grp in $(aws iam list-groups-for-user --user-name "$USER" \
--query 'Groups[].GroupName' --output text); do
aws iam remove-user-from-group --user-name "$USER" --group-name "$grp"
done
aws iam delete-user --user-name "$USER"
Option 3: Disable instead of delete
If you're not ready to delete but want to lock the account immediately, attach a deny-all policy or, more simply, delete the login profile and deactivate the access keys. Deactivating keeps the keys for audit purposes while blocking their use.
aws iam update-access-key \
--user-name jdoe \
--access-key-id AKIAEXAMPLE \
--status Inactive
Tip: Before deleting anyone, check CloudTrail for recent activity from the user across all event sources, not just console logins. Someone might be inactive in the console but still running automation. Search userIdentity.userName in your CloudTrail logs or Athena table for the last 90 days.
How to prevent it from happening again
Manual cleanup doesn't scale. The goal is to make inactive accounts impossible to ignore and, ideally, to stop creating long-lived human users in the first place.
Move humans to federated access
The most durable fix is to stop using IAM users for people. With AWS IAM Identity Center (formerly AWS SSO) or an external identity provider, console access is granted through temporary sessions tied to your central directory. When someone leaves, you disable them in one place and their AWS access disappears. There are no standing console passwords to go stale.
Note: Federated users authenticate through SAML or OIDC and assume roles for short-lived sessions. Because there's no persistent IAM user with a password, this entire class of finding stops applying to your human access.
Automate the detection with a scheduled audit
Run a Lambda on a schedule to pull the credential report, find stale console logins, and either alert or auto-remediate. Here's a minimal Python check you can adapt:
import boto3, csv, io, time
from datetime import datetime, timezone, timedelta
iam = boto3.client("iam")
def get_report():
iam.generate_credential_report()
while True:
try:
return iam.get_credential_report()["Content"]
except iam.exceptions.CredentialReportNotPresentException:
time.sleep(2)
def find_inactive(days=90):
rows = csv.DictReader(io.StringIO(get_report().decode()))
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
stale = []
for r in rows:
if r["password_enabled"] != "true":
continue
last = r["password_last_used"]
if last in ("N/A", "no_information"):
stale.append(r["user"])
else:
if datetime.fromisoformat(last) < cutoff:
stale.append(r["user"])
return stale
if __name__ == "__main__":
for user in find_inactive():
print(f"Inactive console user: {user}")
Enforce it as policy-as-code
Bake the rule into your guardrails so it's checked continuously rather than once a quarter. AWS Config has a managed rule for exactly this case:
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "iam-user-unused-credentials-check",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "IAM_USER_UNUSED_CREDENTIALS_CHECK"
},
"InputParameters": "{\"maxCredentialUsageAge\":\"90\"}"
}'
This rule marks any user whose console password or access keys have been unused beyond the threshold as non-compliant, and you can wire it to an SNS topic or an automatic remediation action through AWS Systems Manager.
Tip: Pair the Config rule with a remediation that calls delete-login-profile after a grace period. Auto-disabling console access for users idle past 120 days, while alerting a human first, removes the manual toil without risking a surprise lockout.
Best practices
- Set a clear inactivity threshold and enforce it. Ninety days is a reasonable default. High-security environments often drop this to 30 or 45 days.
- Require MFA on every console-enabled user. If you must keep IAM users with passwords, enforce MFA with an IAM policy that denies actions when
aws:MultiFactorAuthPresentis false. - Separate human and machine identities. Use roles and federation for people, and reserve IAM users for the rare cases where a service genuinely needs long-lived keys.
- Review access on offboarding. Inactive accounts almost always trace back to a missed offboarding step. Tie IAM cleanup into your HR or ticketing workflow so departures trigger credential removal.
- Run the credential report regularly. Generate and review it at least monthly. It surfaces inactive passwords, unused access keys, and missing MFA in one place.
- Tag ownership on every user. A
ownerorteamtag makes it obvious who to contact before disabling an account, which speeds up safe cleanup.
Inactive console users are one of the easiest wins in cloud security. They cost you nothing to remove and quietly raise your risk for as long as they exist. Clean up what you have, automate the detection, and the problem stops coming back.

