How It Works
Architecture
AgentSec is a Rust workspace with 5 crates:
| Crate | Purpose |
|---|---|
agentsec-core | Shared types, config schema, error types, ApprovalChannel trait |
agentsec-proxy | HTTP proxy service (axum) — the main runtime |
agentsec-bot | Telegram approval bot, implements ApprovalChannel |
agentsec-cli | CLI tool: init, add, status, logs, test |
agentsec-signer | OAuth 1.0a signing sidecar for Twitter/X |
Request Modes
AgentSec supports two ways for agents to reference credentials:
Unified Interface (Recommended)
The agent sends a credential name via the X-AgentSec-Credential header. The proxy handles all routing, auth header injection, and sidecar communication automatically.
POST /forward
X-AgentSec-Key: <agent-api-key>
X-AgentSec-Credential: slack
X-AgentSec-Target: https://slack.com/api/conversations.list
X-AgentSec-Method: GETThe agent never knows whether the credential is a simple API key or requires OAuth — the proxy abstracts that away.
Legacy Placeholder Mode
The agent uses <CREDENTIAL:name> placeholders in headers (and optionally in the request body). The proxy validates placeholder positions, requests approval, then substitutes real values before forwarding.
POST /forward
X-AgentSec-Key: <agent-api-key>
X-AgentSec-Target: https://api.openai.com/v1/chat/completions
Authorization: Bearer <CREDENTIAL:openai-key>
Content-Type: application/json
{"model": "gpt-4", "messages": [...]}Security note: Placeholders are only allowed in authentication-related positions (Authorization header, X-Api-Key header, and opt-in body fields like
token,api_key,access_token). Placeholders in non-auth positions (e.g., a tweet body or email subject) are rejected to prevent credential exfiltration through target APIs.
Connector Types
Each credential has a connector type that determines how the proxy routes requests:
Direct (default)
For API keys and bearer tokens. The proxy injects the credential value into the Authorization header and forwards directly to the target URL.
credentials:
slack:
description: "Slack API"
api_base: "https://slack.com/api"
# connector: direct (default, no need to specify)The auth header format defaults to Bearer {value} but is configurable:
credentials:
notion:
auth_header_format: "Bearer {value}" # default
custom-api:
auth_header_format: "token={value}" # custom formatSidecar
For OAuth, custom protocols, or APIs that need request transformation. The proxy routes through an intermediary service at api_base.
credentials:
twitter:
description: "Twitter API v2"
connector: sidecar
api_base: "http://oauth-signer:8080" # OAuth 1.0a sidecar
telegram:
description: "Telegram Bot API"
connector: sidecar
api_base: "http://telegram-client:8082"
relative_target: true # X-AgentSec-Target is a path, not a full URLWhen relative_target: true, the agent sends a path (e.g., /sendMessage) instead of a full URL, and the proxy prepends api_base.
Request Flow
Agent request
│
▼
Authentication (X-AgentSec-Key → HMAC-SHA256 hash lookup)
│
▼
Parse credential reference (unified header or placeholders)
│
▼
Whitelist check (is this credential allowed for this agent?)
│
▼
Policy evaluation (auto-approve URL? auto-approve method? require approval?)
│
├─ Auto-approved → skip to Forward
│
▼
Human approval (Telegram message with Approve/Deny buttons)
│
▼
Credential injection / placeholder substitution
│
▼
Forward to target API (direct or via sidecar)
│
▼
Response sanitization (scrub credential values from response body)
│
▼
Audit log (JSON line with request ID, agent, credential, status, latency)
│
▼
Return response to agentResponse Sanitization
Before returning API responses to the agent, the proxy scans for credential leakage:
- Exact match — scans for the literal credential value
- Base64 variant — scans for the base64-encoded credential
- URL-encoded variant — scans for the percent-encoded credential
Any matches are replaced with [REDACTED:credential-name]. Responses larger than 10 MB are passed through without scanning.
Trust Model
- Agents are untrusted — they authenticate with API keys but cannot access credential values, cannot bypass policy, and see sanitized responses
- The proxy is trusted — it holds encryption keys and credential values in memory
- The approval channel is semi-trusted — approvers see request details but not credential values (approval messages are scrubbed)