Skip to main content
AI & Full-Stack·14 min read

Treat AI Agents as Non-Human Identities: Least Privilege and Runtime Guardrails in TypeScript / Next.js (2026)

Agents are the new insiders. This production guide shows how to give every TypeScript / Next.js agent its own identity, scoped short-lived credentials, tool allowlists, approval gates, and continuous logging so a compromised or goal-hijacked agent cannot reach production systems.

By Mussawar Hayat

Agents are your newest insiders. Trust the controls, not the model.

In October 2026 Microsoft publicly framed enterprise AI agents as “agentic insiders.” Satya Nadella’s principle is blunt: the most trustworthy agentic system is the one that lets you trust the model the least. Controls must sit outside the model.

This is not abstract risk. Agents already hold API keys, database connections, file-system access, and the ability to call tools. A prompt injection or goal hijack turns that access into an action. The practical response for TypeScript and Next.js teams is to treat every agent as a non-human identity (NHI) with its own credentials, least-privilege tools, and runtime gates.

What you will implement

  • Dedicated agent identity and short-lived credentials (never shared human tokens)
  • Tool allowlists with Zod schemas and risk classes
  • Approval gates for irreversible actions
  • Append-only audit logs of every tool call
  • Sandbox and network egress controls
  • Mapping to OWASP Agentic Top 10 (ASI01–ASI03 especially)

1. The problem

A chatbot that is tricked writes a bad answer. An agent that is tricked can send the email, delete the record, push the package, or move the money. Common failure modes in 2026 include:

  • Agents inheriting a developer’s full GitHub or cloud credentials
  • Shared service accounts with no owner and no expiry
  • Generic tools such as “run_shell” or “http_request” that accept any destination
  • No distinction between low-risk reads and high-impact writes
  • Missing attribution: logs show the human, not the agent

OWASP’s Top 10 for Agentic Applications (2026) names Agent Goal Hijack (ASI01), Tool Misuse (ASI02), and Identity & Privilege Abuse (ASI03) among the highest-impact risks. Microsoft’s guidance is to surround non-deterministic intelligence with deterministic controls.

2. Architecture: identity, tools, and gates

Separate three concerns.

  1. Identity layer – each agent run receives a short-lived token bound to a principal (user + agent id + task). The token never contains the human’s full scopes.
  2. Tool layer – only named, schema-validated tools are exposed. Each tool declares its risk class and required scopes.
  3. Gate layer – low-risk tools execute immediately; high-risk tools create an approval record and pause. A human or policy service must approve before the side effect occurs.

The model never sees raw secrets. It never chooses its own identity. It never receives a connection string.

3. Give every agent its own identity

Create an AgentIdentity table (or use your existing NHI system). Fields: id, ownerUserId, name, allowedScopes[], expiresAt, status. Issue a JWT or opaque token at the start of each run with claims:

{
  "sub": "agent:support-bot:run-abc123",
  "owner": "user:42",
  "scopes": ["tickets:read", "tickets:create"],
  "exp": 1730000000
}

In Next.js, resolve this in middleware or a Route Handler wrapper. Reject any request whose token is missing, expired, or lacks the scope the tool requires. Do not fall back to the user’s session cookie for agent traffic.

For coding agents that currently run as the developer, aim for a dedicated identity as soon as the host supports it. Until then, tightly scope the credentials the agent process can reach and inventory every installation.

4. Tool allowlists and risk classes

Define tools as plain TypeScript objects. Never expose a generic executor.

import { z } from 'zod';

export const tools = {
  create_ticket: {
    schema: z.object({ title: z.string().max(200), body: z.string().max(5000) }),
    risk: 'low' as const,
    scopes: ['tickets:create'],
    handler: async (input, ctx) => { /* DAL call */ }
  },
  refund_order: {
    schema: z.object({ orderId: z.string().uuid(), amount: z.number().positive() }),
    risk: 'high' as const,
    scopes: ['orders:refund'],
    handler: async (input, ctx) => { /* creates approval */ }
  }
} as const;

A dispatcher looks up the tool by name, validates the schema, checks scopes on the agent token, then either calls the handler or inserts an AgentApproval row. High-risk tools return { status: 'pending', approvalId } instead of executing.

5. Approval gates

Store approvals with status PENDING | APPROVED | REJECTED, the original arguments hash, the requesting agent run id, and an expiry. A separate worker or human UI flips the status. Only the worker that holds the privileged credential applies the side effect. The agent process never holds the refund credential.

This breaks the “lethal trifecta” of untrusted input + powerful tools + no human checkpoint.

6. Audit and observability

Log every tool invocation to an append-only table: agentId, toolName, argsHash, resultStatus, timestamp, durationMs. Never store full prompts or secrets. Add a simple anomaly detector (volume, new destinations, off-hours) that can raise a kill switch. The kill switch revokes the agent’s current tokens and marks the identity as suspended.

Microsoft’s Dynamic Agent Risk and Guardian Agents follow the same pattern at enterprise scale: continuous scoring and graduated containment.

7. Sandbox and egress

Run agent processes with restricted filesystem mounts (project directory only) and network egress allowlists. Docker or the host’s built-in sandbox is sufficient for most coding agents. Deny by default: reads allowed, writes and external calls require explicit permission or approval.

Scan the workspace for secrets before the agent starts. Prefer credential injection at execution time over .env files the agent can read.

8. Real use cases

  • Support agent creates tickets and notes; refunds wait for a human.
  • Coding agent opens a PR with a scoped GitHub token that can only push to one repo and cannot merge.
  • Ops agent requests a feature-flag change that appears in an approval queue.

9. Common mistakes

  • Reusing a developer’s personal access token for the agent.
  • Exposing a generic “http” or “shell” tool.
  • Letting the model pass tenantId or userId.
  • Storing long-lived secrets in files the agent can read.
  • Logging full tool arguments that contain customer data.
  • Skipping inventory so shadow agents appear with no owner.

10. FAQ

Can I just put “do not delete production data” in the system prompt?

No. Instructions inside the model are not a security boundary. Enforce limits outside the agent.

Do I need a full enterprise agent platform?

No. The pattern above works with a Next.js Route Handler, Prisma, and short-lived JWTs. Scale the identity store later.

How does this map to OWASP ASI03?

ASI03 (Identity & Privilege Abuse) is addressed by dedicated identities, scoped tokens, and no inheritance of human privileges.

What about MCP servers?

Treat each MCP server as a dependency: pin versions, allowlist tools, and give it only the credentials it needs. Never enable all project MCP servers by default.

How do I test without a live model?

Call the dispatcher with a signed test token and fixed JSON. Assert scope rejection, approval creation, and idempotent behaviour.

11. Summary

Treat every agent as a non-human identity. Give it its own short-lived credentials, only the tools it needs, and approval for anything irreversible. Log every action. The model proposes; your gateway and identity system dispose.

Key takeaway

The most trustworthy agentic system is the one that lets you trust the model the least. Put deterministic controls outside the model.


Need production agent guardrails on Next.js?

I design identity, tool, and approval layers for teams shipping agents that touch real data. Get in touch or see full-stack and AI development services.

Related reading: AI agent Postgres write guardrails and Build a production MCP server in TypeScript.