Back to blog

Restricting Developer Data Access in Microsoft Fabric: OneLake Security vs Workspace Roles

In Microsoft Fabric, the role that lets a developer build also bypasses OneLake security. Here are 3 patterns to isolate developer data access by domain — and which one actually holds.

You want an HR developer who can build notebooks but can't read Finance data. In Microsoft Fabric, the role that lets someone build is the same role that bypasses OneLake security. Here's why, the three patterns teams reach for, and the one that actually holds.

#The problem: the role that lets you build also lets you read everything

Picture a Microsoft Fabric medallion lakehouse serving three business domains — HR, Finance, Operations — living in shared workspaces. The requirement is simple: a developer working on the HR domain should be able to write notebooks and pipelines against HR tables, and should not be able to read Finance or Operations data.

In Fabric today, this is harder than it looks — not because of a missing setting, but because of how workspace roles and OneLake security interact.

  • Workspace Viewer is read-only. A Viewer cannot create, edit, or run notebooks and pipelines — so a Viewer cannot develop.
  • To develop, a user needs Contributor or Member. Both carry Write. And Write overrides OneLake security entirely (OLS, RLS, CLS).

So the moment someone can build, they can read every table in the item — regardless of any OneLake security role you've defined. The capability that grants development is the same capability that dissolves the data wall.

Capability Admin Member Contributor Viewer
Read all data in OneLake / Spark (ReadAll) ❌ by default
Create/run notebooks & pipelines (build)
Edit OneLake security roles
Restricted by OneLake security ❌ bypass ❌ bypass ❌ bypass

Read the bottom row. OneLake security constrains exactly one role — Viewer — and Viewer can't build. That's the whole knot.

#Why this is a control-plane / data-plane collision

Fabric has two access-control planes, and the documentation talks about them as if they're independent:

  • Control plane — what you can do: build, run, manage. Governed by workspace roles.
  • Data plane — what you can read: which tables, rows, columns. Governed by OneLake security.

But in Fabric they're linked. Giving someone Member or Contributor on the control plane (the workspace) automatically grants full read on the data plane (OneLake). So OLS, RLS, and CLS — the tools built to control who sees what — don't apply to the people who work with raw, sensitive data every day: the engineers.

Here's the decision that governs every design below.

flowchart TD
    A[User needs access to a domain table] --> B{Do they hold Write in the<br/>workspace that holds the data?}
    B -->|Yes: Admin / Member / Contributor| C[Bypasses OneLake security<br/>Reads every table in the item]
    B -->|No: Viewer + OneLake role| D[OneLake security enforced<br/>Sees only granted tables / rows / columns]
    C --> E[No domain isolation]
    D --> F[Domain isolation holds]

Everything comes down to keeping developers out of the Write column of the workspace where the data physically lives. Three patterns try to do that.

#Option 1: A workspace per domain

Give each domain its own workspace, per environment: platform-HR-{env}, platform-Finance-{env}, platform-Ops-{env}. Domain developers are Members of their workspace and nothing else.

This is the only pattern that blocks developers outright, because isolation is topological — you can't read what you aren't a member of. Membership is access, which makes it trivially auditable: list the group, you have the access list.

The cost is sprawl. Three problems compound:

  1. Workspace and group explosion. Count multiplies by domains × environments — before you add a single feature workspace for branch-based development.
  2. Conformed dimensions fragment. Shared tables like dim_date, dim_employee, and dim_cost_center live in one domain and must be shared or copied into the others.
  3. Code duplicates. Shared transformation logic gets copied across workspaces unless you invest in a library or %run pattern to keep it DRY.
flowchart TD
    subgraph HR[HR workspace]
        HRdev[HR devs = Member] --> HRlh[(HR Lakehouse)]
    end
    subgraph FIN[Finance workspace]
        FINdev[Finance devs = Member] --> FINlh[(Finance Lakehouse)]
    end
    subgraph OPS[Ops workspace]
        OPSdev[Ops devs = Member] --> OPSlh[(Ops Lakehouse)]
    end
    Conformed[(Conformed dimensions)] -. shared or duplicated .-> HRlh
    Conformed -. shared or duplicated .-> FINlh
    Conformed -. shared or duplicated .-> OPSlh

Verdict: it works, and it's the most defensible thing you can ship today.

#Option 2: One shared workspace + OneLake security

Keep a single consumption workspace. Define OneLake roles that grant each domain group Read (and Write) on only its own tables. No duplication, conformed dimensions are natural, grants are managed centrally.

This is the pattern that looks right and doesn't survive contact with the requirement. OneLake roles only bind Viewers. Your developers need Member or Contributor to build — and those roles bypass OneLake security wholesale. An HR developer with Contributor reads Finance and Ops without a single role granting it. The wall exists in the portal; it does nothing to the people you built it for.

Option 2 is the correct answer to a different question: isolating readers and report consumers. For that it's excellent — down to the row and column, and on Direct Lake on OneLake it reaches into the Power BI report. For isolating developers, it's a non-starter. Don't confuse the two.

Verdict: great for consumers, useless for developers.

#Option 3: Separate the store from the build

This is the pattern hiding inside Microsoft's own Optimizing for CI/CD in Microsoft Fabric guidance, where the internal Azure Data team splits solutions into distinct workspace categories and explicitly advises: place Lakehouses in workspaces separate from their dependent items. They did it for deployment hygiene. It also happens to be the cleanest lever you have for developer isolation.

So you split the topology by function, not by domain:

  • A store workspace holds the Lakehouses. Developers are Viewers here, plus a domain-scoped OneLake security role. Nobody develops in this workspace.
  • An engineering / orchestration workspace holds the notebooks and pipelines. Developers are Contributors/Member here — but this workspace contains no domain data, so ReadAll bypasses nothing.

The notebook runs in the engineering workspace and reaches across to the store lakehouse. Because the developer is not a writer in the store workspace, OneLake security is back in force on the data.

flowchart LR
    subgraph Eng[Engineering workspace]
        N[Notebook / Pipeline<br/>Dev = Contributor<br/>no data here]
    end
    subgraph Store[Store workspace]
        LH[(Domain Lakehouses<br/>Dev = Viewer + OneLake role)]
    end
    N -->|cross-workspace access<br/>runs as the user| LH
    LH -->|OneLake security ENFORCED<br/>dev is not a writer here| N

The bypass lands on an empty room. The data lives where the developer has no Write, so the fine-grained roles finally apply to the people they were always meant to constrain. This is the closest thing Fabric has to control-plane / data-plane separation today, achieved by topology rather than by a native feature.

Verdict: the best-shaped answer available, and the only one that keeps conformed dimensions central and enforces the wall on developers.

#Pick by scenario

Your situation Reach for
Hard isolation required now, audit is non-negotiable, you can eat the sprawl Option 1 — workspace per domain
The people you're isolating only read (analysts, report consumers) Option 2 — shared workspace + OneLake security
Developers must build but stay walled to their domain, and you want conformed dims to stay central Option 3 — split store from build, validate write-back
Central platform team builds everything; domains only consume Option 2 for the consumers, central team as Members of the store

#What Microsoft should change

Every option above is a workaround for one root cause: in Fabric, the control plane and the data plane are fused. Workspace roles decide what you can build; OneLake security decides what you can read — but a single Write grant satisfies both, so they aren't really separate layers.

They should be — the way Databricks Unity Catalog keeps workspace access independent of data grants. Two changes would get Fabric there:

  1. Write should stop implying ReadAll. A data-plane deny must hold even against a writer. If OneLake security says a developer can't read finance.salaries, their Contributor role shouldn't override it.
  2. Add a builder-but-scoped-reader role. Today it's binary — can't build (Viewer) or read everything (Contributor+). A role that builds and runs items while honoring OneLake roles would make Options 1 and 3 unnecessary.

#Key Takeaways

  1. The role that builds is the role that bypasses. Contributor/Member carry Write, and Write overrides OneLake security. Viewer is constrained but can't develop. That's the entire problem.
  2. Option 1 (workspace per domain) is the only pattern that blocks developers with zero ambiguity — at the cost of sprawl and duplicated code.
  3. Option 2 (shared + OneLake security) isolates readers, never developers. Don't ship it as a developer control.
  4. Option 3 (split store from build) puts the data where developers aren't writers, restoring OneLake enforcement — but validate the write-back path before you bet on it.
  5. The real fix is Microsoft's: decouple the control plane from the data plane so Write stops implying ReadAll.

#Sources

Suggest an edit by email

Get future articles

Follow for practical Microsoft Fabric, Azure, Spark, and data engineering writeups.

PreviousModel Context Protocol (MCP): A DevOps Engineer's Guide
NextSwap Fabric Pipeline Connections per Environment with Variable Libraries
On this page