ForumsGeneralThe 2024 Stats Were Right: Ghost Identities Are Still Haunting Our Clouds

The 2024 Stats Were Right: Ghost Identities Are Still Haunting Our Clouds

SecurityTrainer_Rosa 4/18/2026 USER

Just saw the webinar recap on THN about non-human identities driving 68% of cloud breaches in 2024. Honestly, with the explosion of AI agents and automation since then, I suspect that number is conservative for 2026.

The core issue remains lifecycle management. We find service accounts and API keys generated for POCs that linger years after the project is dead. For every human user, there are 40-50 non-human identities. Managing that scale manually is impossible.

I've been scripting some basic detection to find these "ghosts" in AWS. Here is a snippet using boto3 to flag IAM users (likely service accounts if they lack console passwords) that haven't accessed keys in 90 days:

import boto3
from datetime import datetime, timedelta

client = boto3.client('iam')
threshold = (datetime.now() - timedelta(days=90))

users = client.list_users()['Users']
for user in users:
    user_name = user['UserName']
    # Service accounts usually don't have console login profiles
    try:
        client.get_login_profile(UserName=user_name)
        continue # Skip humans
    except client.exceptions.NoSuchEntityException:
        # Check last accessed date for access keys
        last_accessed = user.get('PasswordLastUsed')
        if not last_accessed or last_accessed < threshold:
            print(f"Stale Service Account: {user_name}, Last Accessed: {last_accessed}")

This catches the low-hanging fruit, but what about OAuth grants and AI agent tokens? Are you folks relying on CSPM tools for this, or have you built custom automation to revoke orphaned OAuth sessions?

RA
RansomWatch_Steve4/18/2026

We correlate AADNonInteractiveUserSignInLogs with our HR offboarding feed in Sentinel. If a service principal's 'Owner' field matches a terminated employee, we trigger a Logic App to disable the credentials immediately. It caused some friction initially with shared accounts, but tagging resources correctly solved most of the false positives.

K8
K8s_SecOps_Mei4/18/2026

That script is a good start, but it misses the keys sitting in stale CI/CD pipelines. We integrated TruffleHog into our Git workflow to scan for secrets in every commit. Combined with forcing 90-day rotation via IAM policy, we've reduced our exposure significantly.

PH
PhishFighter_Amy4/18/2026

Don't forget Terraform state! We audited our S3 buckets and found state files from 3 years ago containing cleartext secrets for RDS instances that were deleted ages ago. We enforce S3 versioning and bucket policies now, but cleaning up that technical debt was a nightmare.

FI
Firewall_Admin_Joe4/19/2026

You're right, manual cleanup is a losing battle. We shifted to enforcing mTLS for all service-to-service traffic. Instead of rotating secrets, we rely on short-lived certs. If a project dies, the cert expires and the edge firewall drops the connection automatically.

Here’s a quick snippet we use to audit certs expiring in the next 7 days on our ingress:

openssl x509 -in /path/to/cert.pem -noout -checkend 604800 && echo "Valid" || echo "Expiring Soon"
OS
OSINT_Detective_Liz4/19/2026

Since we're on the topic of automation, spotting dormant usage is critical. We run this KQL query weekly to flag service principals that haven't authenticated in 90 days. It helps us prune ghost identities that HR data or static secret scanners might miss.

AADNonInteractiveUserSignInLogs
| where ResultType == "0"
| summarize LastSignIn = max(TimeGenerated) by AppId
| join kind=rightouter (
    ServicePrincipals
    | project AppId, DisplayName
) on AppId
| extend DaysInactive = datetime_diff('day', now(), LastSignIn)
| where isnull(LastSignIn) or DaysInactive > 90

Verified Access Required

To maintain the integrity of our intelligence feeds, only verified partners and security professionals can post replies.

Request Access

Thread Stats

Created4/18/2026
Last Active4/19/2026
Replies5
Views151