How It Works

How It Works

Architecture

AgentSec is a Rust workspace with 5 crates:

CratePurpose
agentsec-coreShared types, config schema, error types, ApprovalChannel trait
agentsec-proxyHTTP proxy service (axum) — the main runtime
agentsec-botTelegram approval bot, implements ApprovalChannel
agentsec-cliCLI tool: init, add, status, logs, test
agentsec-signerOAuth 1.0a signing sidecar for Twitter/X

Request Modes

AgentSec supports two ways for agents to reference credentials:

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: GET

The 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 format

Sidecar

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 URL

When 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 agent

Response Sanitization

Before returning API responses to the agent, the proxy scans for credential leakage:

  1. Exact match — scans for the literal credential value
  2. Base64 variant — scans for the base64-encoded credential
  3. 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)