9 Battle-Tested Non-Human Identity Security Controls

With AI services rapidly integrated into production, non-human identities (API keys, service accounts, CI tokens, and even AI agents) have become a prime target for misuse. The failure mode is consistent: keys sprawl across repos and pipelines, privileges drift, and monitoring stays human-centric. The result is quiet compromise, unexpected costs, or data access that nobody can explain.

This post gives engineering leaders a practical, code-heavy blueprint for non-human identity security in AI-first architectures – from inventory to CI/CD guardrails to runtime detection and fast revocation.

9 Battle-Tested Non-Human Identity Security Controls

Contents Overview

Why AI-first architectures raise machine-identity risk

AI-first systems tend to add more “machine callers” than traditional apps:

  • AI agents calling internal tools (ticketing, CRM, billing, admin APIs)
  • More third-party APIs (LLM, vector DB, analytics, observability, queues)
  • More automation in CI/CD and platform pipelines
  • More ephemeral compute (jobs, serverless, short-lived environments)

That means more API key security and service account security work, not less.


What counts as a non-human identity?

In practice, your non-human identity inventory should cover:

  • API keys (vendor keys, internal API keys, webhook secrets)
  • Service accounts/service principals
  • IAM roles and workload identities (OIDC, STS, federation)
  • CI/CD deploy tokens, runner tokens, registry tokens
  • Kubernetes service accounts and secret mounts
  • AI agents and bots (including “ops bots” and “LLM agents”)

The goal of non-human identity security is simple: every machine identity is discoverable, scoped, short-lived, monitored, and revocable by default.


Reference implementation (what you can ship this quarter)

A practical repo layout that supports the controls below:

.
├── identity-inventory.yaml
├── policy/
│   ├── iam.rego
│   └── secrets.rego
├── scripts/
│   ├── secret_scan.py
│   ├── validate_inventory.py
│   ├── export_cloud_identities.sh
│   ├── anomaly_detect.py
│   ├── disable_aws_key.sh
│   └── evidence_bundle.py
└── .github/workflows/
    ├── security-gates.yml
    └── rotate-secrets.yml

Control 1) Inventory and classify API keys, service accounts, and tokens

A. Repo inventory: find leaks and stop repeats

Quick repo sweep (bash):

# Find common key patterns and obvious leaks
git grep -nE "(AKIA[0-9A-Z]{16}|xox[baprs]-|ghp_[A-Za-z0-9]{36,}|AIza[0-9A-Za-z\\-_]{35})" -- .

# Find high-entropy strings (rough heuristic)
git grep -nE "['\\\"]([A-Za-z0-9+/=]{40,})['\\\"]" -- .

# Hunt .env drift (should not exist in committed history)
git ls-files | grep -E "(^|/)\\.env(\\.|$)"

Pre-commit guardrail (example .pre-commit-config.yaml):

repos:
  - repo: local
    hooks:
      - id: block-secrets
        name: Block hard-coded secrets
        entry: bash -lc 'python scripts/secret_scan.py'
        language: system
        pass_filenames: false

Minimal Python secret scanner (entropy + pattern checks):

# scripts/secret_scan.py
import os, re, math, sys

PATTERNS = [
    re.compile(r"AKIA[0-9A-Z]{16}"),                # AWS access key id
    re.compile(r"ghp_[A-Za-z0-9]{36,}"),            # GitHub token
    re.compile(r"AIza[0-9A-Za-z\\-_]{35}"),         # Google API key
]

def shannon_entropy(s: str) -> float:
    if not s: return 0.0
    freq = {c: s.count(c) / len(s) for c in set(s)}
    return -sum(p * math.log2(p) for p in freq.values())

def scan_file(path: str) -> list[str]:
    hits = []
    try:
        data = open(path, "r", encoding="utf-8", errors="ignore").read()
    except Exception:
        return hits

    for rx in PATTERNS:
        if rx.search(data):
            hits.append(f"pattern:{rx.pattern}")

    # entropy scan for suspicious long strings (tune thresholds per repo)
    for m in re.finditer(r"['\\\"]([A-Za-z0-9+/=]{40,})['\\\"]", data):
        candidate = m.group(1)
        if shannon_entropy(candidate) >= 4.2:
            hits.append("entropy>=4.2")

    return hits

def main():
    bad = []
    for root, _, files in os.walk("."):
        if any(seg in root for seg in [".git", "node_modules", "dist", "build", ".venv"]):
            continue
        for f in files:
            if f.endswith((".png", ".jpg", ".zip", ".pdf")):
                continue
            path = os.path.join(root, f)
            hits = scan_file(path)
            if hits:
                bad.append((path, hits))

    if bad:
        print("Secret scan failed. Findings:")
        for path, hits in bad:
            print(f"- {path}: {', '.join(hits)}")
        sys.exit(1)

    print("Secret scan passed.")
    sys.exit(0)

if __name__ == "__main__":
    main()

B. Cloud inventory: export identities and last-used signals

A simple export script (extend per cloud):

# scripts/export_cloud_identities.sh
set -euo pipefail
mkdir -p evidence

echo "Exporting AWS IAM users and access keys..."
aws iam list-users --query "Users[].UserName" --output text > evidence/aws_users.txt

while read -r u; do
  aws iam list-access-keys --user-name "$u" --output json > "evidence/aws_keys_${u}.json" || true
done < evidence/aws_users.txt

echo "Exporting GCP service accounts..."
gcloud iam service-accounts list --format=json > evidence/gcp_service_accounts.json || true

echo "Exporting Azure service principals..."
az ad sp list --all -o json > evidence/azure_service_principals.json || true

echo "Done. Evidence written to ./evidence/"

C. Classify identities in a manifest engineers can own

Put this in version control (no secrets inside – just metadata):

# identity-inventory.yaml
version: 1
non_human_identities:
  - name: payments-api-prod
    type: workload_identity
    platform: kubernetes
    owner_team: payments
    environment: prod
    allowed_actions:
      - read:secrets/payments/prod/*
      - write:metrics/*
    rotation_policy:
      ttl_minutes: 60
      rotation_max_days: 7
    detection_baseline:
      allowed_ips: ["10.0.0.0/8"]
      allowed_regions: ["us-east-1"]
      max_calls_per_minute: 200

  - name: agent-support-bot
    type: agent_identity
    platform: internal
    owner_team: support-platform
    environment: prod
    allowed_actions:
      - call:/api/tickets/*:read
      - call:/api/users/*:read
    rotation_policy:
      ttl_minutes: 15
      rotation_max_days: 1
    detection_baseline:
      max_calls_per_minute: 60

This manifest becomes the backbone of your non-human identity security program: owner, scope, TTL, and detection baseline are all reviewable.


Control 2) Replace static keys with short-lived identity by default

Static keys are hard to monitor, easy to copy, and rarely rotated fast enough. Prefer short-lived credentials:

  • OIDC / federation for CI/CD and workloads
  • STS-style role assumption with tight conditions
  • Time-bound tokens for internal APIs (JWT with short exp)

A. GitHub Actions -> Cloud role via OIDC (no static cloud keys)

# .github/workflows/deploy.yml
name: deploy
on:
  push:
    branches: [ "main" ]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy (OIDC)
        run: ./scripts/deploy.sh

B. Example OIDC trust policy concept (tight repo + branch binding)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
        "StringLike": { "token.actions.githubusercontent.com:sub": "repo:ORG/REPO:ref:refs/heads/main" }
      }
    }
  ]
}

C. Internal API tokens: short-exp JWT (Node.js example)

import jwt from "jsonwebtoken";

export function mintServiceToken(serviceName) {
  return jwt.sign(
    { sub: serviceName, scope: ["tickets:read"], typ: "m2m" },
    process.env.SIGNING_KEY,
    { expiresIn: "10m", issuer: "auth.internal" }
  );
}

Control 3) Least privilege by design: roles, scopes, and expiration strategies

Least privilege for non-human identities should be boring:

  • No wildcards unless a compensating control exists
  • Separate identities per environment (dev/stage/prod)
  • Separate identities per workload (API, worker, ETL, agent)
  • Explicit denies for “should never happen” actions

A. Example policy: read only one secret path, deny enumeration

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadOnlySpecificSecrets",
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": ["arn:aws:secretsmanager:us-east-1:123456789012:secret:payments/prod/*"]
    },
    {
      "Sid": "DenyListSecrets",
      "Effect": "Deny",
      "Action": ["secretsmanager:ListSecrets"],
      "Resource": "*"
    }
  ]
}

B. Kubernetes service account to workload identity (example annotation pattern)

apiVersion: v1
kind: ServiceAccount
metadata:
  name: payments-api
  namespace: payments
  annotations:
    # Bind this K8s SA to a cloud role/service account (provider-specific)
    iam.example.com/role: payments-api-prod

Control 4) CI/CD guardrails: fail builds on key misuse and dangerous IAM

Your pipeline is the cheapest place to stop bad secrets and bad identity design.

A. Secret scanning + manifest validation + policy-as-code gate (GitHub Actions)

# .github/workflows/security-gates.yml
name: security-gates
on:
  pull_request:

jobs:
  gates:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run secret scan
        run: python scripts/secret_scan.py

      - name: Validate identity inventory
        run: python scripts/validate_inventory.py

      - name: Policy-as-code for IaC
        run: |
          conftest test infra/ -p policy/

Manifest validator (example):

# scripts/validate_inventory.py
import sys, yaml

doc = yaml.safe_load(open("identity-inventory.yaml"))
items = doc.get("non_human_identities", [])

required = ["name","type","owner_team","environment","rotation_policy"]
bad = []

for i in items:
    missing = [k for k in required if k not in i]
    ttl = (i.get("rotation_policy") or {}).get("ttl_minutes")
    if missing:
        bad.append((i.get("name","<unnamed>"), f"missing:{missing}"))
    if ttl is None or int(ttl) > 120:
        bad.append((i.get("name","<unnamed>"), "ttl_minutes must be <= 120"))

if bad:
    print("Inventory validation failed:")
    for name, reason in bad:
        print(f"- {name}: {reason}")
    sys.exit(1)

print("Inventory validation passed.")

B. Policy-as-code: block static keys and wildcard permissions (Rego)

# policy/iam.rego
package iam

deny[msg] {
  input.resource_type == "aws_iam_access_key"
  msg := "Static IAM access keys are banned. Use OIDC/workload identity."
}

deny[msg] {
  some s
  s := input.iam_statement[_]
  s.Effect == "Allow"
  s.Action[_] == "*"
  msg := "Wildcard Action is not allowed for non-human identities."
}

C. GitLab CI version (same idea, different pipeline)

stages: [test]

security_gates:
  stage: test
  image: python:3.12-slim
  script:
    - python scripts/secret_scan.py
    - python scripts/validate_inventory.py

Control 5) Runtime telemetry: alert on anomalous machine-identity activity

Good non-human identity security has runtime signals. You want alerts for:

  • A key used from a new region / IP / network segment
  • A service account calling a new API set
  • Token usage spikes (possible looping or abuse)
  • “Should never happen” actions (ListSecrets, CreateUser, wildcard assume role)

A. Emit app-side “machine identity” logs (structured)

{
  "event": "non_human_identity_call",
  "identity": "payments-api-prod",
  "action": "secrets.read",
  "resource": "secrets/payments/prod/db_password",
  "request_id": "7c0e...",
  "result": "success"
}

B. Event rule concept: match sensitive secret reads/writes

{
  "source": ["aws.secretsmanager"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventName": ["GetSecretValue", "PutSecretValue", "UpdateSecret"]
  }
}

C. Simple anomaly detector (Python example)

# scripts/anomaly_detect.py
import json, sys
from collections import Counter

events = [json.loads(line) for line in sys.stdin if line.strip()]

by_identity = {}
for e in events:
    by_identity.setdefault(e["identity"], []).append(e)

alerts = []
for ident, evs in by_identity.items():
    actions = [e["action"] for e in evs]
    c = Counter(actions)

    if len(c.keys()) > 20:
        alerts.append((ident, "high action diversity"))
    if len(evs) > 5000:
        alerts.append((ident, "high call volume"))

for a in alerts:
    print(f"ALERT: {a[0]}: {a[1]}")

Control 6) Incident handling: short rotation + revocation playbooks

When a key leaks, you need muscle memory. Keep the playbook short:

  1. Identify the identity (from inventory) and blast radius (where used)
  2. Revoke the credential (disable key / revoke token / rotate secret)
  3. Replace with short-lived identity if possible (OIDC/workload identity)
  4. Prove closure (pipeline checks + telemetry back to baseline)

AWS key disable script (example):

# scripts/disable_aws_key.sh
set -euo pipefail
USER="$1"
KEY_ID="$2"

aws iam update-access-key --user-name "$USER" --access-key-id "$KEY_ID" --status Inactive
echo "Disabled access key $KEY_ID for user $USER"

Rotation pipeline stub (example):

# .github/workflows/rotate-secrets.yml
name: rotate-secrets
on:
  schedule:
    - cron: "0 2 * * 1"  # weekly

jobs:
  rotate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Rotate secrets
        run: ./scripts/rotate_secrets.sh
      - name: Smoke test workloads
        run: ./scripts/smoke_test.sh

Control 7) Bridge to compliance and create audit evidence automatically

Generate evidence artifacts automatically:

  • Inventory manifest (who owns each non-human identity)
  • Rotation logs (when rotated, by pipeline run)
  • Access logs (when used, from where, to do what)
  • Exceptions (approved, time-bounded, reviewed)

Evidence bundle generator (example):

# scripts/evidence_bundle.py
import json, time, hashlib, yaml

inv = yaml.safe_load(open("identity-inventory.yaml"))
payload = {
    "generated_at": int(time.time()),
    "inventory_count": len(inv.get("non_human_identities", [])),
    "inventory": inv.get("non_human_identities", []),
}

raw = json.dumps(payload, sort_keys=True).encode()
payload["sha256"] = hashlib.sha256(raw).hexdigest()

open("evidence/non_human_identity_evidence.json", "w").write(json.dumps(payload, indent=2))
print("Wrote evidence/non_human_identity_evidence.json")

Control 8) Developer-friendly workflows and policy snippets

CODEOWNERS for identity manifests and policy:

# CODEOWNERS
/identity-inventory.yaml  @platform-team @security-champions
/policy/                 @platform-team @security-champions
/.github/workflows/      @platform-team

PR template snippet:

## Non-human identity security checklist (PR)
- [ ] No secrets committed (CI secret scan passes)
- [ ] Identity inventory updated (owner, TTL, baseline)
- [ ] Permissions are least privilege (no wildcards)
- [ ] Telemetry exists for identity actions

Control 9) Quick external reality check: scan your public exposure

Even strong internal non-human identity security needs an outside-in view: misconfigured endpoints, missing headers, exposed admin paths, or API surfaces can increase the impact of any credential misuse.

Use our free tool here: https://free.pentesttesting.com/

Free Website Vulnerability Scanner tools dashboard

Screenshot of the free tools webpage where you can access security assessment tools for different vulnerability detection.
Screenshot of the free tools webpage where you can access security assessment tools for different vulnerability detection.

Sample assessment report to check Website Vulnerability

An example of a vulnerability assessment report generated using our free tool provides valuable insights into potential vulnerabilities.
An example of a vulnerability assessment report generated using our free tool provides valuable insights into potential vulnerabilities.

Call to action

Build stronger developer security practices with real examples and expert help:


Where Cyber Rely can help engineering teams ship this


Recommended next reads (recent Cyber Rely posts)

See more: https://www.cybersrely.com/blog/


Free Consultation

If you have any questions or need expert assistance, feel free to schedule a Free consultation with one of our security engineers>>

🔐 Frequently Asked Questions (FAQs)

Find answers to commonly asked questions about Non-Human Identity Security.

Get a Quote

Leave a Comment

Your email address will not be published. Required fields are marked *

Cyber Rely Logo cyber security
Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.