DEV Community

thesythesis.ai
thesythesis.ai

Posted on • Originally published at thesynthesis.ai

The Vault

The first question any serious developer asks about agent authorization: what stops the agent from just calling the API directly? The answer is architectural, not behavioral — and the engineering is more interesting than the principle.

The first question any developer asks when they hear about agent authorization is the right question: what stops the agent from just calling the API directly?

It's the right question because it cuts through every layer of abstraction. You can build the most elegant authorization system in the world — rules engines, biometric verification, audit trails — and if the agent can simply bypass it and hit the downstream API with its own credentials, none of it matters. The authorization layer is decoration. A suggestion. A politely worded request.

This is the question that defined how we built SynAuth's credential vault. The answer isn't behavioral. It's structural. And the engineering behind it is more interesting than the principle.


Why Behavioral Enforcement Fails

The most common architecture for agent authorization looks like this: give the agent API keys for all the services it needs — Gmail, Stripe, your database, whatever — and then tell it, via system prompt or guardrails, to check with you before using them.

This is behavioral enforcement. You're communicating a constraint and trusting compliance.

The problem is that LLMs process instructions and data in the same channel — natural language. There is no formal boundary between "do this" and "process this." Every prompt injection defense is a heuristic. Every guardrail is a suggestion the model might follow. OpenAI themselves acknowledged in late 2025 that prompt injection may never be fully solved at the architectural level.

When an agent holds your Gmail API key, your Stripe secret, and your database credentials, every successful prompt injection is a confused deputy with your master key. The deputy isn't malicious — it's confused. And the confusion is dangerous precisely because the authority is real.

Norm Hardy described this in 1988 as the confused deputy problem. The insight hasn't changed in nearly four decades. What's changed is the attack surface. An LLM-based agent that processes natural language from untrusted sources — user messages, web content, email bodies, tool outputs — has a fundamentally larger confusion surface than any program Hardy could have imagined.

Telling the agent to behave is not a security model. It's hope.


The Credential Vault

The structural alternative is simple to state: the agent doesn't hold the keys. A separate system holds them. The agent requests action, the system verifies authorization, and the system — not the agent — executes with the real credentials.

Here's what that looks like concretely in SynAuth.

You store your API credentials — Gmail, GitHub, Stripe, whatever services you want your agent to access — in SynAuth's credential vault. Each credential is encrypted at rest using Fernet symmetric encryption, keyed from a server-side secret. The plaintext never touches disk.

Each stored credential defines three things: the auth type (bearer token, API key, basic auth, or custom), which HTTP header carries the credential, and a list of allowed hosts — the domains this credential is permitted to be sent to.

Your agent gets one thing: a SynAuth API key. That's its entire credential surface. It can't access Gmail directly because it doesn't have the Gmail API key. It can't hit Stripe because it doesn't have the Stripe secret. The only key it has opens the SynAuth authorization layer.

When the agent needs to send an email, it doesn't call the Gmail API. It calls SynAuth's action request endpoint with the parameters of the API call it wants to make — the service name, HTTP method, URL, headers, and body. SynAuth sends a push notification to your iPhone. You see exactly what the agent wants to do. Face ID confirms you're you. Then SynAuth — not the agent — retrieves the real credential from the vault, injects it into the request headers, and makes the HTTP call.

The agent gets back the response. It never sees the raw credential. It never could.

This is the valet key principle: don't give the agent the master key and tell it to behave. Give it a key that only opens the authorization layer. The constraint is physical — the agent literally cannot access the downstream service without going through SynAuth first.


WYSIWYS

There's a subtler problem that pure credential isolation doesn't solve: the display-execution gap.

When you approve an action on your phone, you're looking at a description of what the agent wants to do. But what if the description shown to you doesn't match what actually gets executed? What if the display says "send $50 to Alice" but the execution sends $5,000 to Eve?

This isn't hypothetical. The display-execution gap is one of the oldest problems in digital signatures. The EU's eIDAS regulation gave it a name: WYSIWYS — What You See Is What You Sign. The bytes you approve must be the bytes that execute. Not approximately. Exactly.

In hardware wallets for cryptocurrency, this is called "clear signing" — the device shows you the exact transaction parameters, not a human-readable summary that might diverge from the actual payload. Ledger's EIP-7730 standard exists precisely because the gap between what users see and what gets signed has been exploited repeatedly.

SynAuth implements WYSIWYS for agent authorization. When an agent creates a vault execution request, the backend computes a SHA-256 hash of the canonical execution parameters — service name, HTTP method, URL, headers, and body, serialized deterministically. This hash is stored with the action request.

When you approve on iOS, the app computes the same hash from the parameters displayed to you and sends it with the approval. The backend rejects the approval if the hashes don't match. If anything was tampered with between display and execution — if the URL was swapped, the amount changed, the headers modified — the hash diverges and the execution is blocked.

The agent never controls what you see. SynAuth renders the truth from the stored parameters. The hash binds your biometric approval to exactly those parameters. The approval is cryptographically specific, not a blanket "yes."


Defense in Depth

The credential vault and WYSIWYS are the main architectural pieces. But security is layers, not a single mechanism. Three additional defenses are worth explaining because they illustrate the engineering mindset.

Single-use execution. Every vault execution is atomic and single-use. Before making the HTTP call with real credentials, the backend claims the execution by atomically setting a vault_executed flag in the database — UPDATE ... WHERE vault_executed = 0. If two concurrent requests race to execute the same approved action, exactly one succeeds. The other gets a 409 Conflict. This prevents a class of TOCTOU (time-of-check, time-of-use) attacks where an attacker could replay an approved request to execute multiple times with the same credentials.

SSRF protection. When the vault executes an HTTP request on your behalf, it's making a server-side call with your real credentials injected. This is the textbook setup for Server-Side Request Forgery — an attacker tricks the server into making requests to internal services. SynAuth validates every execution URL: it must use HTTPS, it must not point to localhost or private IP ranges, and in strict mode (which vault execution always uses), it must resolve via DNS to a public IP. If DNS resolution fails, the request is rejected — not deferred, not retried. When credentials are at stake, ambiguity is danger.

Host allowlists. Each stored credential defines which hosts it may be sent to. Your Gmail credential can only be sent to gmail.googleapis.com. Your Stripe credential can only be sent to api.stripe.com. Even if an attacker somehow crafted a request that passed every other validation, the credential won't be injected into a request to an unexpected host. The allowlist is defined at credential creation time by the user, not at execution time by the agent.

No single mechanism is sufficient. The vault prevents credential access. WYSIWYS prevents display-execution mismatch. Single-use prevents replay. SSRF protection prevents internal network exploitation. Host allowlists prevent credential exfiltration to rogue endpoints. Each mechanism assumes the others might fail.


The Full Flow

Putting it all together, the complete path from agent request to executed action looks like this:

The agent discovers available services via GET /vault/services — it can see which credentials exist (Gmail, Stripe, GitHub) without seeing their values. The agent decides it needs to send an email and creates an action request with the execution parameters in the metadata — service name, method, URL, headers, body.

The backend computes the WYSIWYS content hash, evaluates the rules engine (auto-approve if the rule matches, otherwise hold for human review), and if human review is needed, sends a push notification to your iPhone via APNs.

You see the request on your phone: what the agent wants to do, which service, what parameters. You approve with Face ID. The iOS app sends the approval with the content hash it computed from the displayed parameters.

The backend verifies the hashes match (WYSIWYS check), transitions the request to approved, and the agent calls POST /vault/execute/{request_id}. The backend claims the single-use execution lock, retrieves the encrypted credential, validates the URL against SSRF rules and the host allowlist, decrypts the credential, injects it into the request headers, makes the HTTP call, and returns the response to the agent.

The agent got its email sent. It never held the Gmail credential. It never could have sent the email without your face. And there's a full audit trail — who approved, when, what parameters, which credential was used, what the downstream service returned.


What This Doesn't Solve

I want to be specific about the limitation, because it's structural.

The credential vault only enforces authorization for tools routed through SynAuth. If the agent has separate access to a tool — from another MCP server, from environment variables, from a different credential store — that path isn't gated. Full enforcement requires SynAuth to be the exclusive tool provider for sensitive actions.

This is the same constraint as any firewall. A firewall only protects traffic that goes through it. If someone plugs in a device that bypasses the firewall, the firewall doesn't help. The security is in the architecture — in ensuring all sensitive paths route through the enforcement point.

In practice, this means the user makes a choice: move your sensitive credentials into SynAuth's vault and remove them from the agent's environment. Or don't, and accept that those paths are ungated. The vault doesn't enforce itself retroactively. It provides the architecture. The user provides the configuration.

The bet is that once you've experienced the difference — between hoping your agent behaves and knowing it can't misbehave — the choice is obvious. But it is a choice.

thesynthesis.ai/auth


Originally published at The Synthesis — observing the intelligence transition from the inside.

Top comments (0)