Field Note · VTS-FN-002

Deploying Claude for your business: identity, isolation, and cost control

TL;DR An internal AI assistant is not a chatbot with a login screen bolted on — it is a system with access to your business, and it needs the same identity discipline as anything else with that access. The three decisions that matter most: identity must come from a source the browser cannot forge, sessions must be isolated per user with the server enforcing ownership (not the client), and API cost must be treated as a security control, not a billing afterthought. We built this stack for our own internal assistant, Aria, before ever proposing it to a client.

The mistake this note exists to prevent

The fastest way to build an internal AI assistant is to stand up a chat interface, ask the browser who's using it, and pass that identity straight through to the model and its tools. It works in a demo. It fails the first time someone edits a request in the browser's dev tools, because a client-supplied identity is a suggestion, not a fact — anything the browser sends, the browser (or the person using it) can also change.

This isn't a hypothetical for AI specifically; it's the oldest lesson in web application security, applied to a new kind of application. The difference with an AI assistant is what's behind it: mailboxes, tickets, documents, sometimes an agent with permission to take action. Get identity wrong here and the failure mode isn't a cosmetic bug — it's one user reading another user's conversation history, or worse.

Reference architecture

The stack we run for Aria, and the one we build for clients, has four layers. Each does one job, and none of them trusts the layer above it more than it has to.

LayerComponentJob
EdgeCaddyTLS termination, routing; forwards authenticated identity as a server-set header
IdentityAuthelia (SSO)Login, TOTP/WebAuthn two-factor, session cookie — the only place identity is decided
ApplicationNode.js API proxyHolds the Anthropic API key server-side; never sends it to the browser
PersistenceSQLite + FTS5Session history, scoped and queried per user, full-text searchable

The detail that makes this work is where identity actually comes from. Caddy, sitting behind Authelia, injects a header — commonly Remote-User — into every request it forwards to the application. That header is set by the edge, after successful authentication, and the application only ever trusts identity from that header, never from anything the browser includes in its own request body or cookies it can read. The browser doesn't get a vote on who it's allowed to be.

Two-factor is not optional here

Password-only access guards a chat window. It does not adequately guard a system that can read a mailbox, search a document store, or draft on someone's behalf. We deploy Authelia in front of the assistant specifically for TOTP and WebAuthn two-factor support, so a leaked password alone isn't enough to reach the tool — the same standard we'd insist on for VPN or email access, applied consistently rather than carved out an exception for "it's just an AI chat."

Session isolation: the part that's easy to get subtly wrong

Persistent, searchable session history is a real feature — people want to find a conversation from two weeks ago. It is also the most common place isolation quietly breaks, because the failure looks identical to success until the wrong person hits the wrong URL.

The pattern we use: every session row is stamped with the owning user's identity at creation, taken from the server-injected header, never from a client-supplied field. Every read, search, or delete against session data runs through an ownership check — call it ownsSession() — that compares the requester's identity against the row's owner and returns a plain 403 on any mismatch. No exceptions, no "admin can see everything" shortcut unless that's an explicit, separately authorized role. The check is boring on purpose. Boring is what you want in an access control path.

Cost control is a security control

This one surprises people. API usage costs money per request, which means an assistant with a leaked endpoint, a misbehaving integration, or a compromised session isn't just a data exposure risk — it's a direct financial one, and the two failure modes often arrive together. We treat spend monitoring the same way we treat intrusion detection: something is watching, and something alerts.

What this looks like end to end

A request from a logged-in user passes through Authelia (already satisfied for the session), arrives at Caddy carrying the server-set identity header, reaches the Node proxy which attaches the API key server-side and calls Claude, and any session read or write is checked against that same identity before touching SQLite. Nothing in that chain asks the browser to vouch for who it's talking to more than once, at the one point — login — where that question actually gets asked and answered under two-factor.

The lesson

None of this is exotic. It's the same identity, isolation, and monitoring discipline every well-run business system already needs — applied to a system that happens to be new. The mistake we see most often isn't a lack of security knowledge; it's treating "AI project" as a different category of software that gets a pass on the basics because it shipped fast. It doesn't need new rules. It needs the old ones, actually followed.

From the team that runs its own AI internally

Thinking about deploying an AI assistant in your business?

The AI Readiness & Security Assessment covers exactly this: identity, isolation, and whether your environment is ready.

Talk to VTS