Skip to content

Non-human identity security

Almost every organisation can list its employees, and almost none can list its service accounts. Hanu builds that list from the access you already grant it, then answers the three questions that decide whether a machine identity is a liability: is it used, who owns it, and what can it reach.

non-human identity securityservice account inventoryunused access key detection

The problem

A non-human identity is anything that authenticates without a person behind it: an IAM access key, a role, a Secrets Manager secret, an SSM parameter, an instance profile, a credential sitting in a Lambda environment variable, an OIDC provider. The count in a growing cloud account is in the hundreds, and nobody enumerates it, because nothing in the console is titled "identities we do not know about".

The risk is not that these exist. It is that the unused and the over-privileged ones do. A key nobody has called in nine months still works, still carries whatever policy somebody attached during a migration, and has no owner to ask. When it is used again, the usage itself is the only signal available — a machine identity has no MFA event, no impossible-travel login, and none of the behavioural baseline a human account accumulates over years.

Attackers have made this the easier door. The poisoned LiteLLM 1.107.0 release harvested cloud credentials from machines that installed it and validated them with sts:GetCallerIdentity within about four and a half hours, then probed model capacity. ConnectWise's own cloud application credentials were used to authenticate into downstream customer tenants because those applications were already trusted inside the tenants, so interactive sign-in and MFA never applied — and app credentials can remain a working backdoor after the compromised application is remediated. Device-code phishing reaches the same place without a password at all, and a password reset does not evict the token.

Non-human identity risk factorsNhiRiskEngine · weighted factors summed, capped at 100
FactorFires whenWeightSeverity
NHI_LAMBDA_ENV_SECRETa credential is in a Lambda environment variable35HIGH
OVERPRIVILEGED_ADMINAdministratorAccess or an admin policy is attached30CRITICAL
LONG_LIVED_KEYan access key is older than 365 days30CRITICAL
CROSS_ACCOUNT_WILDCARDthe role trust principal is *25CRITICAL
STALE_CREDENTIALunused more than 90 days, no rotation25HIGH
NO_ROTATIONno rotation recorded after 60 days20MEDIUM
DORMANT_CREDENTIALunused for more than 30 days15MEDIUM
NO_OWNERno Owner, Team or Service tag10LOW

How Hanu approaches it

Discovery is API-first and repeated, not imported once. Seven identity types are enumerated on every scan across IAM, Secrets Manager, SSM and Lambda, and each one carries creation date, last-used date and service, rotation state, owner tag and the permissions attached. A new identity appearing is an INFO finding — NHI_LIFECYCLE_NEW — so the inventory stays complete without turning completeness into a queue.

Risk is a weighted sum of named factors with the band boundaries written down: 70 or above is CRITICAL, 50 HIGH, 30 MEDIUM. The finding names the factors that produced it, so OVERPRIVILEGED_ADMIN reads as "an admin policy is attached to a machine", not as a decimal. Machine-identity scoring is separate from human IAM scoring on purpose; a dormant key and a dormant employee are not the same problem and should not share a queue.

Lifecycle state is derived from a snapshot and then diffed against the previous scan. DORMANT at 30 days of no use, STALE at 90 days with no rotation, EXPIRED for an access key older than a year, plus ACTIVE, ROTATED and REVOKED. The transitions are where the meaning is: STALE → ACTIVE is HIGH with the reason stated plainly, because a credential that has been quiet for ninety days and is suddenly in use is an indicator of compromise. ACTIVE → DORMANT is not.

Because there is no baseline to compare against, the second anchor is first use and shape. CREDENTIAL_VALIDATION, MODEL_ENUMERATION and MODEL_INVOCATION are ordered stages over CloudTrail, and a validation only counts when it is the first ever recorded for that key inside 24 hours. Two distinct stages, each within 12 hours of the last, produce LLM_JACKING_ACTIVITY: HIGH during reconnaissance, CRITICAL once inference is reached, with the stages, event names, source addresses and elapsed time attached to the signal.

The third anchor is the key's own history. NhiItdrEngine establishes a 30-day source-IP baseline per access key and emits NHI_ITDR_NEW_SOURCE_IP for any address that has never appeared in it. A key silent across the 60-day window ending two days ago and active now is NHI_ITDR_DORMANT_KEY_REACTIVATED at CRITICAL, because dormant-then-active is a compromise shape and not a change of schedule. Service-account keys that reach for CreateAccessKey, AttachUserPolicy, CreateLoginProfile or AssumeRole are flagged by name pattern and by API, and one key working across regions is flagged separately.

Federated trust gets its own rules, because it is persistence that survives rotation. RULE-BEH-AWS-NEW-018 fires on an OIDC provider thumbprint being updated, RULE-BEH-AWS-NEW-105 on a provider being created, RULE-BEH-AWS-NEW-095 on AssumeRoleWithWebIdentity. On Azure, RULE-BEH-AZURE-NEW-014 fires on a federated credential added to an application and RULE-BEH-AZURE-NEW-015 when its subject is a wildcard that lets any repository in an external organisation impersonate the app. On GCP the equivalents are a Workload Identity Pool provider created with an external OIDC issuer, a GenerateIdToken call that impersonates a service account, and an iam.workloadIdentityUser grant that lets a federated workload do it without a key.

The joins are where the inventory earns its place. A leaked secret plus a PRIVILEGE_ESCALATION_PATH through the same identity is RULE-T2-001. A leaked secret plus an OIDC trust change on the same role is RULE-T2-NEW-HI-002. A credential is only interesting in relation to what it opens, which is why identities are graph nodes and not just rows — see attack path analysis for how reach is walked.

What you get

NHI-01Identity inventory
Every discovered non-human identity as a row: type, ARN, creation date, last-used date and service, rotation state, owner, and the permissions attached. Owner attribution reads the Owner, Team and Service tag keys, so an unowned credential is visible as unowned instead of quietly filed under the account that created it.
NHI-02Composite risk score per identity
A 0–100 score that sums weighted factors and names every one of them in the finding. The same inputs produce the same score on every scan, and the factor list is the explanation you hand to the team that owns the identity.
NHI-03Lifecycle transitions and per-key usage history
State changes between scans, plus the source-IP baseline and first-use record the ITDR rules reason against. This is the artefact that lets you answer, months later, whether the key being used right now has ever been used from that address before.

Limits

Hanu is not a PAM and not a secrets vault. It never reads a credential value, never stores one and never proxies one: the NHI engines work from creation, last-use, trust-policy and permission metadata plus CloudTrail events, and the secret scanner records a sha256 prefix of a match rather than the secret itself. Rotation stays your mechanism — where a Secrets Manager rotation Lambda already exists, a remediation can trigger it, but nothing here holds or manages a credential lifecycle.

There is no provisioning integration with an identity provider: no SCIM, no just-in-time grant, nothing that creates or deprovisions an identity. Coverage follows the collectors, too. AWS IAM, Secrets Manager, SSM, Lambda and OIDC providers are enumerated; Kubernetes service accounts appear through audit-event rules on bound-token minting rather than through the NHI inventory; and a cloud platform with no collector contributes no identities and no findings at all.

Questions

How do you find the identities nobody told us about?
By enumerating resource types rather than accepting a list. IAM user keys with GetAccessKeyLastUsed, IAM roles and their trust policies, OIDC providers, Secrets Manager secrets, SSM parameters, instance profiles and Lambda environment credentials are read from the provider's own APIs on each scan. Anything that appears is NHI_LIFECYCLE_NEW at INFO severity with its owner tag status attached, so the first thing you learn about a new credential is whether anyone admits to owning it.
What makes a dormant credential dangerous if nothing is using it?
The reactivation, not the dormancy. Two rules cover it: an access key with no CloudTrail activity between 2 and 62 days before now that is used in the last 24 hours is NHI_ITDR_DORMANT_KEY_REACTIVATED at CRITICAL, and a lifecycle transition from STALE to ACTIVE is HIGH. Both say indicator of compromise rather than drift, because a credential nobody has needed for two months has no legitimate reason to suddenly be worth invoking.
Do you rotate or store our credentials?
No. Hanu observes the credential's metadata and its usage; it does not hold the secret. The scanner records the commit, the file path, the line and a truncated sha256 of the matched value, which is enough to find and revoke the credential and not enough to be a second place it leaks from. Rotation is executed by your own tooling — where Secrets Manager already has a rotation Lambda configured, the remediation path can trigger it.
How does this connect to the rest of the platform?
Identities are nodes in the same graph as workloads and assets, so an identity's blast radius is a query rather than an opinion. The per-identity view returns the graph paths that reach it and stored attack paths through it, and the correlation rules join credentials to the trust changes, leaked secrets and privilege paths that make them worth acting on.

Run it in your own account

One Docker Compose stack, your datastores, read-only credentials into your clouds. The Community edition is free to run in production.