Salesforce CRM Analytics detection
Vulkro's CRM Analytics detector pack
(src/security/sf_crm_analytics.rs) scans Salesforce CRM Analytics
artifacts (formerly Tableau CRM, formerly Einstein Analytics). The
detector activates when an SFDX project contains *.saql files,
*.wdf.json / *.wdf dataflow definitions, *.tcrm.json /
*.dashboard files, a force-app/main/default/wave/ directory, or a
force-app/main/default/dashboards/ directory.
Rules
| ID | Severity | What it catches |
|---|---|---|
| CRMA-001 | High | SAQL injection in dataset filter. A q = filter q by {{...$user_input...}} shape (or any SAQL template that interpolates a non-literal request-derived value) lets the caller alter the dataset filter at runtime. |
| CRMA-002 | High | Dashboard JSON binding to a tainted source step. A binding rule whose source is a free-text input widget without a static-set or whitelisted-values constraint. Users can pass arbitrary SAQL fragments via the widget. |
| CRMA-003 | High | Dataflow row-level security disabled (rowLevelSecurityFilter = "true") or absent on a dataset whose schema references PII fields. RLS is the only enforcement layer between an analyst with dataset access and the underlying customer data. |
| CRMA-004 | Medium | Lens / dashboard with sharing = "Public" on top of a dataset that references PII-flavoured fields (email, phone, ssn, dateOfBirth). Public sharing on a CRM Analytics asset bypasses the org's normal sharing rules for that asset. |
| CRMA-005 | Medium | Hardcoded SF user / record IDs in dashboard JSON. 15 or 18 character SF IDs that only resolve in one org; the dashboard breaks on every clone / sandbox refresh. |
Pro gate
Pro-only. Gated by license::Feature::CrmAnalytics. The vulkro scan
invocation detects CRM Analytics artifacts in the project and prompts
for a Pro license before running the CRMA-* rules. Native scanners
for the project's other languages run unchanged.
Example finding
Severity: High
Rule: CRMA-001
File: force-app/main/default/wave/Customer_360.saql
Line: 17
Message: CRMA-001: SAQL injection in dataset filter. The `q =
filter q by` clause interpolates `${userInput.region}`,
which derives from a dashboard widget without a
whitelisted-values constraint.