Skip to content

sf-audit v1.0: New Checks, Configurable Scoring, and Externalized Queries

The audit plugin has grown from 22 checks to 23, added four new threat surfaces, and gained a fully configurable scoring model. Here is what changed and why it matters.

TL;DR

  • v1.0 adds four new security checks targeting the indirect layer (Flows running without sharing, hardcoded credentials in Apex, Guest User access on Experience Sites, and public group over-sharing), growing the total from 22 to 23 checks.
  • The scoring model is now fully configurable via --scoring-config ./my-scoring.json, letting each org tune risk weights, check weights, and grade thresholds to match its specific risk appetite.
  • All SOQL and Tooling API queries have been moved out of TypeScript source into config/queries/soql.json and config/queries/tooling.json, making queries editable without touching application code.

What You'll Learn

  • What the new threat surfaces added in v1.0 are and why they matter
  • How to use --scoring-config to customise the scoring model
  • Where the externalised SOQL and Tooling queries now live
  • How to upgrade from the previous version

The Problem

The original audit covered visible org configuration risk well: who has ModifyAllData, what your password policies look like, which connected apps lack IP restrictions. What it missed was the indirect layer: automation that bypasses record-level sharing, integrations that embed credentials directly in Apex code, guest profiles with write access to sensitive objects, and public groups quietly over-sharing records to all internal users. These vectors accumulate without anyone noticing because they live in Flows, Apex source, Experience Cloud settings, and sharing rules, not in the standard Security Health Check. v1.0 closes those gaps.

Quick Answer

Upgrade with sf plugins install @cclabsnz/sf-audit@latest and run sf audit security --target-org <alias> as before. The four new checks (Flows without sharing, hardcoded credentials, Guest User access, and public group over-sharing) will appear automatically in the report. To customise scoring, copy scoring.sample.json from the plugin, adjust the weights and grade thresholds for your org's risk profile, and pass it with --scoring-config ./my-scoring.json. The externalised queries now live in config/queries/soql.json and config/queries/tooling.json. If you need to adjust a field name or add a filter for your org, you can edit those files directly without touching the TypeScript source. Re-run the audit after upgrading to see the new findings. Most orgs will surface at least one Flow running without sharing on their first v1.0 run.

The first version of sf audit security covered the obvious risk: who has ModifyAllData, what your password policies look like, which connected apps have no IP restrictions. That catches the visible surface.

What it missed was the indirect layer: automation that bypasses sharing, integrations that embed credentials in code, guest profiles with write access to sensitive objects, and public groups quietly over-sharing records. This update closes those gaps.


What's New at a Glance

Area Before After
Security checks 22 23
Scoring model Fixed weights Configurable via --scoring-config
SOQL / Tooling queries Inline in code Externalized to config/queries/
High-risk findings Username only Username + Profile + clickable org link

The Four New Threat Surfaces

The additions share a common theme: they target the automation and integration layer, not just org configuration. Flows, Apex integrations, Experience Sites, and sharing rules all operate outside the standard permissions model. That is exactly where risk accumulates without anyone noticing.

Flows Running Without Sharing

Salesforce Flows have a Run As setting that most teams set and forget. When an autolaunched Flow is configured to run with SystemModeWithoutSharing, it bypasses all record-level security. It sees and can modify every record in the org regardless of the triggering user's permissions.

The check queries active Flow versions via the Tooling API and inspects RunInMode. A null value on an autolaunched Flow is treated as SystemModeWithoutSharing, which matches Salesforce's own default behaviour for that process type.

  • Autolaunched Flows with SystemModeWithoutSharingHIGH. They run silently on record changes or schedules with no user in the loop.
  • Screen Flows with SystemModeWithoutSharingMEDIUM. User interaction reduces the blast radius, but the data exposure is still real.

Remediation: In Flow Builder, change Run As to System Context With Sharing or User or System Context where the flow logic permits it.


Hardcoded Credentials in Apex

The check scans every non-namespaced Apex class for credential patterns, stripping block comments first to reduce false positives. Classes over 100,000 characters are skipped and noted in the output.

HIGH patterns:

  • Hardcoded Bearer tokens: Bearer <token>
  • Hardcoded Basic auth: Basic <base64>
  • API key assignments: api_key = "..." or apiKey: "..."

MEDIUM pattern: raw endpoint URLs that are not covered by a Named Credential or Remote Site Setting:

// Flagged: raw endpoint not registered anywhere
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/v1/data');

This cross-reference is what makes the check non-trivial. Named Credential endpoints and Remote Site URLs are cached from earlier checks in the run and compared against every setEndpoint() call. If the base URL is already registered, it is not flagged. The call is managed. If it is not, that is an untracked integration.


Guest User Access

Guest users are unauthenticated. They represent every anonymous visitor to an Experience Site. The check only runs if there are live sites. If your org has no active Experience Cloud sites, it exits cleanly.

For each live site, the check resolves the guest profile and inspects object permissions for Account, Contact, Case, and Lead:

  • Create or Edit permissions on any of these objects → HIGH
  • Read permissionsMEDIUM

If Health Cloud is installed (detected via the HealthCloudGA package licence), the sensitive object list extends to clinical records: EhrCondition, EhrMedication, EhrProcedure, and EhrObservation. Guest read access to a patient's medication list is a different category of problem from guest read access to Accounts.

The check also inspects sharing rules on the standard share tables (AccountShare, CaseShare, ContactShare, OpportunityShare) for rules that grant access to guest users directly.


Public Group Over-Sharing

Public groups are created for specific access grants and rarely cleaned up. The check queries the standard share tables and looks for RowCause = 'Manual' rows where the group membership includes "All Internal Users", which is effectively an OWD-wide data grant dressed up as a sharing rule.

Checked tables: AccountShare, CaseShare, ContactShare, OpportunityShare. If Health Cloud is installed, HealthCloudGA__EhrCondition__Share is included.

The finding lists the specific groups and objects involved so you can review intent: sometimes broad sharing is deliberate, but it should be a conscious decision, not an artifact of a three-year-old setup task.


Field-Level Security and Scheduled Apex

Field-Level Security: Checks whether sensitive fields (SSN, credit card numbers, and similar patterns by field name) are accessible to profiles beyond System Administrator. FLS misconfigurations are easy to introduce and rarely surface in standard org reviews.

Scheduled Apex: Lists active scheduled jobs and the users they are configured to run as. This becomes relevant the moment that user is deactivated or their permissions change. The job continues to run under a potentially misconfigured or frozen account.

Configurable Scoring

The original scoring model had fixed severity weights. Every org is different. A Health Cloud implementation treats guest user access differently from a Sales Cloud org with no external portals. The --scoring-config flag lets you bring your own weights.

sf audit security --target-org myOrg --scoring-config ./my-scoring.json

The config file has three optional sections. Omit any section to keep the default:

{
  "riskScores": {
    "CRITICAL": 10,
    "HIGH": 7,
    "MEDIUM": 4,
    "LOW": 1,
    "INFO": 0
  },
  "checkWeights": {
    "guest-user-access": 15,
    "hardcoded-credentials": 10,
    "flows-without-sharing": 7,
    "scheduled-apex": 1
  },
  "gradeThresholds": {
    "A": { "minScore": 85, "maxHigh": 0 },
    "B": { "minScore": 70, "maxHigh": 1 },
    "C": { "minScore": 55, "maxHigh": 3 },
    "D": { "minScore": 40, "maxCritical": 0 },
    "F": {}
  }
}

riskScores controls how much each severity level deducts from the starting score of 100.

checkWeights overrides the weight for a specific check, independent of its risk level. A Health Cloud org might increase guest-user-access from the default 10 to 15, or reduce scheduled-apex to near-zero if that is actively monitored elsewhere.

gradeThresholds redefines the grade bands entirely. The existing architecture post noted that any CRITICAL finding results in an immediate Grade F. That behaviour is now configurable via gradeThresholds. The default config preserves it, but you can adjust the thresholds to match your organisation's risk acceptance criteria.

A scoring.sample.json ships with the plugin covering all 23 checks. Copy it, adjust what matters to your org, and pass it with --scoring-config.

Externalized Queries

All SOQL and Tooling API queries now live in two JSON files outside the TypeScript source: config/queries/soql.json and config/queries/tooling.json. Each entry declares its API type, the query or REST path, a description, and an optional fallbackOnError flag:

{
  "activeStandardUsers": {
    "api": "soql",
    "soql": "SELECT Id, Username, ProfileId FROM User WHERE IsActive = true AND UserType = 'Standard'",
    "description": "All active standard users — used by MFA, admin, and inactive checks"
  },
  "profileIpRanges": {
    "api": "soql",
    "soql": "SELECT ProfileId, StartAddress, EndAddress FROM ProfileLoginIpRange",
    "description": "Profile login IP ranges — not queryable in all org configurations",
    "fallbackOnError": true
  }
}

The fallbackOnError flag is worth calling out. Some queries (profileIpRanges being a clear example) are not available in all org configurations. Rather than crashing the audit when the query fails, the registry handles it gracefully and continues. Without this, running the tool in a Developer Edition org or a restricted sandbox would abort the entire run.

The architecture post described the original QueryRegistry as an abstraction that "didn't pull its weight" at the time. That assessment was accurate when the tool ran 22 checks with simple queries. At 23 checks, cross-referencing between checks (Named Credentials vs. Apex endpoints, Health Cloud package detection feeding into three separate checks), and fallback logic, keeping queries inline would mean hunting through 1,500 lines of TypeScript to change a field name. The abstraction has earned its place.


Clickable Findings

High-risk permission findings (ModifyAllData, ViewAllData, AuthorApex, CustomiseApplication) now show each user as a clickable link:

[[email protected] (System Administrator)](https://myorg.lightning.force.com/005XXXXXXXXXXXX)

The link is constructed from sf_instance on the active connection plus the User record ID. In practice this means going from a finding to the user's Setup page in one click rather than copying a username and searching manually.


Running the updated audit against your org? Start with the usage guide for installation and flag reference. The architecture post covers the cache dependency system and scoring model in depth.

Frequently Asked Questions

Q: How do I pass a custom scoring config?

A: Run sf audit security --target-org <alias> --scoring-config ./my-config.json. Copy scoring.sample.json from the plugin as a starting point. It covers all 23 checks with default weights. Omit any section you do not want to override and the plugin will use the built-in defaults for that section.

Q: Where are the SOQL and Tooling query files located?

A: All queries live in config/queries/soql.json and config/queries/tooling.json within the plugin's installed directory. You can find the exact path by running sf plugins --verbose and looking for @cclabsnz/sf-audit. Each entry in the JSON files declares the API type, the query or REST path, a description, and an optional fallbackOnError flag for queries that are not available in all org configurations.

Q: Will existing scoring configs from the previous version still work?

A: Yes. The scoring config format is backward compatible. The three sections (riskScores, checkWeights, gradeThresholds) are all optional. A config created for the previous version will apply its overrides and use defaults for anything not specified, including the four new v1.0 checks.

Q: How do I upgrade to v1.0?

A: Run sf plugins install @cclabsnz/sf-audit@latest. The plugin manager replaces the previous version.

Key Takeaways

  • New Checks: Four new checks target the indirect security layer (Flows running without sharing, hardcoded credentials in Apex, Guest User access on Experience Sites, and public group over-sharing), surfaces that the original version missed entirely.
  • Configurable Scoring: The --scoring-config flag accepts a JSON file with three optional sections (riskScores, checkWeights, gradeThresholds), letting each org tune the audit model to its own risk acceptance criteria without modifying plugin source code.
  • Externalised Queries: Moving all SOQL and Tooling queries into config/queries/soql.json and config/queries/tooling.json means field names, filters, and fallback behaviour can be adjusted by editing JSON rather than hunting through TypeScript source files.

What's Next?

Recommended Reading:

Action Items:

  1. Upgrade: sf plugins install @cclabsnz/sf-audit@latest
  2. Run with default config first to see new findings
  3. Review and customise scoring weights if your org has specific risk priorities

Resources & References

  • [@cclabsnz/sf-audit on npm: npmjs.com/package/@cclabsnz/sf-audit]
  • [sf-audit GitHub repository: search github.com/cclabsnz/sf-audit]
  • [Salesforce Flow security documentation]
  • [Salesforce Guest User security best practices: help.salesforce.com]