AI agents should earn trust, not assume it.
Built for the Authorized to Act: AI Agents with Auth0 Hackathon 2026 Build an agentic AI application using Auth0 for AI Agents Token Vault.
- Overview
- The Problem
- The Solution
- What Makes Vettra Different
- Architecture
- Backend Infrastructure (Google Cloud Run)
- Token Vault Credential Lifecycle
- Project Structure
- Tech Stack
- Prerequisites
- Installation & Setup (VS Code)
- Running the Application
- Running Tests
- API Documentation
- API Endpoints
- Key Features
- Demo Agent &
@protectDecorator - Threat Simulation Scenarios
- WASM Verifier (Optional Rust Build)
- Docker Deployment
- Cloud Deployment
- Production Readiness
- Judging Notes
- Bonus Blog Post
- License
- Built By
- Acknowledgments
Most AI agents today operate with a blank check. You hand them credentials, they act on your behalf, and somewhere between "helpful automation" and "catastrophic mistake" there is no checkpoint. Vettra closes that gap.
Vettra is a real-time authorization middleware for AI agents. It intercepts high-risk actions before they execute, surfaces them to the user with full context, and waits for an explicit decision. Built on Auth0 Token Vault, it ensures credentials are never exposed to the agent directly, every action is scoped to the minimum necessary permission, and every decision leaves an immutable audit trail.
AI agents are becoming the new automation layer across software. They read your emails, manage your calendar, push code, file tickets, move money. The tooling to build them has matured fast. The tooling to control them has not.
Today, when you give an agent access to your Gmail, it gets access to all of Gmail. When it connects to your database, it can read and write anything. When it calls an external API, it uses your credentials with your full scope. There is no native concept of bounded permission, no moment where a human says "yes, that specific action, right now, is approved."
This creates three compounding risks:
Credential exposure. Agent frameworks store API keys and OAuth tokens in environment variables, config files, or memory. One compromised agent means every integrated service is compromised.
Scope creep. Agents accumulate permissions over time. An agent that "just needed read access" ends up with write, delete, and admin. Nobody audits this because there is no interface to audit it.
Silent failure at scale. When an agent makes a mistake at 3am with no human in the loop, the blast radius is whatever permissions it had. By the time anyone notices, the action has propagated.
These are not hypothetical. They are the documented failure modes of every major AI agent deployment to date. And they are going to get dramatically worse as agents become more autonomous.
Vettra sits between your AI agent and the outside world. Every action the agent wants to take passes through a policy engine. Low-risk actions (reading data, fetching context) proceed automatically. Medium-risk actions (sending messages, creating records) trigger a real-time approval request. High-risk actions (deleting data, making payments, modifying permissions) require step-up authentication before they can even be presented for approval.
The agent never holds credentials. Auth0 Token Vault stores all secrets and releases them only after Vettra confirms that the action has been approved, the scope matches, and the identity of the approver is verified. If any of those checks fail, the token is never issued and the action never runs.
The result is an agent that operates with the minimum possible privilege, in a loop that keeps a human genuinely informed and in control, with a full record of every decision made.
Most agent security tools are perimeter-based. They try to prevent bad actors from accessing your agent. Vettra assumes the agent itself may need to be constrained, and builds the control surface accordingly.
Most authorization systems are static. Permissions are configured once and never revisited. Vettra treats authorization as a live, contextual decision made at the moment of action, with full awareness of what the action is, who is affected, and what the agent has already done in this session.
Most agent frameworks leak credentials by design. API keys flow through environment variables into agent memory into tool calls. Vettra breaks this chain entirely. The agent requests a token, Vettra evaluates the request, Auth0 Token Vault releases a scoped, time-limited credential if approved. The agent never sees the underlying secret.
The Rust WASM cryptographic layer is unique in this space. Before any token is released from Token Vault, Vettra runs a WebAssembly verification module that checks the integrity of the action request against a signed policy bundle. This verification happens client-side, is tamper-evident, and adds a hardware-grade trust anchor that no pure-Python or pure-JavaScript agent framework can match.
graph TB
subgraph Frontend["Vercel (Frontend)"]
REACT["React 18 + Vite 5<br/>Dashboard UI"]
WS_CLIENT["WebSocket Client<br/>Auto-reconnect"]
end
subgraph Backend["Google Cloud Run (Backend · asia-southeast2)"]
direction TB
INTERCEPT["Interceptor<br/>@protect Decorator"]
POLICY["Policy Engine<br/>Risk Scoring 0-100"]
WASM["Rust WASM Verifier<br/>Policy Integrity Check"]
WEBSOCKET["WebSocket Server<br/>Live Approval Push"]
AUDIT["Audit Logger<br/>Hash-chained SHA-256"]
SESSION["Session Manager<br/>Pre-authorization"]
TV_CLIENT["Token Vault Client<br/>RFC 8693 Exchange"]
end
subgraph Auth0["Auth0"]
TOKEN_VAULT["Token Vault<br/>Scoped Credential Issuance"]
STEPUP["Step-up Auth + MFA"]
end
subgraph External["External Services"]
GMAIL["Gmail"]
GITHUB["GitHub"]
STRIPE["Stripe"]
SLACK["Slack"]
end
DB[("SQLite / Cloud SQL<br/>Audit + Actions + Sessions")]
USER["User / Browser"] -->|HTTPS + WebSocket| REACT
REACT -->|"/api/* proxy"| INTERCEPT
WS_CLIENT ---|real-time| WEBSOCKET
INTERCEPT --> POLICY
POLICY --> WASM
WASM --> WEBSOCKET
POLICY --> AUDIT
SESSION --> STEPUP
TV_CLIENT -->|Token Exchange| TOKEN_VAULT
TOKEN_VAULT -->|Scoped Token| GMAIL
TOKEN_VAULT -->|Scoped Token| GITHUB
TOKEN_VAULT -->|Scoped Token| STRIPE
TOKEN_VAULT -->|Scoped Token| SLACK
AUDIT --> DB
TV_CLIENT --> DB
graph LR
subgraph GitHub["GitHub Repository"]
PUSH["Push to main"]
end
subgraph Vercel["Vercel"]
FE["React SPA<br/>CDN + Edge<br/>vettra-eight.vercel.app"]
end
subgraph CloudRun["Google Cloud Run"]
BE["FastAPI + WASM<br/>asia-southeast2<br/>Auto-scaling 0→3<br/>512Mi memory"]
end
subgraph Auth0["Auth0"]
TV["Token Vault<br/>OAuth + MFA"]
end
DB[("SQLite /<br/>Cloud SQL")]
PUSH -->|auto-deploy| FE
PUSH -->|manual deploy| BE
FE -->|"/api/* rewrite"| BE
BE -->|SDK| TV
BE --> DB
Vettra's backend runs on Google Cloud Run, a fully managed serverless container platform that scales from zero to handle production traffic without infrastructure management.
| Endpoint | URL | Status |
|---|---|---|
| Root | https://vettra-backend-810928102951.asia-southeast2.run.app |
{"name":"Vettra","version":"0.1.0","status":"running"} |
| Health | https://vettra-backend-810928102951.asia-southeast2.run.app/health |
{"status":"healthy"} |
| API Docs | https://vettra-backend-810928102951.asia-southeast2.run.app/docs |
Swagger UI |
-
Serverless auto-scaling (0 to 3 instances). Zero cost when idle, instant scale when traffic arrives. For a hackathon demo that gets periodic bursts of judge traffic, this is ideal: no idle server burning credits.
-
Container-native deployment. The backend ships as a Docker container (
backend/Dockerfile) with all dependencies baked in: FastAPI, wasmtime (for Rust WASM execution), SQLAlchemy, Auth0 SDK, and 12+ other packages.gcloud run deploy --source .builds and deploys in one command. -
Region:
asia-southeast2(Jakarta). Chosen for low latency to the primary development location. Cloud Run supports 30+ regions; production deployments can be multi-region with Cloud Run's traffic splitting. -
Managed HTTPS and TLS. Cloud Run provisions TLS certificates automatically. No cert management, no nginx config, no Let's Encrypt renewal scripts. The backend is immediately accessible over HTTPS with a Google-managed certificate.
-
WebSocket support. Cloud Run supports long-lived WebSocket connections (up to 60 minutes), which Vettra uses for real-time approval push. The WebSocket endpoint runs at
wss://vettra-backend-810928102951.asia-southeast2.run.app/ws/{user_id}. -
Environment variable injection. All secrets (Auth0 credentials, signing keys, database URLs) are injected via Cloud Run environment variables, never baked into the container image. This aligns with Vettra's zero-credential-exposure philosophy.
Service: vettra-backend
Region: asia-southeast2 (Jakarta)
Platform: managed (fully serverless)
Authentication: allow-unauthenticated (public API)
Memory: 512Mi per instance
Min instances: 0 (scale to zero)
Max instances: 3 (cost-bounded)
Port: 8080
Container: Python 3.12 + FastAPI + wasmtimeAPP_NAME=Vettra
ENVIRONMENT=production
PORT=8080
DATABASE_URL=sqlite+aiosqlite:///./vettra.db
CORS_ORIGINS=https://vettra-eight.vercel.app
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your_client_id
AUTH0_CLIENT_SECRET=your_client_secret
AUTH0_AUDIENCE=https://your-api-identifier
AUDIT_SIGNING_KEY=<generated-hex-key>
SESSION_SECRET=<generated-hex-key>The frontend (Vercel) proxies all /api/* requests to Cloud Run via vercel.json rewrites:
{
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://vettra-backend-810928102951.asia-southeast2.run.app/api/:path*"
},
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}This means the frontend never exposes the Cloud Run URL to the browser. All API traffic appears to come from vettra-eight.vercel.app/api/*. CORS is configured on the backend to only accept requests from the Vercel domain.
Note on WebSocket: Vercel's edge network does not proxy WebSocket connections. The frontend connects directly to the Cloud Run WebSocket endpoint at
wss://vettra-backend-810928102951.asia-southeast2.run.app/ws/{user_id}for real-time approval push and event streaming.
flowchart TD
A["AI Agent Requests Action"] --> B["Vettra Intercepts<br/>Parse · Classify · Verify (WASM)"]
B --> C{"Risk Classification<br/>(0-100)"}
C -->|"0-19"| LOW["LOW<br/>Auto-approve"]
C -->|"20-54"| MED["MEDIUM<br/>Human Approval Required"]
C -->|"55-100"| HIGH["HIGH<br/>Step-up Auth + Approval"]
LOW --> EXEC
MED --> APPROVE["User Approves on Dashboard"]
HIGH -->|MFA Challenge| APPROVE
APPROVE --> TV["Auth0 Token Vault Issues Credential<br/>Scoped · Time-limited · Single-use<br/>(RFC 8693 Exchange)"]
TV --> EXEC["Agent Executes Action<br/>Token Expires Automatically"]
AUDIT["Hash-chained Audit Trail<br/>(SHA-256 · HMAC-signed)"]
B -.->|log| AUDIT
APPROVE -.->|log| AUDIT
TV -.->|log| AUDIT
EXEC -.->|log| AUDIT
style LOW fill:#d1fae5,stroke:#16a34a,color:#000
style MED fill:#fef9c3,stroke:#ca8a04,color:#000
style HIGH fill:#fecaca,stroke:#dc2626,color:#000
style TV fill:#fef9c3,stroke:#ca8a04,color:#000,stroke-width:2px
style AUDIT fill:#f3f4f6,stroke:#9ca3af,color:#555,stroke-dasharray: 5 5
Vettra/
│
├── README.md # This file
├── FINDINGS.md # 4 ecosystem findings (hackathon deliverable)
├── LICENSE # MIT License
├── .gitignore # Python, Node, Rust, IDE, Docker
├── .env.example # Environment variables template
├── docker-compose.yml # PostgreSQL + backend containers
│
├── docs/
│ └── images/
│ ├── vettra_github_banner.svg # Animated SVG banner (README header)
│ ├── architecture_overview.png # Architecture diagram (high-res)
│ ├── tech_stack.png # Tech stack diagram (high-res)
│ ├── token_vault_flow.png # Token Vault credential flow (high-res)
│
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions: backend tests + frontend build check
│
├── backend/ # FastAPI Backend (Python)
│ ├── main.py # App entry point: wires all services
│ ├── config.py # Environment config + Auth0 settings
│ ├── database.py # PostgreSQL models + async SQLAlchemy
│ ├── requirements.txt # Python dependencies
│ ├── .env.example # Backend-specific env template
│ ├── Dockerfile # Container build
│ │
│ ├── app/
│ │ ├── models/
│ │ │ ├── action.py # ActionBundle, RiskAssessment, PolicyVerifyResult
│ │ │ ├── audit.py # AuditEntry, AuditChainStatus
│ │ │ └── session.py # Session, PreAuth, PolicyRule
│ │ │
│ │ ├── middleware/
│ │ │ └── interceptor.py # Core: intercept → parse → classify → route
│ │ │
│ │ ├── services/
│ │ │ ├── policy_engine.py # Policy evaluation via WASM bridge
│ │ │ ├── session_manager.py # Pre-authorization (Finding #2 workaround)
│ │ │ ├── audit_logger.py # Hash-chained SHA-256 log (Finding #3)
│ │ │ └── auth0_client.py # Token Vault + step-up auth client
│ │ │
│ │ ├── wasm/
│ │ │ └── bridge.py # Python ↔ WASM bridge (auto-fallback to Python)
│ │ │
│ │ ├── websocket/
│ │ │ ├── manager.py # WS connections, approval push, timeout
│ │ │ └── route.py # /ws/{user_id} endpoint
│ │ │
│ │ └── routes/
│ │ ├── actions.py # POST /api/actions/intercept, /approve, /pending
│ │ ├── approvals.py # Sessions, pre-auth, step-up trigger/verify
│ │ ├── audit.py # GET /api/audit/entries, /verify
│ │ └── inventory.py # Permissions CRUD + scope translation
│ │
│ └── tests/
│ └── test_vettra.py # Risk, audit chain, policy, parsing tests
│
├── frontend/ # React Dashboard (Vite)
│ ├── index.html # HTML entry point
│ ├── package.json # Node dependencies
│ ├── vercel.json # Vercel deployment config (API proxy → Cloud Run)
│ ├── vite.config.js # Vite config + API proxy
│ └── src/
│ ├── main.jsx # React root
│ ├── App.jsx # Sidebar nav + page router + WS status
│ ├── hooks/
│ │ └── useWebSocket.js # WebSocket hook with auto-reconnect
│ ├── pages/
│ │ ├── Overview.jsx # Stats: pending, chain status, sessions
│ │ ├── ApprovalQueue.jsx # Real-time approval cards + countdown
│ │ ├── AuditLog.jsx # Hash chain viewer + expandable entries
│ │ ├── Inventory.jsx # Scope visualizer + one-click revoke
│ │ └── ThreatSim.jsx # 5 test scenarios + results
│ ├── utils/
│ │ └── api.js # REST API client
│ └── styles/
│ └── global.css # Dark theme CSS
│
├── wasm-verifier/ # Rust WASM Policy Verifier (optional build)
│ ├── Cargo.toml # Rust dependencies
│ ├── README.md # Build instructions
│ └── src/
│ ├── lib.rs # WASM-exported entry points
│ ├── action.rs # ActionBundle schema + framework adapters
│ ├── risk.rs # Risk classification engine
│ ├── policy.rs # Policy verification + integrity check
│ └── audit.rs # Hash-chained audit entry signing
│
├── demo-agent/ # Demo Agent + @protect decorator
│ └── demo_agent.py # 5 escalation scenarios + prompt injection
│
└── scripts/
└── setup.sh # Automated setup script
CI/CD: GitHub Actions runs backend tests and frontend build checks on every push (
.github/workflows/ci.yml). Frontend auto-deploys to Vercel via GitHub integration. Backend deploys to Google Cloud Run viagcloud run deploy.
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | React 18 + Vite 5 | Approval dashboard, scope visualizer, audit viewer |
| Frontend Hosting | Vercel (Edge CDN) | React SPA with auto-deploy on push, /api/* proxy to Cloud Run |
| Backend | FastAPI + Python 3.12 | Policy engine, action interceptor, session manager |
| Backend Hosting | Google Cloud Run (asia-southeast2) | Serverless container with auto-scaling 0→3, managed HTTPS |
| Crypto | Rust → WebAssembly (wasmtime) | Policy verification, audit signing, tamper detection |
| Auth | Auth0 Token Vault | Credential isolation, scoped token issuance, step-up auth |
| Database | SQLite (demo) / PostgreSQL 16 (production) | Actions, audit log, sessions, permission inventory |
| Real-time | WebSocket (native, via Cloud Run) | Live approval push, step-up challenge delivery |
| Demo | Python + @protect decorator |
5 escalation scenarios + prompt injection defense |
| Container | Docker Compose | PostgreSQL + backend orchestration (local dev) |
| CI/CD | GitHub Actions | Automated backend tests + frontend build checks |
Before running Vettra, make sure you have these installed:
| Tool | Version | Check | Install |
|---|---|---|---|
| Python | 3.10+ | python --version |
python.org |
| Node.js | 18+ | node --version |
nodejs.org |
| npm | 9+ | npm --version |
Comes with Node.js |
| Docker | 20+ | docker --version |
docker.com |
| Git | 2.0+ | git --version |
git-scm.com |
| Rust (optional) | 1.75+ | rustc --version |
rustup.rs |
Note: Rust is only needed if you want to compile the WASM verifier from source. Without Rust, the backend automatically uses an identical Python fallback. All features work the same.
Open VS Code Terminal (Ctrl+`` or Cmd+`` ) and run:
git clone https://github.com/wiqilee/Vettra.git
cd Vettracp .env.example .envThen open .env in VS Code and fill in your Auth0 credentials:
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your_client_id
AUTH0_CLIENT_SECRET=your_client_secret
AUTH0_AUDIENCE=https://your-api-identifierGet Auth0 credentials: Sign up at auth0.com/signup, create a new API application, and copy the values.
python -c "import secrets; print('AUDIT_SIGNING_KEY=' + secrets.token_hex(32))"
python -c "import secrets; print('SESSION_SECRET=' + secrets.token_hex(32))"Copy the output and paste into your .env file, replacing the dev_... placeholder values.
docker compose up -d dbVerify it's running:
docker compose psYou should see vettra-db-1 with status healthy.
cd backend
pip install -r requirements.txtThis installs: FastAPI, SQLAlchemy, asyncpg, Auth0 SDK, wasmtime, cryptography, and 12 other packages.
Open a new terminal in VS Code (Ctrl+Shift+`` ):
cd frontend
npm installThis installs: React, React DOM, React Router, Vite, Lucide React icons.
You need 3 terminals in VS Code. Use Ctrl+Shift+`` to open new ones.
cd backend
uvicorn main:app --reloadYou should see:
══════════════════════════════════════════════════
VETTRA — Starting up
══════════════════════════════════════════════════
WASM: Python fallback
Auth0: your-tenant.auth0.com
VETTRA — Ready
══════════════════════════════════════════════════
cd frontend
npm run devYou should see:
VITE v5.4.3 ready in 300ms
➜ Local: http://localhost:3000/
cd demo-agent
pip install httpx
python demo_agent.pyThis runs 5 escalation scenarios through the interceptor and shows results in the dashboard.
| Service | URL | Description |
|---|---|---|
| Dashboard (Local) | http://localhost:3000 | Main interface for approval queue, audit logs, scope visualization, and threat simulation |
| Dashboard (Live) | https://vettra-eight.vercel.app | Production deployment on Vercel |
| API (Local) | http://localhost:8000/docs | Interactive Swagger UI (local development) |
| API (Live) | https://vettra-backend-810928102951.asia-southeast2.run.app/docs | Swagger UI on Cloud Run |
| Health (Live) | https://vettra-backend-810928102951.asia-southeast2.run.app/health | Backend health check |
| WebSocket (Local) | ws://localhost:8000/ws/{user_id} | Real-time approval and event stream |
| WebSocket (Live) | wss://vettra-backend-810928102951.asia-southeast2.run.app/ws/{user_id} | Production WebSocket |
cd backend
pytest tests/ -vExpected output:
tests/test_vettra.py::TestRiskClassification::test_read_is_low PASSED
tests/test_vettra.py::TestRiskClassification::test_financial_is_high PASSED
tests/test_vettra.py::TestRiskClassification::test_write_reversible_is_medium PASSED
tests/test_vettra.py::TestRiskClassification::test_delete_irreversible_is_high PASSED
tests/test_vettra.py::TestRiskClassification::test_deterministic PASSED
tests/test_vettra.py::TestActionParsing::test_gmail_send PASSED
tests/test_vettra.py::TestActionParsing::test_delete PASSED
tests/test_vettra.py::TestActionParsing::test_financial PASSED
tests/test_vettra.py::TestAuditChain::test_create_entry PASSED
tests/test_vettra.py::TestAuditChain::test_chain_valid PASSED
tests/test_vettra.py::TestAuditChain::test_chain_broken PASSED
tests/test_vettra.py::TestPolicyVerification::test_read_allowed PASSED
tests/test_vettra.py::TestPolicyVerification::test_delete_denied PASSED
tests/test_vettra.py::TestPolicyVerification::test_tampered_rejected PASSED
Once the backend is running, open http://localhost:8000/docs for the interactive Swagger UI where you can test every endpoint directly.
Live API docs: https://vettra-backend-810928102951.asia-southeast2.run.app/docs
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/actions/intercept |
Submit an agent tool call for interception |
POST |
/api/actions/approve |
Approve or deny a pending action |
GET |
/api/actions/pending |
List all pending actions |
GET |
/api/actions/{action_id} |
Get details of a specific action |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/approvals/sessions |
Create a new agent session |
POST |
/api/approvals/sessions/pre-authorize |
Set risk tier pre-authorization |
GET |
/api/approvals/sessions |
List active sessions |
GET |
/api/approvals/sessions/{id} |
Get session details |
DELETE |
/api/approvals/sessions/{id} |
Revoke a session |
POST |
/api/approvals/stepup/trigger |
Trigger step-up auth challenge |
POST |
/api/approvals/stepup/verify |
Verify step-up auth code |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/audit/entries |
Get audit entries (paginated, filterable) |
GET |
/api/audit/verify |
Verify audit chain integrity |
GET |
/api/audit/action/{id} |
Get full audit trail for an action |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/inventory/permissions |
List all active permissions |
POST |
/api/inventory/permissions |
Grant a permission |
DELETE |
/api/inventory/permissions/{id} |
Revoke a permission |
GET |
/api/inventory/scope-translate |
Translate scope strings to plain language |
| Endpoint | Description |
|---|---|
ws://localhost:8000/ws/{user_id} |
Real-time approval channel (local development) |
wss://vettra-backend-810928102951.asia-southeast2.run.app/ws/{user_id} |
Secure real-time channel (production on Cloud Run) |
GET /ws/status |
WebSocket connection status endpoint |
Every medium and high-risk action appears in the dashboard with full context: what the agent wants to do, which resource it targets, the risk score, and a countdown timer. Approve or deny with one click.
Signed policy bundles are verified in a Rust WASM sandbox before any action is evaluated. Tampered policies are rejected before they can influence decisions.
Every action is scored 0-100 across four factors:
| Factor | Weight | Example |
|---|---|---|
| Action type | 0-40 | read=0, write=20, delete=35, financial/auth=40 |
| Reversibility | 0-25 | reversible=0, unknown=15, irreversible=25 |
| Blast radius | 0-20 | single=0, multiple=12, system=20 |
| Sensitivity | 0-15 | public=0, internal=5, confidential=10, regulated=15 |
Score thresholds: LOW (0-19) → auto-approve | MEDIUM (20-54) → human approval | HIGH (55-100) → step-up auth + approval
Override rules: Financial and Auth actions are always HIGH regardless of score.
Every entry is SHA-256 hash-chained to the previous entry and HMAC-signed by a deployment key stored in Auth0. Retroactive tampering invalidates all subsequent entries and triggers a dashboard alert.
OAuth scope strings are translated to human-readable descriptions. Users see "Send emails on your behalf" instead of https://www.googleapis.com/auth/gmail.modify.
Users can set "approve all MEDIUM actions for 30 minutes" with a single MFA check, reducing step-up auth latency without sacrificing control. Addresses Finding #2.
The @protect decorator wraps any agent tool function to route it through Vettra's interceptor:
from demo_agent import protect
@protect(framework="langchain")
async def gmail_send_message(to, subject, body):
# This code only runs if Vettra approves
return send_email(to, subject, body)When the agent calls gmail_send_message(), Vettra intercepts it, classifies risk as MEDIUM (write + irreversible + email), and pushes an approval request to the dashboard. The function only executes if a human approves.
The dashboard includes a built-in Threat Simulation page. You can also run them from the terminal:
cd demo-agent
python demo_agent.py| # | Scenario | Risk | Expected Result |
|---|---|---|---|
| 1 | Read user profile | LOW | ✅ Auto-approved |
| 2 | Send email | MEDIUM | ⏳ Queued for approval |
| 3 | Delete GitHub repository | HIGH | 🔐 Step-up auth required |
| 4 | Charge $500 via Stripe | HIGH | 🔐 Step-up auth required (financial) |
| 5 | Grant admin permissions | HIGH | ⛔ Denied by policy (auth escalation) |
The backend works without Rust. It automatically uses a Python fallback with identical logic. To compile the Rust WASM module for production:
# Install Rust (if not installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
# Build
cd wasm-verifier
cargo build --target wasm32-unknown-unknown --release
# Run Rust tests
cargo testThe compiled module appears at wasm-verifier/target/wasm32-unknown-unknown/release/vettra_verifier.wasm. The backend auto-detects it on startup.
Run the full stack with Docker:
# Start everything
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -f backend
# Stop
docker compose down| Service | Port | Description |
|---|---|---|
db |
5432 | PostgreSQL 16 |
backend |
8000 | FastAPI + WebSocket |
Frontend is served separately via
npm run devor deployed to Vercel.
Vettra runs as a split deployment: frontend on Vercel, backend on Google Cloud Run.
flowchart TD
GH["GitHub<br/>(push to main)"] -->|auto-deploy| VERCEL
GH -->|manual deploy| CLOUDRUN
subgraph VERCEL["Vercel (auto-deploy frontend)"]
FE["vettra-eight.vercel.app<br/>/api/* rewrites to Cloud Run"]
end
subgraph CLOUDRUN["Google Cloud Run (manual deploy)"]
BE["vettra-backend-810928102951<br/>asia-southeast2.run.app"]
FEATURES["FastAPI + WASM/Python fallback<br/>SQLite (demo) · Cloud SQL (prod)<br/>Auto-scaling 0→3 · 512Mi<br/>Managed HTTPS + TLS"]
end
subgraph AUTH0["Auth0"]
TV["Token Vault Integration"]
end
BE --> FEATURES
BE -->|SDK| TV
- Connect your GitHub repository to Vercel
- Set root directory to
frontend - Vercel auto-detects Vite and deploys on every push to
main
cd backend
gcloud run deploy vettra-backend \
--source . \
--region asia-southeast2 \
--platform managed \
--allow-unauthenticated \
--set-env-vars "APP_NAME=Vettra,ENVIRONMENT=production,PORT=8080" \
--set-env-vars "DATABASE_URL=sqlite+aiosqlite:///./vettra.db" \
--set-env-vars "CORS_ORIGINS=https://vettra-eight.vercel.app" \
--min-instances 0 \
--max-instances 3 \
--memory 512MiAfter deploying, update frontend/vercel.json with the Cloud Run URL to connect frontend to backend.
Important: The Cloud Run region is
asia-southeast2(Jakarta). Make surevercel.jsonrewrites point to the correct Cloud Run URL including the region.
Vettra is not a toy. The demo uses mock data to show the authorization flow, but the architecture is designed for real-world deployment. No code changes are required to go from demo to production, only configuration.
The demo agent (demo_agent.py) simulates 5 escalation scenarios to demonstrate Vettra's risk classification, human-in-the-loop approval, and audit trail. All data flows through the same interceptor, policy engine, and WebSocket pipeline that a production agent would use.
To connect Vettra to real external services (Gmail, GitHub, Stripe, Slack), you need one thing: Auth0 Token Vault.
# This is the only missing piece for production
AUTH0_TOKEN_VAULT_URL=https://your-tenant.auth0.com/oauth/tokenToken Vault is not a separate service. It is a built-in Auth0 feature. To enable it:
- Go to Auth0 Dashboard → Applications → Your App → Advanced Settings → Grant Types
- Enable: Token Vault, Refresh Token, Authorization Code
- Go to Authentication → Social and connect providers (Google, GitHub, etc.)
- Set
AUTH0_TOKEN_VAULT_URL=https://your-tenant.auth0.com/oauth/tokenin your.env
Vettra uses two Auth0 applications, each serving a distinct role in the authorization pipeline:
| Application | Type | Purpose |
|---|---|---|
| Vettra Frontend | Single Page Application (SPA) | User login and session management on the React dashboard |
| Vettra Backend | Regular Web Application (confidential client) | Token Vault integration via OAuth 2.0 Token Exchange (RFC 8693) |
Token Vault requires a confidential client with a valid client secret to perform token exchange. SPA applications are public clients and cannot enable the Token Vault grant type. The Regular Web Application provides the client credentials needed for the backend to request scoped, time-limited tokens from Token Vault on behalf of the user.
The frontend .env uses the SPA application's client ID. The backend .env uses the Regular Web Application's client ID and client secret. This separation ensures that sensitive credentials never reach the browser while maintaining a seamless user experience.
Once configured, the flow changes from:
Demo: Agent → Vettra intercepts → mock approval → mock execution
Prod: Agent → Vettra intercepts → real approval → Token Vault issues scoped credential → real API call
The agent code stays identical. The @protect decorator works the same way. The only difference is that Token Vault now issues real, scoped, time-limited OAuth tokens instead of mock responses.
Most hackathon projects are demos that cannot scale. Vettra is a middleware layer with a clear production path: swap mock mode for Token Vault credentials, deploy backend to Cloud Run, and every LangChain/LlamaIndex agent in your stack gets real-time authorization for free.
Security Model: All credentials live in Auth0 Token Vault. The Rust WASM verifier enforces policy integrity before any token is released. Step-up authentication protects high-risk approvals. Scope is always minimum-necessary and time-bounded.
User Control: The dashboard gives users a complete real-time view of what their agent is doing, what it wants to do, and what it has already done. Every permission grant is explicit, reversible, and logged.
Technical Execution: Production-aware patterns throughout: hash-chained audit logs, WASM-sandboxed policy verification, WebSocket-based real-time approval, scoped token issuance. The Rust WASM integration is not a novelty; it is the trust anchor for the entire system.
Design: The approval UI is designed for speed and clarity under pressure. A user should be able to read an approval request, understand exactly what will happen, and make a confident decision in under 10 seconds. The scope visualizer exists specifically to make that possible.
Potential Impact: Every team building AI agents faces this problem today. Vettra is designed as a drop-in middleware layer that works with any LangChain-compatible agent without requiring changes to existing agent code. The target community is large, the problem is urgent, and the solution is deployable.
Insight Value: Vettra surfaces four concrete gaps in the current Auth0 and agent ecosystem (fully documented in FINDINGS.md): (1) there is no standard action bundle format for agent authorization requests; (2) step-up auth flows are not designed for the latency requirements of real-time agent approval; (3) audit log integrity is an unsolved problem in every current agent framework; (4) the authorization UX problem is harder than the security problem. These findings are built into the architecture and demonstrated live in the threat simulation.
I gave an AI agent access to my email. Then I tried to figure out what I had agreed to.
I was testing a LangChain agent that could read my Gmail, send messages, and manage my calendar. Standard productivity assistant. Then I paused and asked myself: what can this agent actually do right now? It could read every email in my inbox. It could send emails to anyone, from my address, with any content. It could delete emails. It could forward sensitive threads to an external API. All of this was within the scope I had granted. I had not approved any of that.
I went looking for a way to scope this down. LangChain has no permission model. OpenAI function calling has no concept of scope. Every major agent framework treats tool access as binary: the agent either has a tool or it does not. No conditional access. No approval flow. No audit trail.
That felt like a problem worth solving. So I spent ten days building Vettra.
The full blog post covers:
- Why every agent framework leaks credentials by design, and how Token Vault breaks that chain
- The five-layer architecture: interceptor, risk engine, WASM verifier, approval dashboard, and hash-chained audit log
- How Token Vault's OAuth 2.0 Token Exchange (RFC 8693) replaces static API keys with scoped, time-limited credentials
- The complete infrastructure: React + Vercel on the frontend, FastAPI + Google Cloud Run on the backend, Rust WASM for cryptographic integrity
- Five threat simulation scenarios that demonstrate every risk tier and authorization flow
- Four ecosystem findings that surface real gaps in agent authorization (no standard action bundle format, step-up auth latency, unsolved audit integrity, and the UX problem that turned out to be harder than the security problem)
- Concrete recommendations for Auth0 and the broader agent community
Read the full post on Medium →
MIT License. See LICENSE for details.
Wiqi Lee | Full Stack Engineer
Built for the Authorized to Act: AI Agents with Auth0 Hackathon 2026 organized by Auth0, Okta and Devpost. Powered by Auth0 Token Vault, FastAPI, React, Google Cloud Run, and a Rust WASM module that takes policy integrity seriously.












