ACL Explained
CoreCube's access control has four concepts. Learn these four, and the rest of this page is just showing how they fit together.
The cast of characters
| Term | What it is | Where it lives |
|---|---|---|
| Compartment | A named group of content (engineering, hr, finance, …) | Label on a connection |
| Sensitivity level | How secret the content is: public → internal → confidential → restricted | Label on a connection |
| Connection | A data source (Confluence, SharePoint, a Library upload) carrying exactly one compartment + one sensitivity | The content side of the model |
| Scope | A permission bundle — "these compartments, up to this sensitivity ceiling" | Assigned to users |
The one sentence that ties it all together:
A scope (on the user) is checked against the compartment + sensitivity (on the content). Both must match, or the content is invisible.
Everything below is detail, analogies, and examples for those four terms. You don't need to memorize them — by the end of the page they'll be second nature.
If you prefer analogies: the office building
Think of your knowledge base as an office building.
| Analogy | Real concept |
|---|---|
| Wing of the building | Compartment |
| Room inside a wing (deeper = more sensitive) | Sensitivity level on content |
| Keycard in an employee's pocket | Scope — it says "these wings, up to room N" |
The room classification lives on the door. The keycard lives in the user's pocket. Two different things that happen to share the same vocabulary (public → restricted): one is a label on the room, the other is a ceiling printed on the keycard that gets compared against the room at access time.
A keycard that opens the Engineering wing up to the internal room cannot open the HR wing at all — even if that person physically walks over there.
Labelling content: the connection side
Every connection carries exactly one compartment and one sensitivity level. All content ingested through that connection inherits both labels automatically — you don't label individual pages or documents, you label the connection.
Examples of what a real connection list looks like:
| Connection | Compartment | Sensitivity |
|---|---|---|
| Confluence — All Staff | all-staff | public |
| Confluence — Engineering | engineering | internal |
| Confluence — HR Policies | hr | confidential |
| Board Reports (SharePoint) | finance | restricted |
Granting access: the scope side
A scope packages two things into a single assignable unit: a list of allowed compartments, and a sensitivity ceiling.
At query time, every chunk is checked against the user's scope. Both rules must pass, or the chunk is invisible:
Failing chunks are not blurred out, not shown with a lock icon — they are simply absent from the result set.
How it all connects
Scope is the assignment primitive. You don't tick compartments on a user directly — you create a scope (e.g. Engineering) that bundles the right compartments and sensitivity ceiling, then add the user to that scope. The compartments the user can effectively reach are a consequence of which scopes they belong to.
Here is the full chain from user to content:
Cardinality legend — every relationship above has a concrete example in the diagram:
| Relationship | Type | Example in the diagram |
|---|---|---|
| User ↔ Scope | N:N | One scope → many users: Alice and Dave are both in Engineering. One user → many scopes: Alice is in Engineering and All Staff. |
| Scope ↔ Compartment | N:N | One compartment → many scopes: all-staff is granted by Engineering, HR Team, and All Staff. One scope → many compartments: Engineering grants both all-staff and engineering. |
| Compartment → Connection | 1:N | One compartment → many connections: engineering labels both Confluence — Engineering and GitHub Wiki — Engineering. Each connection, however, has exactly one compartment (immutable after creation). |
Read it left to right:
- Users are people (or applications, via API keys). Each user is a member of one or more scopes.
- Scopes are the assignment primitive — a named bundle of (allowed compartments, max sensitivity). One scope can be shared by many users: everyone in Engineering is added to the
Engineeringscope. - Compartments are what each scope lists. The user's effective compartment set is the union of compartments across all their scopes.
- Connections are data sources, each labelled with one compartment + one sensitivity level. Every chunk ingested through a connection inherits those labels.
The scope-to-connection mapping happens automatically via the compartment label. Add a new Confluence connection with compartment engineering and every user in a scope that allows engineering instantly sees its content — you never manually wire users to connections.
Even though the user's goal is "access to the HR compartment", the mechanism is always "add them to a scope that includes hr". If you find yourself wanting to tick compartments directly on a user, create a purpose-built scope for that combination instead — that's how the model stays consistent across many users.
Can a user be in more than one scope?
Yes. A user's effective access is the union of all their scopes' compartments — each compartment capped at that scope's maximum sensitivity.
Adding a user to an additional scope can only ever widen their access — never narrow it. To reduce access, remove them from a scope.
How API keys fit in
API keys are how users (or applications) talk to the headless API. They come in two flavours:
- Personal keys belong to a single user and inherit that user's scope memberships. When Alice creates a personal key and uses it, the backend resolves the key → Alice → Alice's scopes → compartments.
- Service keys belong to an application (n8n, OpenWebUI, a custom bot). The application passes the real end-user in an
X-Cube-Userheader, and CoreCube resolves that user's scopes for the request. Audit logs therefore always show who asked the question, even when the call comes from a shared backend service.
Both types end up at the same place: a resolved set of compartments + sensitivity ceilings that govern retrieval for that request.
The access matrix
Every possible piece of content sits somewhere in a 2D grid — compartment on one axis, sensitivity on the other. A scope carves out a region of this grid. Everything inside the region is visible; everything outside is not.
Scope: All Staff
Compartments: all-staff · Max sensitivity: public
| Compartment | public | internal | confidential | restricted |
|---|---|---|---|---|
| all-staff | ✅ | ❌ | ❌ | ❌ |
| engineering | ❌ | ❌ | ❌ | ❌ |
| hr | ❌ | ❌ | ❌ | ❌ |
| finance | ❌ | ❌ | ❌ | ❌ |
Scope: Engineering
Compartments: all-staff, engineering · Max sensitivity: internal
| Compartment | public | internal | confidential | restricted |
|---|---|---|---|---|
| all-staff | ✅ | ✅ | ❌ | ❌ |
| engineering | ✅ | ✅ | ❌ | ❌ |
| hr | ❌ | ❌ | ❌ | ❌ |
| finance | ❌ | ❌ | ❌ | ❌ |
Scope: HR Team
Compartments: all-staff, hr · Max sensitivity: confidential
| Compartment | public | internal | confidential | restricted |
|---|---|---|---|---|
| all-staff | ✅ | ✅ | ✅ | ❌ |
| hr | ✅ | ✅ | ✅ | ❌ |
| engineering | ❌ | ❌ | ❌ | ❌ |
| finance | ❌ | ❌ | ❌ | ❌ |
Scope: Executive
Compartments: all four · Max sensitivity: restricted
| Compartment | public | internal | confidential | restricted |
|---|---|---|---|---|
| all-staff | ✅ | ✅ | ✅ | ✅ |
| engineering | ✅ | ✅ | ✅ | ✅ |
| hr | ✅ | ✅ | ✅ | ✅ |
| finance | ✅ | ✅ | ✅ | ✅ |
Notice the pattern: the allowed compartments get green rows, but only up to the max sensitivity column. Everything past that column stays red.
How a query actually works
When a user sends a query, CoreCube runs through this sequence before touching the database:
The database never even sees rows the user is not allowed to read — PostgreSQL's Row-Level Security removes them before they reach the application.
Setting it up: the four steps
1. Create compartments
Name them after your teams or content groups. Start simple.
all-staff engineering hr finance
2. Create connections with the right label
Each connection gets one compartment and one sensitivity level. Use space key filtering (Confluence) or project key filtering (Jira) to split one source system into multiple connections with different labels.
If Engineering Confluence has both general wikis (internal) and architecture decision records (confidential), create two connections — one per sensitivity level. Don't mix sensitivity in a single connection.
3. Create scopes
Each scope is a rectangle in the access matrix above. Name scopes after the role or team they represent.
Scope "Engineering"
Compartments: all-staff, engineering
Max sensitivity: internal
4. Assign scopes to users
Go to Admin Console → Scopes, open a scope, and add the users who should be members. A user can belong to any number of scopes — their effective access is the union of all of them.
The Confluence multi-space example, step by step
Situation: One Confluence tenant. Three kinds of content. Three teams.
Goal: Engineers see engineering wikis. HR sees HR policies. Everyone sees company announcements. No cross-access.
Step 1 — Three compartments
| Name | Purpose |
|---|---|
all-staff | Company-wide content |
engineering | Engineering team content |
hr | HR team content |
Step 2 — Three connections, same Confluence tenant
| Connection | Space keys | Compartment | Sensitivity |
|---|---|---|---|
| Confluence — Company | COMPANY, IT | all-staff | public |
| Confluence — Engineering | ENG, DEVOPS | engineering | internal |
| Confluence — HR | HR | hr | confidential |
Step 3 — Three scopes
| Scope | Compartments | Max sensitivity |
|---|---|---|
| All Staff | all-staff | public |
| Engineering | all-staff, engineering | internal |
| HR Team | all-staff, hr | confidential |
Step 4 — Assign users
| User | Scope |
|---|---|
| Alice (engineer) | Engineering |
| Bob (HR manager) | HR Team |
| Carol (office manager) | All Staff |
Result
Alice queries "how do I deploy to staging?"
→ Scope: Engineering
→ Allowed: all-staff/public, all-staff/internal,
engineering/public, engineering/internal
→ Returns: engineering chunks + all-staff chunks
→ Never sees: hr chunks (wrong compartment)
Bob queries "what is the parental leave policy?"
→ Scope: HR Team
→ Allowed: all-staff/public, all-staff/internal,
hr/public, hr/internal, hr/confidential
→ Returns: hr chunks + all-staff chunks
→ Never sees: engineering chunks (wrong compartment)
Carol queries "when is the next all-hands?"
→ Scope: All Staff
→ Allowed: all-staff/public only
→ Returns: all-staff public chunks only
→ Never sees: engineering or hr chunks
Common questions
Q: Can a user be in more than one scope?
Yes. A user's effective access is the union of all their scopes' compartments, each capped at that scope's own maximum sensitivity. Adding a scope only ever widens access; to reduce access, remove the user from a scope.
Q: What happens if a user isn't in any scope?
With enforcement enabled, the user gets empty search results — not an error. They can still log in and use the admin console; they just retrieve nothing from the knowledge base.
Q: Can I change a connection's compartment after creation?
No. Compartment and sensitivity are permanent once set. To reclassify content, delete the connection and recreate it with the new label. The old content is purged, and a fresh sync runs.
Q: Does the Confluence service account's own permissions affect what CoreCube ingests?
Indirectly. If you configure a connection with spaceKeys: ["HR"], CoreCube only fetches the HR space — even if the service account can read all spaces. The service account's permissions are the upper ceiling; the connection's space key filter is the actual gate.
There's a trust-boundary consideration worth knowing about: with a single shared service account, CoreCube is the only enforcement point. If an operator later edits the connection and adds spaces, the connector will happily ingest them into the same compartment — and CoreCube has no way to know that was unintended. For the highest level of isolation, use dedicated service accounts per connection, with only the source-system permissions required for that connection's scope. That way the source system caps the blast radius even if CoreCube configuration is changed inadvertently. This is defense-in-depth, not a correctness requirement — CoreCube's access control works correctly with a shared service account.
Q: What about files uploaded to the Library?
Library uploads and check-ins are not tied to a connector. You choose the compartment and sensitivity at upload time. CoreCube validates that at least one scope covers that label before accepting the upload — preventing documents from being permanently invisible.