Skip to main content

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

TermWhat it isWhere it lives
CompartmentA named group of content (engineering, hr, finance, …)Label on a connection
Sensitivity levelHow secret the content is: publicinternalconfidentialrestrictedLabel on a connection
ConnectionA data source (Confluence, SharePoint, a Library upload) carrying exactly one compartment + one sensitivityThe content side of the model
ScopeA 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.

AnalogyReal concept
Wing of the buildingCompartment
Room inside a wing (deeper = more sensitive)Sensitivity level on content
Keycard in an employee's pocketScope — 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 (publicrestricted): 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:

ConnectionCompartmentSensitivity
Confluence — All Staffall-staffpublic
Confluence — Engineeringengineeringinternal
Confluence — HR Policieshrconfidential
Board Reports (SharePoint)financerestricted

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:

RelationshipTypeExample in the diagram
User ↔ ScopeN:NOne scope → many users: Alice and Dave are both in Engineering. One user → many scopes: Alice is in Engineering and All Staff.
Scope ↔ CompartmentN:NOne 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 → Connection1:NOne 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:

  1. Users are people (or applications, via API keys). Each user is a member of one or more scopes.
  2. 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 Engineering scope.
  3. Compartments are what each scope lists. The user's effective compartment set is the union of compartments across all their scopes.
  4. 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.

Assign scopes, not compartments

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-User header, 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

Compartmentpublicinternalconfidentialrestricted
all-staff
engineering
hr
finance

Scope: Engineering

Compartments: all-staff, engineering · Max sensitivity: internal

Compartmentpublicinternalconfidentialrestricted
all-staff
engineering
hr
finance

Scope: HR Team

Compartments: all-staff, hr · Max sensitivity: confidential

Compartmentpublicinternalconfidentialrestricted
all-staff
hr
engineering
finance

Scope: Executive

Compartments: all four · Max sensitivity: restricted

Compartmentpublicinternalconfidentialrestricted
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.

One connection per sensitivity group

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

NamePurpose
all-staffCompany-wide content
engineeringEngineering team content
hrHR team content

Step 2 — Three connections, same Confluence tenant

ConnectionSpace keysCompartmentSensitivity
Confluence — CompanyCOMPANY, ITall-staffpublic
Confluence — EngineeringENG, DEVOPSengineeringinternal
Confluence — HRHRhrconfidential

Step 3 — Three scopes

ScopeCompartmentsMax sensitivity
All Staffall-staffpublic
Engineeringall-staff, engineeringinternal
HR Teamall-staff, hrconfidential

Step 4 — Assign users

UserScope
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.

We use cookies for analytics to improve our website. More information in our Privacy Policy.