Lightweight authorization sidecar for AI agents, APIs, and microservices.
Open-source. Self-hostable. No vendor lock-in.
The Vengtoo Agent runs alongside your services and makes authorization decisions locally with sub-millisecond latency. It pulls policies from Vengtoo Cloud (or loads them from a local file), evaluates requests against the authorization engine in-memory, and returns allow/deny decisions without network round-trips on the hot path.
Policies are written in Vengtoo's native YAML/JSON format: declarative allow/deny rules with roles, attribute conditions, time windows, IP allowlists, MFA checks, and trust levels, no policy language to learn. Already running OPA? The agent also accepts Rego files as a drop-in in local mode, so you can point it at an existing policy fleet and try it in minutes.
- Serves
POST /access/v1/evaluationwith sub-millisecond decision latency, no per-request calls to the cloud - Syncs policy bundles automatically from Vengtoo Cloud: pushed instantly over a change stream, with a poll fallback (configurable interval)
- Keeps serving decisions from the last good bundle if the cloud becomes unreachable (reported via
/healthzasdegraded) - Verifies Ed25519 bundle signatures against pinned keys, so a compromised distribution channel can't inject policy
- Exposes Prometheus metrics, structured decision logs, and health endpoints out of the box
Verify this yourself:
go run ./cmd/benchmark
go run ./cmd/benchmark -policies 10000 -roles 200 -subjects 20000 -iterations 50000cmd/benchmark generates a synthetic policy bundle (mixed direct/role assignments, ABAC conditions, occasional DENY overrides; see cmd/benchmark/bundle.go for exact proportions, which are a documented assumption, not customer data) and measures YAMLEngine.Evaluate(), the exact code path that serves every /access/v1/evaluation request in default (cloud-synced) mode. No network calls, no OPA/Rego involved (Rego is only used by the separate --policy standalone mode, for pointing the agent at your own existing .rego files).
Representative results on a normal development laptop:
| Bundle | p50 | p95 | p99 |
|---|---|---|---|
| 300 policies / 500 subjects | 0.6µs | 1.0µs | 2.4µs |
| 2,000 policies / 5,000 subjects | 1.3µs | 2.3µs | 4.0µs |
| 10,000 policies / 20,000 subjects | 1.5µs | 2.4µs | 3.0µs |
p95 stays in single-digit microseconds even at 10,000 policies: evaluation is O(subject assignments), not O(all policies), so it doesn't degrade with bundle size the way a linear policy scan would. -policies/-roles/-subjects/-iterations are all flags; vary them and see how it holds up on your own hardware and bundle shape, not just ours.
Docker (recommended): vengtoo/agent on Docker Hub.
docker pull vengtoo/agent:latestGo install:
go install github.com/vengtoo/agent/cmd/agent@latestBinary download:
Grab the latest release from GitHub Releases for your platform (linux/amd64, linux/arm64, darwin/arm64).
Create vengtoo-agent.yaml (or set environment variables):
api_key: "your-vengtoo-api-key"
cloud_url: "https://api.vengtoo.com"
listen_addr: "0.0.0.0:8181"
poll_interval: "30s"# With config file
vengtoo-agent --config ./vengtoo-agent.yaml
# With env vars
VENGTOO_API_KEY=your-key vengtoo-agent
# With Docker
docker run -d \
-e VENGTOO_API_KEY=your-key \
-p 8181:8181 \
-v vengtoo-cache:/var/lib/vengtoo/bundles \
vengtoo/agent:latest# Allowed request
curl -s -X POST http://localhost:8181/access/v1/evaluation \
-H "Content-Type: application/json" \
-d '{
"subject": { "type": "agent", "id": "ai-assistant" },
"resource": { "type": "mcp_tool", "name": "database__query" },
"action": { "name": "invoke" }
}'{
"allowed": true,
"reason": "Access granted via role",
"access_path": "role"
}# Denied request — AI agent tries to drop a table
curl -s -X POST http://localhost:8181/access/v1/evaluation \
-H "Content-Type: application/json" \
-d '{
"subject": { "type": "agent", "id": "ai-assistant" },
"resource": { "type": "mcp_tool", "name": "database__execute", "attributes": { "sql": "DROP TABLE users" } },
"action": { "name": "invoke" }
}'{
"allowed": false,
"reason": "BLOCKED: DROP operations are not permitted for AI agents"
}The agent loads config from YAML (--config <path>, ./vengtoo-agent.yaml, or ~/.vengtoo/agent.yaml). Environment variables override YAML values.
| Env var | YAML key | Default | Description |
|---|---|---|---|
VENGTOO_API_KEY |
api_key |
- (required for cloud mode) | API key from Vengtoo Cloud |
VENGTOO_CLIENT_SECRET |
client_secret |
- | When set, requires Authorization: Bearer <secret> on /access/v1/evaluation. |
VENGTOO_CLOUD_URL |
cloud_url |
https://api.vengtoo.com |
Vengtoo Cloud base URL |
VENGTOO_TENANT_ID |
tenant_id |
(auto-resolved) | Tenant ID; auto-detected from bundle if not set |
VENGTOO_LISTEN_ADDR |
listen_addr |
0.0.0.0:8181 |
HTTP listen address |
VENGTOO_POLL_INTERVAL |
poll_interval |
30s |
How often to sync policies from the cloud |
VENGTOO_CACHE_DIR |
cache_dir |
~/.vengtoo/bundles |
Directory for persisted policy bundles |
VENGTOO_LOG_LEVEL |
log_level |
info |
Log verbosity (debug, info, warn, error) |
VENGTOO_DECISION_LOG |
decision_log |
false |
Enable structured JSON decision logging to stdout |
VENGTOO_AGENT_NAME |
agent_name |
hostname | Identifies this agent instance in the dashboard |
VENGTOO_AGENT_REGION |
agent_region |
- | Display label for the region this agent runs in |
VENGTOO_AGENT_DOMAIN |
agent_domain |
- | Display label for the domain or environment |
VENGTOO_HEARTBEAT_INTERVAL |
heartbeat_interval |
30s |
How often the agent sends a liveness ping to cloud |
VENGTOO_AUDIT_FORWARDING |
audit_forwarding |
true |
Forward decisions to cloud Decision Log |
VENGTOO_AUDIT_ENDPOINT |
audit_endpoint |
derived from cloud_url |
Override the audit ingest endpoint |
VENGTOO_AUDIT_BATCH_SIZE |
audit_batch_size |
100 |
Decision events to buffer before flushing |
VENGTOO_AUDIT_BATCH_INTERVAL |
audit_batch_interval |
5s |
Maximum wait before flushing a partial batch |
VENGTOO_AUDIT_BUFFER_SIZE |
audit_buffer_size |
10000 |
In-memory buffer capacity for decision events |
VENGTOO_AUDIT_RETRY_ATTEMPTS |
audit_retry_attempts |
5 |
Retry attempts for failed audit log flushes |
VENGTOO_TRUSTED_KEYS_PATH |
trusted_keys_path |
~/.vengtoo/trusted_keys.json |
Pinned public keys for bundle signature verification |
VENGTOO_BUNDLE_SIGNATURE_REQUIRED |
bundle_signature_required |
false |
Reject unsigned bundles; set true for strict verification |
VENGTOO_AGENT_HOSTING |
agent_hosting |
self |
Hosting label shown in the dashboard (self, aws, gcp, etc.) |
The agent connects to Vengtoo Cloud, pulls your tenant's native policy bundle, and keeps it fresh two ways: an SSE change stream pushes updates the moment a policy changes, and a poll fallback (default 30s) covers dropped streams. This is the standard production deployment. Cloud mode always uses the native engine; Rego is not involved.
VENGTOO_API_KEY=your-key vengtoo-agentNote: cloud-mode requests address subjects and resources by their canonical IDs. If the initial bundle fetch fails at startup the agent exits; it does not yet cache bundles to disk across restarts (planned).
Load policies from a local file instead of the cloud. No API key or cloud account required. Two formats are supported:
Native YAML/JSON (recommended): Vengtoo's own policy format, same semantics as cloud mode:
vengtoo-agent --policy ./authz.yaml
# Two-file mode: rules in YAML, assignments/role members in JSON data
vengtoo-agent --policy ./authz.yaml --data ./data.json# authz.yaml
policies:
- id: eng-can-read-docs
effect: ALLOW
actions: [read]
resource_type: document
conditions:
subject_attrs:
- {key: dept, op: eq, value: engineering}
mfa_required: {}
assignments:
- {entity_type: role, entity_id: engineers}
roles:
- id: engineers
members:
- {entity_type: entity, entity_id: alice}Conditions support eq/ne/gt/gte/lt/lte/in/not_in/matches attribute checks on subject, resource, and context, plus time_window, ip_allowlist, mfa_required, trust_level, and custom Go condition functions. Malformed condition keys fail closed: the policy is kept but never matches, and a warning names the policy and the offending key at load time.
OPA Rego (drop-in compatibility): point the agent at an existing .rego policy fleet and it behaves as a local OPA engine. Useful for trying the agent as an OPA replacement without rewriting anything:
# Single file
vengtoo-agent --policy ./examples/policy.rego
# Directory of .rego files (loaded as separate OPA modules)
vengtoo-agent --policy ./policies/
# With external data (roles, groups, permissions, etc.)
vengtoo-agent --policy ./policies/ --data ./data/
# With a non-standard decision rule name
vengtoo-agent --policy ./policies/ --decision permitRego is supported in local mode only; cloud bundles are always native. If you're starting fresh, use the native format.
| Flag | Default | Description |
|---|---|---|
--policy <path> |
- | Path to a native .yaml/.yml/.json policy file, a .rego file, or a directory of .rego files |
--data <path> |
- | Native: JSON file with assignments/role_members. Rego: JSON file(s) loaded as OPA data (data.*) |
--decision <rule> |
allow |
Rego only: OPA rule name for the allow/deny result (e.g. permit, authorized) |
--addr <addr> |
0.0.0.0:8181 |
HTTP listen address |
See examples/policy.rego for a starter Rego policy.
Local mode is useful for:
- Self-hosted deployments without cloud dependency
- Dropping in an existing OPA policy fleet with no changes
- CI/CD pipeline testing
- Policy authoring and iteration
The agent passes the following input shape to OPA:
{
"subject": { "id": "...", "type": "...", "attributes": {}, "roles": [] },
"resource": { "id": "...", "type": "...", "name": "...", "attributes": {} },
"action": { "name": "invoke" },
"context": { "time": "2026-06-27T10:00:00Z" }
}input.action is an object: use input.action.name to get the action string. This matches standard OPA convention so existing policies work without modification.
The agent auto-detects the package from your Rego file. Use any package name you like:
package myapp # query path: data.myapp.allow
package authz # query path: data.authz.allow
package vengtoo.mcp # query path: data.vengtoo.mcp.allowYour policy must define allow (bool). Optionally define reason (string) for a human-readable deny message returned in the response.
| Endpoint | Purpose |
|---|---|
POST /access/v1/evaluation |
Single authorization check. Returns { allowed, reason, access_path }. |
POST /access/v1/evaluations |
Batch authorization: evaluate multiple subject/resource/action tuples in one request. |
GET /.well-known/authzen-configuration |
AuthZEN 1.0 discovery endpoint. |
GET /healthz |
Liveness check. Returns 200 while the process is running. Includes bundle revision, sync age, and degraded status. |
GET /readyz |
Readiness check. Returns 200 once a policy bundle is loaded (from cache or cloud). Returns 503 until ready. |
GET /metrics |
Prometheus metrics (decisions total, latency histogram, sync status, degraded state). |
Enable structured decision logs for auditing and debugging:
VENGTOO_DECISION_LOG=true vengtoo-agentEach /access/v1/evaluation call emits a JSON log line:
{
"time": "2026-04-19T14:03:11.482Z",
"level": "INFO",
"msg": "decision",
"subject_id": "user-123",
"resource_id": "doc-456",
"action": "view",
"decision": true,
"reason": "Access granted via role",
"access_path": "role",
"ms": 0.42
}| Metric | Type | Description |
|---|---|---|
vengtoo_agent_decisions_total |
counter | Total authorization decisions (by allowed, access_path) |
vengtoo_agent_decision_duration_seconds |
histogram | Evaluation latency |
vengtoo_agent_degraded |
gauge | 1 when serving from stale cache |
vengtoo_agent_bundle_last_sync_timestamp_seconds |
gauge | Unix timestamp of last successful sync |
Using AI agents with MCP (Claude Code, Cursor, VS Code, GitHub Copilot)? The Vengtoo MCP Gateway sits in front of your MCP servers and uses the Vengtoo Agent to authorize every tool call before it executes.
- GitHub Issues: Bug reports and feature requests
- Documentation: Guides and API reference
Apache-2.0, see LICENSE.