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_KEYis 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_ONLYseparates 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,RestrictAccessToUserandMarkFileAsPrivatemaintain 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 withoutAsUserruns 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 checksscope.Graph.CanUserSeeAsyncbefore 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.AddSnippetand 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 carrysearch.TargetUIDsto 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.
- A keyword list is exactly as good as the list. A classifier returns a score, not a fact. Both produce false positives and misses — which is why flag is the default and the evaluations tab exists.
- The workspace-authored system prompt is never matched against. It is the thing policies protect, not something to test.
- A verdict outlives its conversation: deleting the chat does not erase the evidence.
- A conversation that passes every policy leaves nothing behind.
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
AsUservariant. 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.