{"slug": "agent-identities-are-the-new-service-accounts", "title": "Agent identities are the new service accounts", "summary": "A developer built agent-off-switch (aos), an offline CLI tool that reads saved exports from Entra, AWS and GitHub to inventory non-human identities, rank findings by blast radius and provide an off switch for agent identities. The tool addresses the governance gap where AI agents act through service principals, managed identities, IAM roles and GitHub Apps that are often unowned and unre-certified, a risk OWASP categorizes as ASI03, Identity & Privilege Abuse.", "body_md": "Part of my job is running access reviews. Human accounts are the easy part: there is an HR system, a manager, a leaver process. The hard part has always been the service accounts, and AI agents have made that part bigger.\n\nAn agent does not act with its own authority. It acts with whatever identity it was given. In practice that is an Entra app registration or service principal, a managed identity, an AWS IAM role or an access key, a GitHub App installation or a deploy key. These are the service accounts we already struggle to govern, and agents add more of them, created faster, often by a team running a pilot rather than by the people who run identity.\n\nThe pattern I keep seeing has three parts. Nobody owns the list. Nobody recertifies it. And when something goes wrong, nobody can stop one of these identities quickly and prove it is stopped.\n\nThe [OWASP Top 10 for Agentic Applications for 2026](https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/) lists ASI03, Identity & Privilege Abuse, as its own category. The [OWASP announcement](https://genai.owasp.org/2025/12/09/owasp-top-10-for-agentic-applications-the-benchmark-for-agentic-security-in-the-age-of-autonomous-ai/) describes leaked credentials letting agents operate \"far beyond their intended scope\". Whatever an agent is tricked into doing, the blast radius is set by its identity's grants.\n\nMicrosoft says much the same in its [governance guidance for agent identities](https://learn.microsoft.com/en-us/entra/id-governance/agent-id-governance-overview): agents have historically reached Microsoft services through tools that use service principals. [Microsoft Entra Agent ID](https://learn.microsoft.com/en-us/entra/agent-id/what-is-microsoft-entra-agent-id) is its answer, a dedicated identity type for agents built from agent identity blueprints, with Conditional Access, Identity Protection, governance and audit logs extended to them. It is a good direction. It does not cover the AWS role your coding agent assumes or the GitHub App it pushes with, and many agents in Entra still run as ordinary app registrations.\n\nThese are the patterns I look for first in a review.\n\n`Mail.ReadWrite` works for every mailbox in the tenant with no signed-in user. If nobody owns the app registration, nobody will notice when it is misused, and nobody will answer the question \"do we still need this?\".`Action: \"*\"` that a CI agent assumes through OIDC.`*` on `AWSRevokeOlderSessions` policy keyed on `aws:TokenIssueTime`. If your off switch is \"edit the trust policy\", it is half a switch.\nNone of these controls is new. They are the service account controls, applied to a population that grew while nobody was counting.\n\nI built [agent-off-switch](https://github.com/basitalisandhu/agent-off-switch) (`aos`) for the reviews I run. It reads saved exports from Entra, AWS and GitHub, plus an optional `agents.csv` register, and works offline with no credentials of its own. Everything below is real output from the synthetic demo tenant that ships in the repository: the ids are fake and the AWS account is the documentation account `111122223333`.\n\n**Inventory, owner, least privilege and credential hygiene** come from `aos review`, which ranks findings by blast radius (0 to 100) and gives each a fix:\n\n``` bash\n$ aos review examples/demo-tenant --as-of 2026-10-09\n# Agent access review\n\nAs of 2026-10-09: 13 non-human identities, 11 look like agents. 15 findings (8 error), ranked by blast radius (0 to 100, see docs/scoring.md).\n\n## 4 agent identities nobody owns\n\n- **coding-agent-runner** `aws-role:coding-agent-runner` (blast radius 100): Action * on Resource *\n  - switch off: `aos off examples/demo-tenant aws-role:coding-agent-runner`\n- **repo-autopilot** `github-app:repo-autopilot` (blast radius 95): administration: write on every repository (settings, branch protection, deletion)\n  - switch off: `aos off examples/demo-tenant github-app:repo-autopilot`\n- **mail-triage-agent** `entra:aaaaaaaa-0000-4000-8000-000000000001` (blast radius 90): Mail.ReadWrite as an application permission changes data for every user in the tenant\n  - switch off: `aos off examples/demo-tenant entra:aaaaaaaa-0000-4000-8000-000000000001`\n...\n```\n\nThe findings table that follows covers the rest of the failure modes above, including a client secret 434 days old and a deploy key that can push, each with its fix.\n\nThe same review can be written as SARIF for GitHub code scanning. `aos inventory` gives the full table of identities with owners, credentials, grants and last use, as Markdown, CSV or JSON.\n\n**The off switch** is `aos off`. It prints an ordered runbook and runs nothing. For the coding agent's role, the first two steps are the point I made above: stop new sessions, then revoke the ones already issued.\n\n``` bash\n$ aos off examples/demo-tenant aws-role:coding-agent-runner --as-of 2026-10-09\n# Off-switch runbook: coding-agent-runner\n\nIdentity `aws-role:coding-agent-runner` (aws-iam-role on aws). Owners: none recorded. Blast radius 100. Exports as of 2026-10-09.\n\naos printed these commands and ran none of them. Run each change in order, then its verification, and keep the verification output for the incident record. Values in <angle brackets> are for you to fill in.\n\n## 1 disable\n\n### Step 1: Stop new sessions: replace the trust policy (now trusts Federated: arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com) with a deny-all one; save the current one first\n...\n## 2 revoke sessions and grants\n\n### Step 2: Revoke every session issued before the disable time (the console's Revoke active sessions)\n...\n## 5 verify with the audit log\n\n### Step 5: Confirm in CloudTrail that nothing succeeded after the disable time\n...\n## Coverage\n\nEvery credential and grant in the exports is named by a step.\n```\n\nEach step in the full output has a change command, a verification command and a line to record the time and the person. The coverage line matters: if a grant in the exports is not named by any step, `aos` says so and exits 1.\n\n**Drift in CI** is `aos drift`, which compares a saved inventory with today's exports:\n\n``` bash\n$ aos drift examples/drift/baseline.json examples/demo-tenant --as-of 2026-10-09 --accept examples/drift/accepted.txt\n# Agent identity drift\n\nFrom 2026-09-01 (11 identities) to 2026-10-09 (13 identities): 4 changes, 3 not reviewed.\n\n| Change | Identity | Detail | Reviewed |\n|---|---|---|---|\n| new-identity | `github-app:repo-autopilot` | repo-autopilot (github-app, no owner, blast radius 95) | **no** |\n| new-identity | `github-app:docs-preview-bot` | docs-preview-bot (github-app, web-team@example.com, blast radius 55) | yes |\n| new-grant | `aws-role:coding-agent-runner` | agent-all (score 100) | **no** |\n| owner-lost | `entra:aaaaaaaa-0000-4000-8000-000000000001` | was owned by messaging-team@example.com; now no owner | **no** |\n...\n```\n\nIt exits 1 while anything is unreviewed, so it can gate a pull request. Accepting a change means adding its key to the accept file, so the decision sits in version control next to the inventory.\n\nI would rather say this plainly.\n\n`aos` gives you the data a recertification needs: owners, grants, credential ages, last use, and the unused and no-owner findings. It does not send attestations, collect decisions or remove access when nobody answers. That is still a process you run, or a governance product's job.`off` prints commands for a person to run.`drift` flags an owner that disappears between two inventories. It does not check whether a named owner's account is still active.\nIf you run access reviews, add the agent identities to the next one. Take the read-only exports, list what exists, and put a name against every row. The ones nobody claims are your first off-switch drill.\n\nThe repository is at [github.com/basitalisandhu/agent-off-switch](https://github.com/basitalisandhu/agent-off-switch), with the demo tenant, the [threat model](https://github.com/basitalisandhu/agent-off-switch/blob/main/docs/threat-model.md) and the [scoring rules](https://github.com/basitalisandhu/agent-off-switch/blob/main/docs/scoring.md). Issues and pull requests are welcome, especially new export formats and corrections to the scoring.", "url": "https://wpnews.pro/news/agent-identities-are-the-new-service-accounts", "canonical_source": "https://dev.to/basitalisandhu/agent-identities-are-the-new-service-accounts-4e97", "published_at": "2026-10-16 07:58:18+00:00", "updated_at": "2026-10-16 08:09:32.247167+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-policy", "developer-tools", "mlops"], "entities": ["agent-off-switch", "Microsoft Entra Agent ID", "Microsoft", "OWASP", "AWS", "GitHub", "Entra"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/agent-identities-are-the-new-service-accounts", "markdown": "https://wpnews.pro/news/agent-identities-are-the-new-service-accounts.md", "text": "https://wpnews.pro/news/agent-identities-are-the-new-service-accounts.txt", "jsonld": "https://wpnews.pro/news/agent-identities-are-the-new-service-accounts.jsonld"}}