AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Security

skill-kpbray-power-bi-agent-skills-security · by kpbray

Implements Row-Level Security (RLS) and Object-Level Security (OLS) for Power BI models. Use for data access control, dynamic security, and multi-tenant scenarios.

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add skill-kpbray-power-bi-agent-skills-security

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-kpbray-power-bi-agent-skills-security)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
6mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Security? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Security Skill

This skill helps implement Row-Level Security (RLS) and Object-Level Security (OLS) for Power BI semantic models.

When to Use This Skill

  • Restricting data access by user/role
  • Implementing dynamic security based on user identity
  • Creating multi-tenant data models
  • Protecting sensitive columns with OLS
  • Setting up manager/hierarchy security
  • Testing security configurations

Security Types

| Type | Controls | Use Case | |------|----------|----------| | RLS (Row-Level Security) | Which rows users can see | Regional sales reps see only their region | | OLS (Object-Level Security) | Which columns/tables users can see | Hide salary column from non-HR users |

Row-Level Security (RLS)

TMDL Syntax

role 'Sales Representatives'
	modelPermission: read
	lineageTag: 

	tablePermission Sales = 'Sales'[Region] = USERPRINCIPALNAME()

Role Structure

role 'RoleName'
	modelPermission: read
	description: 'Description of who this role is for'
	lineageTag: 

	tablePermission TableName = 
	tablePermission AnotherTable = 

Model Permissions

| Permission | Description | |------------|-------------| | read | Can read data (standard for RLS) | | administrator | Full access (no filtering) | | none | No access |

Common RLS Patterns

Static Role (Fixed Filter)

/// Role for West region users
role 'West Region'
	modelPermission: read
	lineageTag: {{guid}}

	tablePermission Sales = 'Sales'[Region] = "West"

Dynamic Security (User Identity)

/// Each user sees only their own data
role 'User Data'
	modelPermission: read
	lineageTag: {{guid}}

	tablePermission Sales =
		'Sales'[User Email] = USERPRINCIPALNAME()

Security Table Lookup

/// Users mapped to regions in a security table
role 'Region Security'
	modelPermission: read
	lineageTag: {{guid}}

	tablePermission Sales =
		CONTAINS(
		    FILTER(
		        'User Region Mapping',
		        'User Region Mapping'[User Email] = USERPRINCIPALNAME()
		    ),
		    'User Region Mapping'[Region], 'Sales'[Region]
		)

Manager Hierarchy

/// Managers see their team's data
role 'Manager Security'
	modelPermission: read
	lineageTag: {{guid}}

	tablePermission Sales =
		VAR CurrentUser = USERPRINCIPALNAME()
		VAR UserEmployeeID = LOOKUPVALUE(
		    Employees[Employee ID],
		    Employees[Email], CurrentUser
		)
		RETURN
		    PATHCONTAINS(
		        'Sales'[Manager Path],
		        UserEmployeeID
		    )

Multi-Tenant with Tenant ID

/// Each tenant sees only their data
role 'Tenant Isolation'
	modelPermission: read
	lineageTag: {{guid}}

	tablePermission Sales =
		VAR UserTenant = LOOKUPVALUE(
		    Users[Tenant ID],
		    Users[Email], USERPRINCIPALNAME()
		)
		RETURN
		    'Sales'[Tenant ID] = UserTenant

Multiple Conditions

/// Users see data for their region AND department
role 'Region and Department'
	modelPermission: read
	lineageTag: {{guid}}

	tablePermission Sales =
		VAR CurrentUser = USERPRINCIPALNAME()
		VAR UserRegion = LOOKUPVALUE(Users[Region], Users[Email], CurrentUser)
		VAR UserDept = LOOKUPVALUE(Users[Department], Users[Email], CurrentUser)
		RETURN
		    'Sales'[Region] = UserRegion &&
		    'Sales'[Department] = UserDept

Date-Based Access

/// Users can only see data after their start date
role 'Date Restricted'
	modelPermission: read
	lineageTag: {{guid}}

	tablePermission Sales =
		VAR UserStartDate = LOOKUPVALUE(
		    Users[Start Date],
		    Users[Email], USERPRINCIPALNAME()
		)
		RETURN
		    'Sales'[Date] >= UserStartDate

Security Table Design

User-to-Data Mapping Table

table 'User Security'
	lineageTag: {{guid}}

	column 'User Email'
		dataType: string
		lineageTag: {{guid}}

	column 'Region'
		dataType: string
		lineageTag: {{guid}}

	column 'Department'
		dataType: string
		lineageTag: {{guid}}

	column 'Access Level'
		dataType: string
		lineageTag: {{guid}}

	partition 'User Security' = m
		mode: import
		source =
			let
			    Source = Table.FromRows({
			        {"user1@company.com", "North", "Sales", "Standard"},
			        {"user2@company.com", "South", "Sales", "Standard"},
			        {"manager@company.com", "ALL", "Sales", "Manager"}
			    }, {"User Email", "Region", "Department", "Access Level"})
			in
			    Source

RLS with Security Table

role 'Data Access'
	modelPermission: read
	lineageTag: {{guid}}

	tablePermission Sales =
		VAR CurrentUser = USERPRINCIPALNAME()
		VAR UserAccessLevel = LOOKUPVALUE(
		    'User Security'[Access Level],
		    'User Security'[User Email], CurrentUser
		)
		VAR UserRegion = LOOKUPVALUE(
		    'User Security'[Region],
		    'User Security'[User Email], CurrentUser
		)
		RETURN
		    IF(
		        UserAccessLevel = "Manager" || UserRegion = "ALL",
		        TRUE(),
		        'Sales'[Region] = UserRegion
		    )

Object-Level Security (OLS)

Column-Level Security

OLS hides columns from specific roles. In TMDL:

role 'Non-HR Users'
	modelPermission: read
	lineageTag: {{guid}}

	/// Hide salary column
	columnPermission Employees.Salary = none

	/// Hide SSN column
	columnPermission Employees.SSN = none

Table-Level Security

role 'External Users'
	modelPermission: read
	lineageTag: {{guid}}

	/// Completely hide internal table
	tablePermission 'Internal Metrics' = false

OLS Permission Values

| Permission | Effect | |------------|--------| | none | Column hidden, inaccessible | | read | Column visible (default) |

Combining RLS and OLS

/// Role with both row and column restrictions
role 'Limited Access'
	modelPermission: read
	lineageTag: {{guid}}

	/// Row filter: only see own region
	tablePermission Sales = 'Sales'[Region] = LOOKUPVALUE(
	    Users[Region], Users[Email], USERPRINCIPALNAME()
	)

	/// Column filter: hide margin columns
	columnPermission Sales.'Profit Margin' = none
	columnPermission Sales.'Cost' = none

DAX Security Functions

USERPRINCIPALNAME()

Returns the email/UPN of the current user:

USERPRINCIPALNAME()  // Returns "user@company.com"

USERNAME()

Returns the domain\username (less common in cloud):

USERNAME()  // Returns "DOMAIN\username"

CUSTOMDATA()

Returns custom data passed via connection string:

CUSTOMDATA()  // Returns custom parameter value

USEROBJECTID()

Returns the Azure AD Object ID:

USEROBJECTID()  // Returns GUID of user

Testing RLS

In Power BI Desktop

  1. Modeling tab > View as > Roles
  2. Select role(s) to test
  3. Optionally enter "Other user" email
  4. Click OK to see filtered data

In Power BI Service

  1. Dataset settings > Security
  2. Test as role with specific user
  3. Verify filtered results

Test Measure

Create a measure to debug security:

Security Debug =
VAR CurrentUser = USERPRINCIPALNAME()
VAR UserRegion = LOOKUPVALUE(
    'User Security'[Region],
    'User Security'[User Email], CurrentUser
)
RETURN
    "User: " & CurrentUser & " | Region: " & UserRegion

Performance Considerations

DO

/// GOOD: Direct column comparison
tablePermission Sales = 'Sales'[Region] = LOOKUPVALUE(...)

/// GOOD: Use CONTAINS for multiple values
tablePermission Sales = CONTAINS(
    VALUES('User Security'[Region]),
    'User Security'[Region], 'Sales'[Region]
)

DON'T

/// BAD: Complex DAX in security filter
tablePermission Sales =
    CALCULATE(
        COUNTROWS(FILTER(...)),  // Avoid complex calculations
        ...
    ) > 0

/// BAD: Iterating over large tables
tablePermission Sales =
    SUMX(LargeTable, ...)  // Slow!

Security Best Practices

Design Principles

  1. Principle of Least Privilege - Start restrictive, add access
  2. Single Security Table - Centralize user mappings
  3. Test All Scenarios - Every role combination
  4. Document Roles - Clear descriptions of access levels
  5. Avoid RLS on Aggregated Models - May expose totals

Common Mistakes

| Mistake | Problem | Fix | |---------|---------|-----| | No default restriction | New users see all data | Use catch-all role | | Filter only fact table | Dimension data exposed | Filter fact AND dimensions | | Complex DAX filters | Poor performance | Simplify with lookup tables | | Hardcoded emails | Maintenance nightmare | Use security table | | No testing | Security gaps | Test as each role |

Security Checklist

  • [ ] All fact tables have appropriate filters
  • [ ] Related dimension tables filtered (if needed)
  • [ ] Security table maintained and current
  • [ ] Roles tested with real user emails
  • [ ] Performance acceptable with security applied
  • [ ] Documentation of role purposes
  • [ ] No admin role exposed to end users
  • [ ] OLS applied to sensitive columns

Multiple Roles

When a user belongs to multiple roles, filters combine with OR:

User in "North Region" AND "Sales Department"
→ Sees: (Region = "North") OR (Department = "Sales")

To require AND logic, use a single role with combined filter:

role 'North Sales'
	tablePermission Sales =
	    'Sales'[Region] = "North" && 'Sales'[Department] = "Sales"

Boundaries and Constraints

DO

  • Use USERPRINCIPALNAME() for cloud deployments
  • Create a security mapping table for flexibility
  • Test with "View as" before deployment
  • Filter at the most granular fact table level
  • Document each role's intended audience

DO NOT

  • Never use RLS as the only security layer
  • Don't filter only dimension tables (facts may leak)
  • Avoid complex DAX in filter expressions
  • Never expose administrator role to users
  • Don't rely on RLS for aggregated/composite models

Workflow Integration

After implementing security:

  1. Test locally - Use "View as Roles" in Desktop
  2. Document - Describe each role's purpose
  3. Deploy - Publish to Power BI Service
  4. Assign - Add users/groups to roles in Service
  5. Validate - Test with actual user accounts

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.