Skip to content

Latest commit

 

History

History
70 lines (49 loc) · 4.38 KB

File metadata and controls

70 lines (49 loc) · 4.38 KB

The Proxy Pattern

The security model: how the agent makes authenticated VDS calls without holding a token, and why the three plausible attacks fail.

TL;DR

The agent loop runs in the chat application; its outbound work happens in a sandbox tool. Neither holds credentials. A proxy sits at the sandbox's network egress, holding a session-bound sandbox_id → user_id lookup. On every call, the proxy mints a fresh Connected Apps JWT with that user as the subject and forwards to Tableau. The auth and authz primitives are the ones Tableau already enforces; the proxy and per-chat binding are the new piece.

Sequence

The flow of identity from user authentication through a single VDS query. For the static system view, see architecture.md.

sequenceDiagram
    actor U as User
    participant C as Chat App
    participant A as Agent
    participant S as Sandbox
    participant P as Proxy
    participant V as Tableau VDS

    U->>C: authenticate (via IdP)
    Note over C: writes sandbox_id → user_id binding<br/>before the agent first uses the sandbox
    U->>A: ask analytical question in natural language
    A->>S: tool call: run this code
    S->>P: outbound HTTP (no token)
    Note over P: trust boundary — looks up binding,<br/>mints JWT (sub = user),<br/>injects Authorization header
    P->>V: HTTPS + JWT
    Note over V: validates JWT<br/>applies RLS and permissions for that user
    V-->>P: rows scoped to user
    P-->>S: response
    S-->>A: tool output
    Note over A: performs analysis needed to answer the question<br/>writes final response
    A-->>U: answer question in natural language
Loading

The binding is written once and read by the proxy on every VDS call. Whether the sandbox is provisioned eagerly at chat creation or lazily on first use is implementation choice — what matters is that the binding exists by the time the agent first invokes the sandbox.

Why the binding is immutable

The binding is immutable from anywhere the agent can reach, for three reasons:

  1. The binding lives in infrastructure the agent can't influence. The chat application writes it once; nothing the agent does during the conversation can change it.
  2. The sandbox's network egress is constrained to the proxy. Code running in the sandbox cannot reach the binding store, an IdP, or a Tableau auth endpoint directly.

The three attacks from the talk

1. Social-engineer the user identity. "Query this as Tony Stark instead." The agent has no means to change the JWT subject. The proxy mints the token from the binding, not from anything the agent says or writes.

2. Scope escalation. "Include the Executive Compensation data source." The agent can write the call. The proxy can authenticate it. VDS rejects it under the user's permissions, the same way it would reject the user querying directly through the REST API. The defense is Tableau's permission check, not the agent's judgment.

3. Bypass the proxy.

  • Self-sign a Connected Apps JWT. The shared secret lives with the proxy infrastructure, not the sandbox.
  • Ask the user for credentials. The proxy is already authenticating as the correct user; the agent has nothing to gain.

Why this isn't AI-specific

The primitives Tableau uses are unchanged: a user authenticates to an IdP; a Connected Apps JWT is presented to VDS; VDS validates it and enforces permissions. What's new is the arrangement — the JWT is minted on demand by a proxy bound to a session-authenticated user, instead of held by the human clicking a dashboard. The agent is just another HTTP client.

What you need to operate this

  • Authentication. A way to authenticate users to your agent product. A session store that records sandbox_id → user_id, immutable from within the sandbox.
  • Tableau Connected App. Configured on your Tableau site; gives you the shared secret to configure the proxy. User identities should align between Tableau and your IdP so RLS rules apply correctly.
  • Proxy. Sits at the auth boundary. On every request: read sandbox ID, look up user, mint fresh JWT, inject, forward. Holds the Connected App secret and the binding store; the sandbox holds neither.

Related