ZTAgent
Zero Trust Security for AI Agents
Never trust an agent action. Verify before execution.
ZTAgent is an open-source security framework for AI agents, from small developer projects to enterprise systems. Originally developed by Victor Fang in 2026.
- Product: https://ztagent.ai
- Open source: https://github.com/victorfang/ztagent
- Contact the ZTAgent.ai team: https://forms.gle/Bq4XNxcSVD2aHrrK6
- Author: VictorFang.com · X · LinkedIn
This repository is the Apache-2.0 ZTAgent Core gateway at the center of
ZTAgent. The PyPI package is ztagent;
the import remains ztagent_core. It puts one enforcement pipeline in front of
model and tool calls, with useful secure defaults, without replacing your
identity provider, reverse proxy, model host, or SIEM.
pip install ztagent
ztagent --help
Editions and services
| Offering | What it is |
|---|---|
| ZTAgent Core (this repository) | Apache-2.0 foundation you can self-host, audit, and extend |
| ZTAgent Enterprise | Commercial edition for production organizations (coming) |
| Consulting | Custom policy, tools, threat modeling, and deployment help |
Use Core when you want a compact, inspectable security boundary. Enterprise is planned for teams that need vendor-supported controls beyond the open-source core. Until that ships, ztagent.ai offers consulting to customize the gateway for your identity, policy, and tool landscape.
Contact the ZTAgent.ai team: https://forms.gle/Bq4XNxcSVD2aHrrK6
This is a security foundation, not a claim that signature matching makes an AI system completely safe. Prompt injection is not a solved problem. Layer the gateway with least-privilege tools, sandboxing, egress controls, human approval for consequential actions, and application-specific evaluation.
Where ZTAgent sits in the AI agent stack
ZTAgent is the policy enforcement point between your orchestrator (LangGraph, LangChain, or a custom loop) and every model or tool call. It does not replace the identity provider, reverse proxy, model host, or SIEM.
flowchart TB
U[User / client]
APP[Your app / API]
IDP[Identity<br/>Auth0 / Keycloak / OIDC]
ORCH[Orchestrator<br/>LangGraph / LangChain / custom]
ZT["ZTAgent Core<br/>verify before execution"]
M[Models<br/>OpenAI / Anthropic / compatible]
T[Registered tools]
OPA[OPA policy]
AUD[Audit / portal / SIEM export]
U --> APP
APP --> IDP
IDP --> ORCH
ORCH -->|"every model and tool call"| ZT
ZT --> M
ZT --> T
ZT --> OPA
ZT --> AUD
Inside ZTAgent the request is verified, then allowed or blocked:
flowchart LR
IN[Model or tool request] --> JWT[OIDC JWT]
JWT --> BL[Containment blocklist]
BL --> SIG[Signature scan]
SIG --> ANO[Anomaly counters]
ANO --> POL[OPA allow / deny]
POL -->|allowed| EXEC[Execute model or tool]
POL -->|denied| DENY[Block + audit]
EXEC --> OUT[Scan tool output]
OUT --> AUD2[HMAC-chained audit]
Bypass is a missed edge: if LangGraph still calls OpenAI or a webhook directly, ZTAgent never sees the action.
Detect, contain, and revoke a malicious agent
A stolen or abused Auth0 (or Keycloak) token can still pass JWT checks. ZTAgent
does not trust that token for execution. When a contain detection fires —
destructive-command signatures, or enough blocked events in 24 hours — it stops
the current call, writes the JWT sub to a local blocklist, then asks identity
to kill sessions. Local deny is applied first, so an Auth0 or Keycloak outage
cannot restore access.
sequenceDiagram
autonumber
actor Agent as Malicious agent
participant ZT as ZTAgent Core
participant BL as Local identity blocklist
participant AUD as HMAC audit
participant IdP as Auth0 / Keycloak / OIDC
participant WH as Containment webhook
Agent->>ZT: Model or tool call with a still-valid JWT
ZT->>ZT: Verify issuer, audience, signature, expiry
ZT->>ZT: Signatures, anomaly counters, OPA
Note over ZT: contain signature or repeated blocks in 24h
ZT-->>Agent: 403 block this request
ZT->>BL: Contain subject immediately
ZT->>AUD: security_detection contained true
opt Keycloak admin configured
ZT->>IdP: Logout all sessions for that user
end
opt webhook configured
ZT->>WH: subject, reason, event_id
WH->>IdP: Revoke Auth0 sessions or refresh tokens
end
Agent->>ZT: Later retry with the same JWT
ZT->>BL: Subject already contained?
ZT-->>Agent: 403 Identity is contained
Core Open Source implements the local blocklist and Keycloak session logout.
Auth0 (and other IdPs) are revoked through containment.webhook_url — point
that at a small Automation/SOAR job that calls the Auth0 Management API. The
gateway remains closed even if that webhook fails. An administrator unblocks a
subject through the protected admin API after review.
Three ways to use ZTAgent
Pick one path. You can start with 1 and move to 2 or 3 later; the same signatures, tool registry, and gateway code sit underneath.
| Path | You have | You add | Minimum extras |
|---|---|---|---|
| 1. Standalone | A prompt, an OpenAI (or compatible) key | ztagent serve as the agent API + admin portal |
Audit HMAC key. OPA/Docker optional in development |
| 2. Integrate an existing app | LangGraph / LangChain, Auth0 (or Keycloak), your tools | Wrap model and tool edges with the ZTAgent gateway | OIDC issuer, audience, JWKS. Do not take identity from graph state |
| 3. Import modules only | Your own server and orchestrator | Call SignatureScanner, ToolRegistry, or SecureAgentGateway in-process |
No FastAPI process required |
Hands-on attack examples (injection, malicious tools) are in docs/tutorial.md. Architecture detail is in docs/architecture.md.
1. Standalone ZTAgent
Use this for a new project or a demo. ZTAgent owns the HTTP API, model call, optional tools, audit log, and portal. Development defaults leave authentication off and allow running without OPA.
python -m venv .venv
source .venv/bin/activate
pip install -e .
cp .env.example .env
# OPENAI_API_KEY=...
# ZTAGENT_AUDIT_HMAC_KEY=$(ztagent secret)
ztagent check
ztagent serve
Open http://127.0.0.1:8000/admin. Call /v1/agent/run with a user message.
You do not need Auth0, LangGraph, or Docker for this path. Turn on OIDC and
fail-closed OPA before production (server.environment: production).
ztagent init my-agent scaffolds the same files in a new directory.
2. Integrate LangGraph, Auth0, and your app
Keep your identity provider and graph. Insert ZTAgent as the only path to models and side-effecting tools so the graph cannot bypass policy.
- Point
authat Auth0 (or Keycloak):issuer,audience,jwks_url,algorithms: [RS256], androle_claimmatching your token (permissions,realm_access.roles, or a custom namespaced claim). - Verify the access token, then build
Principalfrom that result — never from model output or LangGraph state. - Put
as_langchain_runnable(gateway, principal)on the model node. Send high-risk tools throughgateway.execute_tool(...), not raw HTTP/SDK calls.
pip install 'ztagent[langchain]'
# From this repository: pip install -e '.[langchain]'
from ztagent_core.api import create_gateway
from ztagent_core.auth import JWTAuthenticator
from ztagent_core.config import load_config
from ztagent_core.integrations import as_langchain_runnable
config = load_config()
gateway = create_gateway(config, tools=registry)
principal = JWTAuthenticator(config.auth).verify(access_token) # Auth0 JWT
secure_node = as_langchain_runnable(gateway, principal)
# LangGraph: attach secure_node as the model edge.
result = await secure_node.ainvoke({"messages": [{"role": "user", "content": "Hello"}]})
Auth0 example config/agent.yaml fragment:
auth:
enabled: true
issuer: https://YOUR_TENANT.auth0.com/
audience: ztagent-core
jwks_url: https://YOUR_TENANT.auth0.com/.well-known/jwks.json
algorithms: [RS256]
role_claim: permissions
The same pattern works with Keycloak or any OIDC provider that issues RS256 access tokens. See docs/tutorial.md.
3. Import only the modules you need
Use this when you already have an API and only want selected controls — for
example prompt-injection scanning — without running ztagent serve.
from pathlib import Path
from ztagent_core.guardrails import SignatureScanner
from ztagent_core.tools import ToolRegistry, ToolSpec
scanner = SignatureScanner.from_file(Path("config/signatures.yaml"))
findings = scanner.scan(user_prompt)
if any(item.action in {"block", "contain"} for item in findings):
raise PermissionError("Request blocked by ZTAgent signatures")
Other drop-in pieces: ToolRegistry / ToolSpec (schema-validated tools),
SecureAgentGateway (full PEP without FastAPI), JWTAuthenticator,
AuditLog, AnomalyDetector, ContainmentService, OPAClient. You are
responsible for calling them on every model and tool path; a missed edge is
a bypass.
What is included
- FastAPI-based API gateway with request limits and security headers
- OIDC JWT validation for Keycloak, Auth0, and compatible identity providers
- OPA policy enforcement point/client with fail-closed production defaults
- Registered, schema-validated tool gateway with risk metadata
- YAML signature rules with Unicode normalization and regex timeouts
- OpenAI Responses, Anthropic Messages, and OpenAI-compatible model APIs
- Optional LangChain
Runnableadapter; the core remains orchestrator-neutral - Stateless request heuristics and memory/SQLite rolling 24-hour counters
- HMAC-chained JSONL audit records with default secret/prompt redaction
- Local identity containment, Keycloak session logout, and a webhook for Auth0 or SOAR revocation
- Protected, dependency-free web administration portal
- Friendly project wizard and operational checks
Quick start (standalone)
Requires Python 3.11+. For the standalone path you only need an OpenAI (or compatible) API key and an audit HMAC key. Docker/OPA is optional until you disable the development policy bypass.
python -m venv .venv
source .venv/bin/activate
pip install ztagent
# From this repository, developers can instead: pip install -e .
# Create a separate starter project, or use this repository's included example.
ztagent init my-agent
cd my-agent
cp .env.example .env
# OPENAI_API_KEY=sk-...
ztagent secret # put output in ZTAGENT_AUDIT_HMAC_KEY
set -a; source .env; set +a
ztagent check
# Optional until you disable the OPA development bypass:
# docker compose up -d
ztagent serve
Open http://127.0.0.1:8000/admin. Development defaults disable authentication; the portal therefore uses a development administrator. Production configuration validation refuses disabled authentication or an OPA bypass.
Call the gateway (no bearer token required in standalone development):
curl http://127.0.0.1:8000/v1/agent/run \
-H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"Summarize this report"}]}'
Provider setup
Set provider.kind in config/agent.yaml:
| Provider | kind |
Key environment variable | Notes |
|---|---|---|---|
| OpenAI | openai |
OPENAI_API_KEY |
Uses /v1/responses, with storage disabled |
| Anthropic | anthropic |
ANTHROPIC_API_KEY |
Uses /v1/messages |
| Kimi/self-hosted | openai-compatible |
configurable | Set the server's /v1 base_url |
Models are deny-by-default: a requested model must appear in allowed_models.
API keys are read only from environment variables and are never returned or
written to audit events.
Identity and policy
On the standalone path, leave auth.enabled: false only in development.
On the integrate path, configure the issuer, audience, JWKS URL, fixed
asymmetric algorithms, and role claim under auth for Auth0, Keycloak, or
another OIDC provider. ZTAgent verifies signature, expiration, issued-at,
issuer, audience, and subject. Do not derive accepted JWT algorithms from a
token.
The gateway is the policy enforcement point (PEP). It sends only identity, roles, action, resource metadata, source IP, and request ID to OPA (the PDP). Prompts and tool arguments are not sent to OPA. The starter Rego policy allows:
- authenticated model calls;
- low/medium-risk tools for authenticated identities;
- high-risk tools only for the
ztagent-tool-adminrole.
Tailor policies/authz.rego to tenant, model, tool, data classification, and
business approval requirements. OPA is bound to loopback in compose.yaml.
Add a tool
from pydantic import BaseModel
from ztagent_core.tools import ToolRegistry, ToolSpec
class LookupArgs(BaseModel):
ticket_id: str
registry = ToolRegistry()
registry.register(
ToolSpec(
name="lookup_ticket",
description="Read one support ticket",
arguments=LookupArgs,
handler=lambda args: {"id": args.ticket_id},
risk="low",
)
)
Pass the registry to create_gateway(config, registry). For standalone, also
pass it to create_app(config, gateway). For LangGraph, keep the same registry
on the gateway the graph calls. Every /v1/tools/{name} request (and
gateway.execute_tool) receives authentication, signature scanning, OPA
authorization, validation, and audit. The registry intentionally has no shell,
filesystem, or arbitrary HTTP tool.
LangChain / LangGraph
The optional adapter is a LangChain Runnable, so it works as a LangGraph node.
See path 2 above. The application
must derive principal from a verified request; never accept identity or roles
from model output or untrusted chain state.
Before/after demo agents
Install the demo extra and run three small LangChain applications:
pip install 'ztagent[demos]'
# From this repository: pip install -e '.[demos]'
ztagent demo stock-injection
ztagent demo unauthorized-publish
ztagent demo article-only
Each command contrasts a deliberately vulnerable baseline with the protected framework path. The examples cover an indirect prompt injection hidden in stock data, unauthorized article publishing, and a safe article-only workflow. Email, DM, and social actions always remain in a local JSONL sandbox.
Use --live to exercise the configured OpenAI API or stay with the default
deterministic offline model for a repeatable, credential-free security demo.
See docs/tutorial.md for a step-by-step walkthrough of
configuration, prompt injection, and malicious tool use. See
docs/demos.md for expected output, the exact controls being
demonstrated, and important limitations.
CLI
ztagent init [DIRECTORY] Generate config, signatures, Rego, Compose, and app files
ztagent secret Generate a strong audit HMAC key
ztagent check Validate configuration and signatures; flag dev bypasses
ztagent demo Compare vulnerable and secured LangChain demo agents
ztagent serve Start the gateway and portal
Architecture and security scope
See the hands-on tutorial for configuration, prompt-injection blocks, malicious tool use, and audit examples. See docs/architecture.md for the request flow, threat coverage, design decisions, limitations, deployment checklist, and research references. See SECURITY.md for vulnerability reporting and operational security guidance. The blast-radius threat-modeling tutorial provides worked examples for the included agents.
Development
pip install -e '.[dev]'
ruff check .
mypy
pytest
Apache-2.0 licensed. Product: ZTAgent · source: github.com/victorfang/ztagent.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ztagent-0.1.0.tar.gz.
File metadata
- Download URL: ztagent-0.1.0.tar.gz
- Upload date:
- Size: 60.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1cb7033a00e6dc9129b3a831b6f83293f7bdfda9e1a5039b6062acea14c9d71b
|
|
| MD5 |
4772e82e790ac75585071cf1a8a070dc
|
|
| BLAKE2b-256 |
5ef35bf4af1a40e36c99d644689cacc047f29b79a3898c44d3ab420da6926c11
|
Provenance
The following attestation bundles were made for ztagent-0.1.0.tar.gz:
Publisher:
publish.yml on victorfang/ztagent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ztagent-0.1.0.tar.gz -
Subject digest:
1cb7033a00e6dc9129b3a831b6f83293f7bdfda9e1a5039b6062acea14c9d71b - Sigstore transparency entry: 2886471862
- Sigstore integration time:
-
Permalink:
victorfang/ztagent@d80524b683ed28031335b9c47b5a8450eece1228 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/victorfang
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d80524b683ed28031335b9c47b5a8450eece1228 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ztagent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ztagent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 46.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce4c0d1cbab566752477a1965a287c7c32aaba60c7591093e1a46b8c06811ae4
|
|
| MD5 |
d293ec8fb51b19bf8231f6db259e3892
|
|
| BLAKE2b-256 |
adb9b4ac03d265a5369bb20f28ef78985512cc4c013b95acfdd2ec0e393ea375
|
Provenance
The following attestation bundles were made for ztagent-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on victorfang/ztagent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ztagent-0.1.0-py3-none-any.whl -
Subject digest:
ce4c0d1cbab566752477a1965a287c7c32aaba60c7591093e1a46b8c06811ae4 - Sigstore transparency entry: 2886471878
- Sigstore integration time:
-
Permalink:
victorfang/ztagent@d80524b683ed28031335b9c47b5a8450eece1228 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/victorfang
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d80524b683ed28031335b9c47b5a8450eece1228 -
Trigger Event:
release
-
Statement type: