Back Built with Neko

Curiosity Workspace

Five layers between a question and your data

How applications built on Curiosity Workspace keep an AI assistant useful without letting it become the weakest link. Drawn from docs.curiosity.ai.

The LLM never decides what a user is allowed to read. The graph and search engines decide; the model gets a filtered view.

The problem

A single control is a single point of failure

Most RAG failures are not model failures. They are a missing boundary somewhere between the source system and the answer.

Leaked retrieval
A vector store built without ACLs returns the CFO's deck to a contractor, ranked first.
Over-scoped tools
An action tool written for admins is advertised to every user in the tool list.
Injected instructions
A poisoned document tells the assistant to ignore its prompt, and nothing inspects the conversation.
Silent egress
Content is embedded and shipped to a provider nobody signed a data agreement with.
No trail
Something went wrong last Tuesday and there is no record of which tool ran, for whom, or on what.

The shape

Each layer assumes the one above it failed

A question travels inward through every boundary. An answer travels back out carrying citations. Nothing crosses a layer on trust alone.

Layer 1 · Ground

Identity, deployment, and the keys

Curiosity runs on infrastructure you operate, so the perimeter is yours to set. The product supplies the controls; the deployment decides how they are turned on.

Sign-in
Local accounts with TOTP, or SSO over OIDC and SAML 2.0 — Entra ID, Okta, Auth0, Google, Cognito, OneLogin. Require SSO with MFA for anyone holding admin rights.
In transit
TLS terminated in front, HSTS and HTTPS redirect on. The workspace binds to the proxy network, never a public interface.
At rest
AES-256 over graph content when MSK_GRAPH_MASTER_KEY is set. Off by default and not autogenerated — set it, and back it up.
Secrets
Anything your endpoints and tools call out with lives in workspace secrets: write-only, never returned to the UI or an export, and every read is audited.
Admin change control
MSK_ADMIN_READ_ONLY separates holding an admin login from being able to reconfigure the workspace. Unlocks are time-boxed and recorded.

Layer 2 · Access control

Permissions live in the graph, not in your app code

Curiosity models access as relationships — ReBAC — using the same graph that holds the content. There is no second system to keep in sync.

What gets modeled

  • _User — one per person, created on first sign-in.
  • _AccessGroup — a team, shown as Team in the UI.
  • _MemberOf / _HasMember — user to team, both directions.
  • _Owns / _OwnedBy — owner to resource, both directions.
  • _AccessGroup.Public — visible to every signed-in user.

Where the ACL comes from

  • Connectors attach access while writing, mapping each source system's own permissions onto teams.
  • RestrictAccessToTeam, RestrictAccessToUser and MarkFileAsPrivate maintain the edge pairs and update the index.
  • SSO group claims map to teams on every sign-in, so a removal in the IdP propagates at next login.

Layer 2 · Enforcement

Four enforcement points, one source of truth

Graph engine
Fetching a node or traversing to a neighbor is implicitly filtered: is there a path from this user to this resource? If not, it is dropped. Application code does not check.
Search engine
ACLs are denormalized into the index at write time. Every query is augmented with a filter over the user's group UIDs — so permission-aware retrieval costs nothing after ranking.
Custom endpoints
CreateSearchAsUserAsync(req, CurrentUser, ct) runs in the caller's context. The variant without AsUser runs as the system and ignores ACLs — reserved for audit, backfill and sync.
Custom AI tools
The same rule, one level in: retrieval goes through scope.Graph.CreateSearchAsUserAsync(search, scope.CurrentUser, ct), and a mutating tool checks scope.Graph.CanUserSeeAsync before it writes. A tool that reaches for the system context bypasses every ACL above it.

A UID list handed over by a client is data, not an access grant. Implicit filtering covers traversal, not "these are already on screen" — re-check each one with Graph.CanAccess.

Layer 3 · Retrieval

The model is handed evidence, not a database

Three things happen on every chat turn, and none of them are the model's decision.

Tools, not access
The orchestrator advertises only the tools this user may call. The model never touches the graph or the search index directly.
Retrieval as the user
Content the person cannot open never enters the context window. The user's identity travels with the tool call, not just with the interface that made it.
Everything cited
Each retrieved chunk is registered with scope.AddSnippet and the prompt instructs the model to cite the returned IDs. Unregistered claims have nowhere to hide.
Search as the user
CreateSearchAsUserAsync(search, scope.CurrentUser, ct) is the call every retrieval tool makes. The same request can still carry search.TargetUIDs to bind it to a graph traversal — this product, this customer, this programme — with permissions applied on top.

Layer 3 · Tools

A tool is C# with a contract, not a prompt

Business rules belong in tool code where they can be tested, reviewed and audited — not in a system prompt where they can be argued with.

The runtime narrows the surface

  • Admin-only tools never appear for non-admin users.
  • Tools can be tagged to a chat surface — support chat is not ops chat.
  • Full listings with tool source and model-facing descriptions are admin-only; the composer reads a display-name view.
  • Per-call timeouts, a cap on tool calls per turn, and the user's cancellation token bound every run.
  • Overlapping tools make selection unreliable — consolidate them, or make the descriptions sharply distinct.

Rules for action tools

  • Validate what the model passes in — assume it may be hostile or malformed.
  • Check visibility explicitly before mutating, and log caller and target.
  • Destructive work goes propose → approve → execute, gated on a person — see the next slide.
  • Read credentials at the point of use, and never let a secret reach the tool's return value — that goes straight back to the model.

Layer 3 · Destructive actions

A gate outside the conversation

Anything that deletes, archives or changes state in bulk goes through a person. The workspace ships no approval UI for custom tools — this is a pattern your application implements, modelled on the one Sudo uses for workspace configuration.

The model proposes. A person disposes. The tool that executes checks the approval, not the conversation.

Layer 4 · Guardrails

Policies judge the conversation itself

A chat policy is an administrator's rule that inspects a conversation and decides whether it proceeds. Two kinds ship with the workspace.

Forbidden keywords
A written list, matched case- and whitespace-insensitively, with no model call. New workspaces are seeded with a prompt-injection list set to flag rather than reject.
Local safety classifier
Mistral Shieldstral 1.0, a 3B model running on the workspace server. You write the question and the evaluation context; the probability of "yes" is the score. Nothing is sent to a provider.
When it runs
Before the chat — the only moment a rejection actually blocks an answer. After the chat, when the reply is what you are judging. Or batched while the workspace is idle.
What a match does
flag conversation continues, verdict recorded, reviewer notified. reject the turn is refused as the assistant's own answer, with the reason you wrote.

Layer 4 · Honest limits

What guardrails are not

A policy cannot make a document readable or unreadable. It is not a substitute for permissions on the graph.

Use policies for the layer above access control: what may be asked, and what should be reviewed.

Layer 5 · The model

Choosing the model is a security decision

Twenty providers are configurable, with a per-model enable toggle. The enabled set is the entire menu available to chats and agents — a model left off disappears from both.

Aligned by default
Frontier providers — Anthropic, OpenAI, Google, Mistral — ship safety training in the model itself. That work is real, and it is the innermost layer, not the outer one.
Residency
Regional Azure deployments for EU/US/JP. Ollama, vLLM or any OpenAI-compatible server for air-gapped operation.
Keep the corpus home
Embeddings are configured separately. Built-in CPU models — MiniLM, Arctic XS, Harrier — need no external network, so a hosted chat model can pair with local embeddings and most text never leaves.
Hard caps
Max output tokens, per-call timeout, max tool calls per turn. A fallback provider takes over on 5xx, and the workspace degrades to text-only retrieval if embeddings are unreachable.

Through every layer

The trail is the fifth control

Depth only helps if you can see which layer caught what.

Recorded

  • Every tool call: name, chat, user, duration, result size, and whether it failed or was cancelled.
  • Every chat turn: prompt, tool calls, results, answer, citations.
  • Admin actions, written before the action runs.
  • Every read of a workspace secret, with the calling endpoint or tool.
  • Team membership and ACL changes.

Deliberately not recorded

  • Tool arguments and results carry workspace content, so the default lines record only their size.
  • Secret values never appear in an audit entry.
  • Policy evaluations that passed.

Forward the audit log to your SIEM. Per-tool counts, latency and error rates are exposed for monitoring.

Why depth

Each layer covers the one next to it

If a connector mis-maps an ACL
The search filter still applies whatever ACL was recorded, and the audit log shows what changed and when.
If a tool is written carelessly
ReBAC still bounds what that tool can retrieve, and the tool never appears for users outside its audience.
If a document carries an injection
Permissions already bounded what could be reached, policies flag the conversation, and the model's own training resists the instruction.
If a policy misses
The model was picked for alignment, the tool surface is narrow, and destructive actions still need a human confirmation.
If the model misbehaves
It never held the credentials, never saw restricted content, and could only act through tools it was granted.

Before you ship

The checklist

  • Access — every user-facing retrieval path uses the AsUser variant. No client-supplied UID is trusted without a re-check.
  • Ingestion — every connector maps source permissions onto teams. "Public" is a decision, not a default you inherited.
  • Tools — one clear intent each, bounded result sets, admin tools marked, destructive actions staged.
  • Policies — at least one enabled, reviewed in the evaluations tab rather than assumed to be working.
  • Models — only the models you intend are enabled; embedding fields reviewed for what must not leave.
  • Operations — master key set and backed up, secrets out of code, audit forwarded, restore tested.

Sources: docs.curiosity.ai — permission model architecture, RAG and agent architecture, AI tools, chat policies, LLM configuration, and the deployment security baseline.