crm-analytics-security-predicates

Row-level security in CRM Analytics datasets via security predicates — SAQL filter expressions stored on the dataset that apply at query time per running user. Covers the syntax (`'DatasetColumn' operator value`), the `$User.*` context variables, multi-level predicates (role hierarchy + team + region), the performance cost of complex predicates, and the testing discipline (admins bypass predicates by default). NOT for Salesforce Core sharing rules (different runtime), NOT for App / Dashboard / Lens-level access (that's CRM Analytics App sharing, not predicates), NOT for field-level masking inside a dataset (use Encryption + dataset transformations).

Best use case

crm-analytics-security-predicates is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Row-level security in CRM Analytics datasets via security predicates — SAQL filter expressions stored on the dataset that apply at query time per running user. Covers the syntax (`'DatasetColumn' operator value`), the `$User.*` context variables, multi-level predicates (role hierarchy + team + region), the performance cost of complex predicates, and the testing discipline (admins bypass predicates by default). NOT for Salesforce Core sharing rules (different runtime), NOT for App / Dashboard / Lens-level access (that's CRM Analytics App sharing, not predicates), NOT for field-level masking inside a dataset (use Encryption + dataset transformations).

Teams using crm-analytics-security-predicates should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/crm-analytics-security-predicates/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/data/crm-analytics-security-predicates/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/crm-analytics-security-predicates/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How crm-analytics-security-predicates Compares

Feature / Agentcrm-analytics-security-predicatesStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Row-level security in CRM Analytics datasets via security predicates — SAQL filter expressions stored on the dataset that apply at query time per running user. Covers the syntax (`'DatasetColumn' operator value`), the `$User.*` context variables, multi-level predicates (role hierarchy + team + region), the performance cost of complex predicates, and the testing discipline (admins bypass predicates by default). NOT for Salesforce Core sharing rules (different runtime), NOT for App / Dashboard / Lens-level access (that's CRM Analytics App sharing, not predicates), NOT for field-level masking inside a dataset (use Encryption + dataset transformations).

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# CRM Analytics Security Predicates

Row-level security in CRM Analytics. A **security predicate** is a SAQL
filter expression stored at the **dataset** level. Every query that
reads the dataset has the predicate applied automatically, scoped to
the running user's context (Id, role, profile, custom User fields).
The dataset's owner / admin doesn't see it; the running user does.

What this skill is NOT. Salesforce Core SObject sharing rules (Org-Wide
Defaults, role hierarchy, sharing rules, manual sharing, Apex managed
sharing) operate on the source records — predicates operate on the
dataset's *copy* of those records. Both can apply; they're independent.
App / Dashboard / Lens sharing is different — that's CRM Analytics
*Asset* sharing (who can open the dashboard at all), not row-level
filtering inside it. Field-level masking is a different control (use
Shield Platform Encryption or transform the dataset to omit / hash the
field).

---

## Before Starting

- **Confirm the dataset, not the dashboard, is the right level.**
  Predicates apply to the dataset, not to individual dashboards or
  lenses. A predicate set on a dataset filters every dashboard /
  lens / SAQL query that reads that dataset. If different audiences
  need different filtering, you may need separate datasets — or a
  predicate that varies per running user via `$User.*` rather than
  per-dashboard.
- **Verify SObject-level sharing is also in place.** Predicates do
  not replace sharing rules on the source records. If a user can't
  see an Account in Salesforce Core, the dataflow / recipe that
  built the dataset already had access (it ran as the dataflow
  user) — the predicate is the only thing keeping the user from
  seeing the row in CRM Analytics. That asymmetry is correct, but
  surprising.
- **Plan the test set.** At minimum: a row owner, a non-owner with
  legitimate access, a non-owner without access, an admin (who
  bypasses predicates by default — that's the most common mistake).
- **Budget for predicate cost.** Predicates run on every query.
  Complex multi-condition predicates with `IN` against a large list,
  or predicates that reference computed `$User.*` values, can slow
  every dashboard that reads the dataset.

---

## Core Concepts

### Predicate syntax

```
'<DatasetColumn>' <operator> <value> [<logical-op> '<DatasetColumn>' <operator> <value>] ...
```

Components:

- **DatasetColumn** — the API name of a column in the dataset, in
  single quotes. Case-sensitive.
- **Comparison operators** — `==`, `!=`, `<`, `<=`, `>`, `>=`,
  `in`, `matches` (regex).
- **Logical operators** — `&&` (AND), `||` (OR), parentheses for
  grouping.
- **Values** — string literal `"..."`, numeric, list `[...]`, or
  a `$User.*` context variable.

### `$User.*` context variables

Available inside any predicate value position. They resolve at query
time to the running user:

| Variable | Resolves to |
|---|---|
| `"$User.Id"` | The 18-character Id of the running user |
| `"$User.UserRoleId"` | The role Id (or null if no role) |
| `"$User.ProfileId"` | The profile Id |
| `"$User.UserName"` | The Username |
| `"$User.Email"` | Email |
| `"$User.<CustomField__c>"` | Any standard or custom User field |

The custom-field form is the most useful — define a `Region__c` on
User, populate per user, then write `'Region' == "$User.Region__c"`
to filter the dataset by the running user's region.

### The five access patterns and their predicate shapes

| Access pattern | Predicate |
|---|---|
| Owner-only | `'OwnerId' == "$User.Id"` |
| Owner OR my team | `'OwnerId' == "$User.Id" \|\| 'TeamMembers' matches "$User.Id"` |
| Role hierarchy (my role and below) | `'UserRoleId' in [matches "$User.UserRoleId"]` (predicate refers to a precomputed role-hierarchy column in the dataset) |
| Region-scoped (custom User field) | `'Region' == "$User.Region__c"` |
| Multi-condition: region AND own team | `('Region' == "$User.Region__c") && ('TeamMembers' matches "$User.Id")` |

### Admins bypass predicates by default

CRM Analytics admins (users with the `Manage Analytics` permission)
see all rows regardless of the predicate. This is intentional — it
prevents admins from being locked out of their own datasets — but
it's the source of the most common testing failure: the team writes
the predicate, the admin tests it, sees all the rows, declares it
working, and discovers in production that non-admin users see what
they expected.

**Always test with a non-admin user.**

### Predicate cost

Predicates run on every query against the dataset. The cost is:

- **Per-row evaluation** of the predicate expression.
- **Variable substitution** — `$User.Email` requires looking up the
  running user's email at query time.
- **Regex matches** — `matches` is more expensive than `==` /
  `in`.
- **Long `IN` lists** — `IN ['a', 'b', 'c', ..., 'zzz']` evaluates
  each comparison.

For most predicates, the cost is negligible (sub-100ms added). For
predicates with regex matches against role-hierarchy columns over
millions of rows, the cost can be a noticeable fraction of dashboard
load time. Profile in CRM Analytics's query inspector if a
predicate-protected dashboard is slow.

---

## Common Patterns

### Pattern A — Owner-only access

**When to use.** Every user sees only the rows they own. Standard
"my pipeline" / "my cases" semantics.

```
'OwnerId' == "$User.Id"
```

**Test plan.**
- User A owns rows 1-10 → sees rows 1-10.
- User B owns rows 11-20 → sees rows 11-20.
- Admin → sees all rows (bypasses predicate; expected).

**Trade.** Doesn't surface team-shared visibility. If the user has
been granted access via a sharing rule, they still don't see the row
in the dataset because the predicate is per-dataset, not per-source-row.
For team visibility, use Pattern B.

### Pattern B — Owner OR team-member

**When to use.** Standard sharing-rule equivalent for opportunities
or accounts where team members get visibility.

```
'OwnerId' == "$User.Id" || 'AccountTeamMembers' matches "$User.Id"
```

**Prerequisite.** The dataset must include a column listing
team-member Ids per row — typically a multi-value column populated by
the dataflow / recipe joining `AccountTeamMember` against `Account`.

**Test plan.**
- Owner → sees their owned rows.
- Team member → sees rows where they're listed in `AccountTeamMembers`.
- Non-owner / non-team-member → no rows.

### Pattern C — Role hierarchy (my role and below)

**When to use.** Sales managers see rows owned by their reports.

```
'UserRoleHierarchyIds' matches "$User.UserRoleId"
```

**Prerequisite.** `UserRoleHierarchyIds` is a dataset column that
contains, for each row, the chain of role Ids from the row owner up
to the org top — populated by the dataflow / recipe.

**Implementation note.** Computing the role chain per row is a
dataflow / recipe responsibility. The predicate is straightforward;
the *data preparation* is where the cost lives.

### Pattern D — Multi-dimensional (region AND team)

**When to use.** A regional manager sees rows in their region AND on
their team — but not other regions, even if they're on the team.

```
('Region' == "$User.Region__c") && ('AccountTeamMembers' matches "$User.Id")
```

**Trade.** Adding dimensions multiplies the test matrix. Each new
condition adds a new dimension of test users.

### Pattern E — Service-account bypass for system queries

**When to use.** A scheduled SAQL query runs as a service user that
must see all rows (export to a warehouse, daily aggregate). The
predicate would block it.

**Approach.** Grant the service user the `Manage Analytics`
permission so the predicate is bypassed. Document the bypass: a
service-account-only role + permission set, audit-logged, scope
limited to the relevant dataset(s).

**Anti-pattern.** Writing `('OwnerId' == "$User.Id" || 'OwnerId' ==
'service-user-id')` — works but ties the predicate to a specific
user record. If the service user is recreated, the predicate
silently breaks.

---

## Decision Guidance

| Situation | Predicate | Reason |
|---|---|---|
| Standard "my pipeline" semantics | `'OwnerId' == "$User.Id"` (Pattern A) | Cleanest; no extra dataset prep |
| Sharing-rule equivalent (owner or team) | Pattern B with team-member column | Mirrors Salesforce sharing semantics in the dataset |
| Manager sees their reports | Pattern C with role-hierarchy column | Standard hierarchical access |
| Region-scoped reporting | `'Region' == "$User.Region__c"` (custom User field) | Use custom User fields for non-standard dimensions |
| Multi-dimensional access | Pattern D — combine with `&&` | Each new dimension multiplies test users |
| Need different access per dashboard | **Either** separate datasets **or** predicate that varies via `$User.*` | Predicates are per-dataset, not per-dashboard |
| Service account that needs full access | `Manage Analytics` permission on the service user | Don't hardcode user Ids in the predicate |
| Test passes for admin only | **Test with a non-admin user** | Admins bypass predicates by default |
| Predicate is slowing dashboards | Profile in CRM Analytics query inspector; consider precomputing fields in the dataset | Per-row evaluation cost adds up |

---

## Recommended Workflow

1. **Decide access pattern.** Pick from owner-only, owner+team, role-hierarchy, region, multi-dimensional. Start simple; add dimensions only when justified.
2. **Identify the columns the predicate needs.** Owner Id is usually present; team-members and role-hierarchy chains are dataflow / recipe-computed columns.
3. **Write the predicate** following the syntax in Core Concepts.
4. **Define the test matrix.** Owner / non-owner / admin / cross-team — one user per axis. Include a non-admin user; admins bypass predicates.
5. **Apply via the dataset's `Security Predicate` field** (Setup or via the dataset XMD JSON).
6. **Test.** Run the same SAQL query as each test user; confirm the row sets match expectations.
7. **Profile.** If the dataset feeds latency-sensitive dashboards, run a representative query in the inspector and confirm predicate cost is acceptable.

---

## Review Checklist

- [ ] Predicate syntax is valid (column names quoted, operators spelled correctly).
- [ ] Every `$User.*` reference resolves to a real User field.
- [ ] If using regex `matches`, regex is verified against sample data.
- [ ] Test matrix includes a non-admin user.
- [ ] Test matrix includes a user with NO access (verifies the predicate actually blocks).
- [ ] Service-account bypass strategy is documented (if any).
- [ ] Performance baseline measured (especially if `matches` is used).
- [ ] Predicate is stored on the dataset, not duplicated across dashboards.

---

## Salesforce-Specific Gotchas

1. **Admins bypass predicates by default.** Test with a non-admin user, always. (See `references/gotchas.md` § 1.)
2. **Predicates apply to the dataset, not the dashboard.** A predicate set on dataset X filters every dashboard that reads dataset X. (See `references/gotchas.md` § 2.)
3. **Predicates do NOT replace SObject-level sharing on source records.** Both layers apply. The dataflow / recipe ran as a privileged user, so the *dataset* has the data; the predicate is the only filter the running user sees through. (See `references/gotchas.md` § 3.)
4. **`$User.<CustomField__c>` resolves at query time** — if the custom field is null on the running user, the predicate's right-hand side is null, and `'Column' == null` returns no rows for that user. (See `references/gotchas.md` § 4.)
5. **Role-hierarchy access requires a precomputed column in the dataset.** SAQL can't traverse the role hierarchy at query time; the dataflow / recipe must produce a `UserRoleHierarchyIds` chain per row. (See `references/gotchas.md` § 5.)
6. **`matches` regex is more expensive than `==` and `in`.** Profile if the dashboard is slow. (See `references/gotchas.md` § 6.)
7. **Hard-coding a service-account user Id in the predicate breaks when the user is recreated.** Use the `Manage Analytics` permission to bypass instead. (See `references/gotchas.md` § 7.)

---

## Output Artifacts

| Artifact | Description |
|---|---|
| Security predicate expression | The actual string applied to the dataset's `Security Predicate` field |
| Dataflow / recipe additions | Any precomputed columns the predicate depends on (team membership, role-hierarchy chain) |
| Custom User fields used | Per-user values referenced via `$User.<CustomField>` |
| Test matrix | Owner / non-owner / admin / cross-team users with expected row sets per user |
| Performance baseline | Query inspector output before/after predicate; documented overhead |

---

## Related Skills

- `data/crm-analytics-dataflow-design` — building the dataset that the predicate protects; predicate-required columns (team membership, role chain) are dataflow concerns.
- `data/crm-analytics-app-sharing` — different layer: who can open the dashboard / app at all (Asset sharing), not row-level filtering inside it.
- `security/sharing-architecture` — Salesforce Core sharing on the source SObjects; both layers apply when CRM Analytics queries Salesforce data.
- `security/byok-key-rotation` — when the dataset contains Shield-encrypted fields, key rotation interacts with predicate-protected reads.

Related Skills

visualforce-security-and-modernization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).

transaction-security-policies

8
from PranavNagrecha/AwesomeSalesforceSkills

Transaction Security policy creation and configuration: condition builder, enhanced policies, enforcement actions (block, MFA, notification, end session), real-time monitoring mode, and policy troubleshooting. NOT for Event Monitoring log analysis or Shield Event Monitoring setup (use event-monitoring). NOT for Apex testing or debug-log analysis.

security-incident-response

8
from PranavNagrecha/AwesomeSalesforceSkills

When to use: active or suspected Salesforce org compromise, unauthorized access investigation, attacker containment, forensic evidence collection from EventLogFile/LoginHistory, session revocation, OAuth token cleanup, eradication of attacker persistence, and post-incident recovery verification. Trigger keywords: org compromised, suspicious login, attacker access, session revocation, forensic investigation, breach response, event log forensics, login anomaly investigation, incident response runbook. Does NOT cover general security setup, permission set design, field-level security configuration, or proactive security hardening — those are separate skills. NOT for general security setup.

security-health-check

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when running, interpreting, or acting on Salesforce Security Health Check results — reading the score, understanding risk categories, evaluating specific settings, creating or importing a custom baseline, querying the Tooling API programmatically, or planning remediation from findings. Triggers: 'security health check score', 'health check failing settings', 'custom baseline', 'remediate health check findings', 'fix risk'. NOT for org hardening implementation, permission model design, or broad baseline config beyond what Health Check directly measures.

network-security-and-trusted-ips

8
from PranavNagrecha/AwesomeSalesforceSkills

Configure and audit Salesforce network security controls — trusted IP ranges (org-wide Network Access), login IP ranges on profiles, CSP Trusted Sites for Lightning components, CORS allowlists for external JavaScript, and TLS requirements — and troubleshoot login-blocked-by-IP or CSP violation errors. NOT for org-wide session settings, MFA configuration, or real-time Transaction Security Policies.

guest-user-security

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when hardening the Experience Cloud guest user profile, controlling unauthenticated access to records and Apex, or investigating data exposure through guest SOQL. Covers object permissions, sharing model enforcement for unauthenticated users, and Apex execution context. NOT for Experience Cloud site creation (use Experience Cloud skills) or for authenticated external user security (use security/experience-cloud-security).

guest-user-security-audit

8
from PranavNagrecha/AwesomeSalesforceSkills

Auditing the security posture of an Experience Cloud (Community) site's Guest User. Covers the post-Spring '21 secure-by-default lockdown (object permissions removed, sharing rule grants required for any access), the Guest User profile permissions to remove (View All Data, Modify All Data, Manage Users, etc.), guest sharing rules, the Run-As-Guest test, OWASP A01 (Broken Access Control) mapping, and the standard set of leakage vectors (Apex with `without sharing`, Aura / LWC `@AuraEnabled` methods, public-site Visualforce, REST endpoints under `/services/apexrest`). NOT for Experience Cloud authenticated user setup (see experience/experience-cloud-user-management), NOT for general Salesforce profile design (see admin/profile-permset-design).

experience-cloud-security

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when configuring access controls, sharing, or site security for authenticated or guest Experience Cloud (community) users: external OWD, Sharing Sets, Share Groups, CSP, clickjack protection, guest user record access. NOT for internal sharing model configuration (use sharing-and-visibility).

connected-app-security-policies

8
from PranavNagrecha/AwesomeSalesforceSkills

Managing OAuth policies, IP relaxation, session security, PKCE, and credential rotation for Salesforce Connected Apps. Use when hardening Connected App security, rotating client secrets, configuring IP restrictions, or requiring high-assurance sessions. NOT for basic Connected App setup or creation. NOT for OAuth flow implementation (use oauth-flows-and-connected-apps).

api-security-and-rate-limiting

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when configuring, auditing, or troubleshooting API rate limits, Connected App OAuth scope restriction, Connected App IP restrictions, API session policies, or API usage monitoring in a Salesforce org. Trigger keywords: 'API rate limit', '429 error', 'OAuth scope restriction', 'Connected App IP restriction', 'API usage monitoring', 'concurrent API limits', 'Bulk API limits'. NOT for OAuth flow implementation, token exchange mechanics, or general Connected App setup — use security/oauth-flows-and-connected-apps for those.

omnistudio-security

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing OmniStudio security across OmniScripts, Integration Procedures, DataRaptors, custom LWCs, Apex actions, guest-user exposure, and outbound HTTP actions. Triggers: 'OmniStudio security', 'guest user omniscript', 'DataRaptor CRUD FLS', 'OmniStudio Apex security', 'HTTP action data exposure'. NOT for general portal identity architecture or generic Apex security reviews when OmniStudio is not the main surface.

lwc-security

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing Lightning Web Components for DOM safety, Lightning Web Security boundaries, third-party library handling, and secure server-side data access from LWC. Triggers: 'innerHTML in lwc', 'Lightning Web Security', 'document.querySelector', 'light DOM security', 'secure apex class for lwc'. NOT for org-wide sharing architecture or Apex-only security reviews when no LWC surface is involved.