Detecting Non-Human Identity Abuse: A Practitioner's Guide

How to baseline and detect abuse of service accounts, API keys, and AI agents in cloud environments using open source tooling and pseudo-code.

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

DecisionWhy it matters
Baseline NHIs individually, not against a human-login modelA 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 volumeCredential 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 scopesAgents 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

PriorityDetectionWhy
P1Service account credential used from a source ASN or region never seen beforeStrong signal of key theft or exfiltration — legitimate automation runs from stable infrastructure
P1NHI performs a privilege-granting or credential-creation action for the first timeClassic escalation step after initial compromise of a lower-privileged key
P2API key or token used after being flagged unused for 90+ daysDormant credentials reactivating is either a forgotten integration or a stolen key finally being used
P2AI agent identity calls a tool or API outside its declared scope for that workflowAgent 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
P3Service account activity volume outside its historical p95, same action setLower 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:

  1. 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.
  2. 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

ToolRole
SigmaVendor-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 CustodianScheduled policy checks for stale keys, unused service accounts, and missing rotation — good for the “sprawl” half of this problem
FalcoRuntime signal for what a workload identity actually does at the syscall/API level, useful for correlating with baseline deviations
OpenSearch / WazuhOpen 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.