Every service account you provisioned in 2022 is still there. So is its key. Nobody remembers what it does, and nobody will notice when it starts doing something else.
Why this matters now
Non-human identities (NHIs) — service accounts, API keys, OAuth tokens, workload identities, and now autonomous AI agents — outnumber human users in most cloud environments by a wide margin. Unlike people, they don’t get offboarded when a project ends, they rarely have MFA, and their credentials are often long-lived by default. A leaked API key or an over-permissioned service account is one of the quietest ways into a cloud estate, because nothing about the login looks unusual — it’s not a login at all.
2025 and 2026 added a new wrinkle: agentic AI. Tools that call APIs on a human’s behalf, with their own credentials and their own blast radius, are now a normal part of engineering and SOC workflows. An AI agent with a broad API key is, from a detection standpoint, just another non-human identity — one that’s easier to provision and easier to forget about than the service accounts that came before it.
Most teams monitor human logins closely (impossible travel, new device, MFA prompts) and monitor NHIs barely at all. This guide covers how to close that gap: how to build a behavioral baseline for non-human identities and turn deviations from it into detections, using open source tooling and technique patterns you can adapt to whatever log pipeline you already run.
The short version
| Decision | Why it matters |
|---|---|
| Baseline NHIs individually, not against a human-login model | A service account’s “normal” is a fixed set of API calls at a fixed cadence — very different from a person’s login pattern |
| Alert on new actions and new source infrastructure, not just volume | Credential theft usually shows up as a new capability being used, not more of the old one |
| Treat AI agent credentials as a distinct NHI class with tighter scopes | Agents call tools dynamically and are the easiest class of NHI to over-provision by default |
Why NHIs are hard to monitor
A few properties make non-human identities structurally different from human ones, and most detection content (written for interactive logins) doesn’t account for them:
- No interactive session. There’s no MFA challenge, no device fingerprint, no browser to inspect. You have an API key or a token and whatever request it authenticates.
- Static, long-lived credentials. Many API keys and service account keys are created once and never rotated. A key stolen from a leaked config file or a compromised CI pipeline stays valid indefinitely unless something forces rotation.
- Legitimate high-volume, high-frequency activity. A cron job that calls an API every minute looks “anomalous” by human standards but is completely normal for that identity. Generic rate-based rules built for people generate constant noise on NHIs.
- Sprawl and orphaning. Every integration, CI pipeline, and internal tool tends to mint its own service account. Few organizations track which ones are still in use, so “this account did something new” is often the only signal you have — there’s no owner to ask.
The fix isn’t to reuse your human-identity detection logic with the thresholds turned up. It’s to baseline each NHI against itself.
Step 1: Build a per-identity behavioral baseline
The core idea: for every non-human identity, learn its normal action set, calling pattern, and source infrastructure over a rolling window, then alert on the parts of new activity that fall outside it — not on the activity itself.
for each identity_id in non_human_identities:
baseline = {
actions_seen: set of (service, action) pairs called in the last N days
source_ip_ranges: set of CIDR ranges / ASNs the identity has authenticated from
source_identities: set of hosts, container images, or roles that have used this credential
typical_hours: distribution of activity by hour-of-day / day-of-week
typical_volume: p50 / p95 request count per hour
}
persist baseline, keyed by identity_id, refreshed daily on a trailing window
A rolling 14–30 day window is usually enough for automation-heavy identities (CI jobs, cron scripts) since their behavior repeats often. Identities with sparse or irregular activity need a longer window before the baseline is trustworthy — flag those as “not yet baselined” rather than alerting on them prematurely.
Step 2: Turn baseline deviations into detections
Once you have a baseline, the highest-signal detections come from new dimensions of behavior, not raw volume:
# pseudo-code, run per event at ingest or in a scheduled batch job
event = {identity_id, service, action, source_ip, source_asn, timestamp}
baseline = load_baseline(event.identity_id)
if baseline is missing:
emit_low_priority("unbaselined identity active")
if (event.service, event.action) not in baseline.actions_seen:
emit_alert(
severity = "high" if action_is_sensitive(event.action) else "medium",
reason = "new action for this identity",
mitre = "T1078.004" # Valid Accounts: Cloud Accounts
)
if event.source_asn not in baseline.source_ip_ranges:
emit_alert(
severity = "high",
reason = "identity used from unrecognized network",
mitre = "T1550" # Use Alternate Authentication Material
)
if event.action in SENSITIVE_ACTIONS and event.identity_id in RECENTLY_ROTATED_KEYS:
emit_alert(
severity = "critical",
reason = "sensitive action immediately after credential rotation"
)
action_is_sensitive() is a small allowlist you maintain: credential creation, permission grants, data export, encryption key deletion, and anything that changes another identity’s access. Those actions deserve a lower bar for alerting even when the identity looks otherwise normal, because they’re the actions an attacker actually wants.
Concrete detections worth building first
| Priority | Detection | Why |
|---|---|---|
P1 | Service account credential used from a source ASN or region never seen before | Strong signal of key theft or exfiltration — legitimate automation runs from stable infrastructure |
P1 | NHI performs a privilege-granting or credential-creation action for the first time | Classic escalation step after initial compromise of a lower-privileged key |
P2 | API key or token used after being flagged unused for 90+ days | Dormant credentials reactivating is either a forgotten integration or a stolen key finally being used |
P2 | AI agent identity calls a tool or API outside its declared scope for that workflow | Agent frameworks often default to broad scopes; a call outside the expected tool set is a strong deviation signal even before you know if it’s malicious |
P3 | Service account activity volume outside its historical p95, same action set | Lower confidence alone, useful as a correlating signal with other findings |
Feed these as Sigma-style rules if your detection layer supports them — Sigma’s format keeps the logic portable across whatever log storage and query engine you run, which matters if that engine changes later.
title: Service Identity - New Source ASN
id: 8f2c1a90-nhi-example
status: experimental
logsource:
category: cloud_audit
detection:
selection:
identity_type: "service_account"
filter:
source_asn|in_baseline: true
condition: selection and not filter
level: high
tags:
- attack.t1550
- attack.credential_access
AI agents are a non-human identity too
Treat every AI agent credential — an API key handed to an LLM-driven tool, a scoped token an agent framework mints per session — the same way you’d treat a service account, with two adjustments:
- Scope it tighter than you think you need to. Agents call tools dynamically based on model output, not a fixed code path. A broad key that’s “fine” for a deterministic script is a much larger blast radius when a model decides what to call with it. Least privilege matters more here, not less.
- Baseline the tool call pattern, not just the API calls. If your agent framework logs which tools it invoked and with what parameters, that’s a richer baseline than the underlying API calls alone. A coding agent suddenly calling a tool that reads cloud credentials, when its normal pattern is read-only file access, is a stronger signal than any single API call in isolation.
The detection logic from Step 2 applies without modification — an AI agent’s credential showing up with a new action or new source is exactly as suspicious as a cron job’s would be. The only change is where you draw the baseline boundary: per agent workflow, not just per credential, since one API key is often reused across many different agent tasks.
Open source building blocks
| Tool | Role |
|---|---|
| Sigma | Vendor-neutral detection rule format — write the logic once, keep it portable |
| OPA (Open Policy Agent) | Enforce least-privilege policy on what an identity or agent is allowed to do, independent of detection |
| Cloud Custodian | Scheduled policy checks for stale keys, unused service accounts, and missing rotation — good for the “sprawl” half of this problem |
| Falco | Runtime signal for what a workload identity actually does at the syscall/API level, useful for correlating with baseline deviations |
| OpenSearch / Wazuh | Open source options for storing and querying the audit trail baselining depends on — any log store with reasonable retention works |
None of this requires a specific vendor stack. The pattern — baseline per identity, alert on new dimensions rather than raw volume, tighten AI agent scopes proactively — works with whatever log pipeline and query engine you already have in place.
Final thought
Non-human identities don’t announce themselves the way a suspicious human login does. There’s no failed MFA prompt, no new-device email, no obvious tell. The only real signal is behavioral: what an identity has always done versus what it’s doing now. Building that baseline is more work upfront than turning on a rate-based alert, but it’s the difference between catching credential theft in the first hour and finding it in a breach report six months later.
If you want help building this into your detection coverage, contact us.