Service is a per-vault configuration that defines which upstream traffic agents can reach through the proxy and how Agent Vault authenticates to each one. When an agent makes a proxied request, Agent Vault matches the request against the vault’s services by host pattern (which may include an inline path glob like slack.com/api/*), attaches the configured credentials, and forwards the request. If no service matches, the request is forwarded as plain proxy traffic by default; vault admins can flip a vault into strict deny mode (unmatched_host_policy=deny in vault settings) to reject unmatched requests with 403 instead.
Each service contains:
- Name: A canonical per-vault identifier (slug, 3–64 lowercase alphanumeric/hyphen chars, no leading/trailing hyphen, no consecutive hyphens). Required on write for new services — pick a deliberate name like
stripe,slack-bot, orinternal-billing.namemay be omitted only whenhost+pathuniquely matches an existing service (the server adopts that service’s name, the same pattern as host-based delete). Legacy services persisted without a name before this contract are auto-slugified fromhost+pathon read so they remain addressable; the slug becomes durable on the next write. - Host: The single matcher field. Accepts a bare hostname (
api.stripe.com), a one-level wildcard (*.github.com), an inline path-scoped form (slack.com/api/*), or a port-scoped form (internal.corp.com:3000orinternal.corp.com:3000/api/*). When a port is included, the service matches only traffic to that port; without a port, the service matches any port. Reads and writes use the same shape: writes accept any of those forms; reads return the joined inline form so you see exactly what you wrote. - Auth config: How Agent Vault authenticates. Five types are supported:
bearer,basic,api-key,custom, andpassthrough(opt out of injection).
- Automatically: As agents do their work, they can raise proposals to add or modify services. You review each proposal, provide any required credentials, and approve. This is the recommended workflow for working with Agent Vault.
- Manually: You can set services directly via the CLI by applying a YAML file or by using the interactive builder. This can be useful for pre-configuring a vault before inviting agents.
Service structure
Services are defined in a YAML file. Here’s what a single service looks like:stripe-services.yaml
host tells Agent Vault which requests this service applies to. The auth block tells Agent Vault how to authenticate, referencing credential keys by name (not the actual credential values). The following section covers all five auth types.
Auth types
Every service must include anauth config that tells Agent Vault how to authenticate to the target host. The auth config references credential key names (not the actual credential values) stored in the vault.
Bearer
Attaches anAuthorization: Bearer <token> header. The token field references a credential key.
Basic
Attaches anAuthorization: Basic <base64> header using HTTP Basic authentication. The username field is required; password is optional (defaults to empty).
API key
Attaches a credential value to a named header with an optional prefix. Theheader field defaults to Authorization if omitted.
Custom
Freeform header templates with{{ SECRET }} placeholders. Each placeholder is resolved to the corresponding credential at proxy time. Use this when none of the typed auth methods fit.
Passthrough
Allowlists the host without injecting a credential. No credential is stored, read, or attached; client request headers (includingAuthorization and Cookie) flow through to the upstream — same as for credentialed services, just without a broker-injected auth header on top.
Header forwarding
Agent Vault forwards your client’s request headers to the upstream unchanged, except for:- Hop-by-hop headers per RFC 7230 (
Connection,Keep-Alive,Proxy-Authenticate,Proxy-Authorization,Proxy-Connection,Te,Trailer,Transfer-Encoding,Upgrade). - Broker-scoped headers that authenticate the client to Agent Vault itself (
X-Vault,Proxy-Authorization). - The auth slot — the specific header(s) the configured auth type manages. With
auth.type: bearerorbasic, the broker overridesAuthorization. Withapi-key, it overrides whatever you named inauth.header. Withcustom, it overrides every key inauth.headers. Withpassthrough, nothing.
anthropic-version, OpenAI-Beta, If-Match, and tracing IDs reach the upstream regardless of auth type. The broker only takes over the headers it’s responsible for injecting.
Substitutions
Auth types only inject HTTP headers. Some APIs want a credential value elsewhere — in the URL path, query string, request body, or even inside a WebSocket message. For those, declare asubstitutions block alongside the auth.
GET https://api.twilio.com/2010-04-01/Accounts/__account_sid__/Messages.json (routed through the broker via HTTPS_PROXY/HTTP_PROXY). Agent Vault:
- Resolves the basic auth credentials and injects
Authorization: Basic <base64(SID:TOKEN)>. - Scans only the path (the only surface in
in:), finds__account_sid__, replaces it with the URL-encoded SID. - Forwards the request.
Field reference
key— UPPER_SNAKE_CASE credential reference. Must resolve to a credential in the vault or a slot in the same proposal.placeholder— the exact wire string Agent Vault matches case-sensitively as a literal. Operators choose the string; the broker does not auto-wrap delimiters around it. The recommended convention is__name__(double-underscore-bounded snake_case).in— list of surfaces the broker is allowed to scan. Subset ofpath,query,header,body,websocket. Defaults to[path, query]if omitted.header,body, andwebsocketmust be explicit.
Scoping is the security boundary
Agent Vault only scans surfaces listed inin. If an agent embeds the placeholder in a non-declared surface, the literal string passes through to upstream — the broker will not substitute. There is no way to coerce the broker into substituting somewhere the operator did not authorize.
Use this property to pick a narrow in for strong secrets: keep in: [query] and the agent cannot exfiltrate the value through a body, header, or different surface.
Placeholder safety rules
The validator rejects placeholders that would cause false-positive substitutions in legitimate URL content:- Length ≥ 4 characters.
- Must contain a
__sequence or a non-[A-Za-z0-9_]character — so a bare word likeaccount_sidis rejected (it could appear as a real path segment), while__account_sid__,sid.value,sid-val, and~sid~all pass. - At least one alphanumeric character — strings made entirely of delimiters like
____or~~~~are rejected because they would aggressively match URL punctuation. - Only RFC 3986 unreserved characters
[A-Za-z0-9_-.~]are permitted, so the placeholder survives any HTTP client / SDK / middlebox without percent-encoding round-trips.
header scope is request-wide
When in includes header, the broker scans every outbound header for the placeholder — not one specific named header. Pick a unique placeholder so a typo or a copy-pasted value can’t accidentally land in a header you didn’t intend to rewrite. (Path and query are scoped to those URL components and don’t have this concern.)
body scope
When in includes body, the broker reads the HTTP request body, replaces placeholders, and updates Content-Length. Encoding is content-type-aware:
application/x-www-form-urlencoded— the replacement value is URL-encoded (matchingquerysurface behavior), so characters like&,=, and+in the credential don’t break form parsing.application/json— the replacement value is JSON-string-escaped, so"and\in the credential produce valid JSON.multipart/*— skipped (body returned unmodified). Raw replacement inside multipart boundaries could corrupt binary parts.- Compressed bodies (
Content-Encodingset) — skipped. - All other content types — raw replacement (no encoding).
body for OAuth client_secret_post flows where the secret is sent as a POST form parameter.
websocket scope
When in includes websocket, the broker scans client→server WebSocket text frames for placeholders and replaces them before forwarding to the upstream. Server→client frames are never modified.
Only unfragmented, uncompressed text frames are scanned. Binary frames, control frames (close, ping, pong), fragmented messages, and frames larger than 1 MB pass through unmodified.
When websocket is active, the broker strips Sec-WebSocket-Extensions from the upgrade handshake to prevent compression negotiation (which would make all frames opaque to the broker).
Use websocket for protocols like Discord Gateway where the auth token appears inside a JSON message body (the Identify and Resume events), not in HTTP headers.
Composes with all auth types
Substitutions and auth are independent. A service can use either, both, or neither:- Twilio:
auth: basic(header) + path substitution for the SID. - Legacy
?api_key=API:auth: api-keyfor an audit header + query substitution for the actual key. - A pure-substitution case (e.g. an internal API where the only secret lives in the path):
auth: passthrough+ path substitution. - Discord Gateway:
auth: passthrough+ header substitution for REST API + websocket substitution for Gateway token injection. - OAuth
client_secret_post:auth: passthrough+ body substitution for the client secret in the token exchange POST.
Matching rules
Thehost pattern is the single matcher knob. It can carry just a hostname (api.stripe.com), a wildcard host (*.github.com), an inline path glob (slack.com/api/*), or a port-scoped form (internal.corp.com:3000 or internal.corp.com:3000/api/*).
Host portion supports two modes:
- Exact match:
api.stripe.commatches onlyapi.stripe.com. - Wildcard:
*.github.commatchesapi.github.com,uploads.github.com, etc. The*replaces exactly one subdomain segment.
host:port): when present, the service matches only traffic to that port. When omitted, the service matches any port. Use this when multiple services run on the same host at different ports (e.g. internal.corp.com:3000 and internal.corp.com:4000).
Path portion (optional, follows the host or host:port with a /) is a URL glob. A bare host with no path matches any path; a non-empty path must start with / and may use * as a greedy glob (cross-/). **, ?, regex, and a bare * are rejected.
Matching priority
When twohost patterns could match a request, the winner is chosen deterministically:
- Host tier first. A pattern whose host portion is an exact match of the request host always beats one with a wildcard host (
*.example.com) — even when the wildcard rule has a more specific path or port. - Port specificity. Within the same host tier, a service with a specific port beats one without a port (any-port wildcard).
- Path specificity. Among rules with the same host tier and port specificity, the rule with the longest literal path prefix wins (characters in the path portion before the first
*). A bare-host pattern (no path) scores 0 (catch-all). - Declaration order on tie. If two rules tie on all the above, the rule appearing first in the configured service list wins.
| Request | slack-bot (slack.com/api/*) | slack-conn (slack.com/api/apps.connections.*) | Winner |
|---|---|---|---|
slack.com/api/apps.connections.open | matches, prefix length 5 | matches, prefix length 22 | slack-conn |
slack.com/api/chat.postMessage | matches | doesn’t match | slack-bot |
slack.com/oauth/v2/authorize | doesn’t match | doesn’t match | none |
- To override a wildcard-host rule for a specific subdomain, add the exact-host rule — it takes priority no matter what its path portion looks like.
- To layer two credentials on the same host, give them different path portions; the more specific path will win.
- The matcher does not run regex, does not match on method/headers/query/body, and does not bound
*at path segments. Patterns that tie under rule 2 (e.g.slack.com/api/*/v2andslack.com/api/*/v3) fall to declaration order.
Example
A vault can define multiple services with different auth types and path scopes:services.yaml
my-vault can now proxy requests to these hosts, and Agent Vault attaches the real credentials at request time.
Managing services
Set from a file
Set interactively
View current services
Clear all services
403 on every proxy request until new services are configured. Prompts for confirmation unless you pass --yes.
vault service remove, enable, and disable accept either the canonical service name or the bare host. When several path-scoped services share a host (e.g. Slack with slack.com/api/* and slack.com/api/apps.connections.*), passing the bare host returns 409 multiple services match host … with a candidates array of {name, host} (each host carries the joined inline form). Retry with the specific name shown in agent-vault vault service list.How proposals modify services
How proposals modify services
Agents don’t edit service configurations directly. Instead, they propose changes
through proposals, bundles of service and credential
modifications that a human reviews and approves.Each service in a proposal has an
action field:set: Idempotent upsert keyed by canonicalname(slug). Adds the service when no entry has that name, or replaces it when one does.nameis required for new services. It may be omitted only whenhost+pathuniquely matches an existing service — the server adopts that entry’s name (the same pattern as host-based delete), so an agent that knows the host but not the canonical name can still target an existing service.delete: Removes the service matching the givenname. Whennameis omitted, the bare host portion ofhostis used as a fallback — a unique host match is resolved automatically; a host shared by multiple path-scoped services returns409 multiple services match host …with acandidateslist, and the proposal must be retried with an explicitname.
name.This means agents can request access to new services without you manually
editing YAML files. You just review and click “Allow” in the browser
approval page.What happens when an agent hits an unknown host
What happens when an agent hits an unknown host
When a proxy request targets a host not covered by any service, Agent Vault returns
403
with a proposal_hint in the response body. The hint includes the denied
host and the endpoint to create a proposal.Well-behaved agents use this hint to automatically raise a
proposal requesting access. You receive a browser link
to review the request and provide any needed credentials.
