← All posts
18 November 2025

The Workspace as a Security Boundary

Access controls bolted onto shared databases are fragile. On why structural isolation — one file per user — is a more honest security model.

There's a bug class that doesn't show up in unit tests. It doesn't trigger alarms. It fails silently, and when it does fail, it fails for someone else — not the developer who wrote the code, and not the user who just logged in. The user who notices is the one who sees data that isn't theirs.

In March 2023, OpenAI had a nine-hour window during which roughly 1.2% of ChatGPT Plus users could see fragments of other users' chat histories, names, and partial payment details. Not through a hack. Through a bug in a shared infrastructure layer — a race condition in connection handling that briefly surfaced the wrong session data. The fix was fast. The implication took longer to absorb.

The problem with access controls bolted on

Most SaaS applications store all their users' data in a shared database. A single Postgres instance, a single schema, millions of rows tagged with a user_id or tenant_id column. Access control is then enforced at the application layer: every query includes a WHERE user_id = ? clause, and the assumption is that the clause will always be there, always be correct, and never be bypassed.

That assumption is almost always right. But "almost always" is a fragile guarantee for a personal knowledge base.

The failure modes are subtle. A developer adds a new reporting endpoint and forgets the tenant filter. A background job runs outside user context. A caching layer shares response data between requests that arrive at just the right moment. An ORM abstracts a join in a way that quietly drops the filter condition. None of these require an attacker. They're just code bugs in a system that's structurally vulnerable to code bugs.

Row-level security in Postgres helps, but it has its own edges. If the session context isn't set, RLS returns an empty result — not an error. The system fails silently. You might not notice for months.

What structural isolation actually looks like

The alternative is to put the security boundary at the data layer itself, not at the query layer. One SQLite file per user. Separate files. Separate inodes. The user's knowledge doesn't share a table with anyone else's; it lives in a completely distinct database file that the application opens only after authentication.

Turso — a hosted SQLite platform — has been building explicitly toward this model, and in 2024 shipped improvements to "database per tenant" architectures for production use. The appeal isn't complicated: when data is structurally separate, cross-tenant leakage becomes architecturally impossible rather than just unlikely. You can't accidentally read User B's data from User A's session, because User A's database file doesn't contain User B's data. The access control isn't enforced by a WHERE clause. It's enforced by the filesystem.

The tradeoff is real. Per-user databases cost more to operate, make cross-user analytics harder, and complicate migrations. For a consumer SaaS with millions of users and a shared reporting dashboard, a shared schema is probably the right call. But for a personal knowledge base — a system storing someone's notes, people, decisions, and preferences — the tradeoff flips. The data is personal. Privacy is the product. A cross-user leak isn't a minor incident; it's a fundamental violation of what the tool is supposed to be.

Why it matters when AI is involved

Add AI to this picture and the stakes get higher. A personal knowledge base with AI integration means the AI is reading your private notes, querying your contact records, updating your preferences. It's operating on genuinely sensitive information — the kind that's useful precisely because it's specific to you.

If that AI operates through a shared application layer, every query it makes passes through the same shared infrastructure. A bug in how the AI orchestrator builds its database query doesn't just expose a user to wrong information — it could expose their knowledge to another user's AI session. The vulnerability is the same; the information at risk is much worse.

With per-user SQLite files, the AI can only ever see what's in the file it's been authorized to open. There's no query to malform. No WHERE clause to drop. The scope of what the AI can access is determined by which file is opened, and that's determined by the authentication layer before any AI code runs at all.

This is Harbor's approach. When a workspace loads, the application opens that user's SQLite file — not a shared table filtered to a user_id. Every document, task, preference, and person record in that file belongs to them. An AI agent running against that workspace has structural access to their knowledge and structural inaccessibility to anyone else's.

It's a quieter guarantee than a permission system, but more reliable. Permission systems can have bugs. File handles don't.

The part that's easy to miss

There's a second benefit to per-user databases that has nothing to do with security. When your knowledge base is a single file, it's portable in a way that a filtered row in a shared table never is. You can copy it, back it up, inspect it in DB Browser for SQLite, migrate it to a self-hosted instance, or open it locally without any cloud API call.

That portability is itself a kind of isolation — not from other users, but from the platform. The data isn't trapped in a multi-tenant database that only the vendor can access. It's yours in a concrete, operational sense.

Most personal software doesn't work this way. Most personal software is a filtered view of a shared dataset with the word "your" in the UI. The workspace-as-a-file is a different model — one where the isolation is real, and the ownership isn't just a privacy policy.


Asgeir Albretsen is the founder of Harbor.

The Workspace as a Security Boundary: Harbor Blog | Harbor