Skip to main content

A read-only Salesforce security audit: what a live-org scan can see, and what it cannot

Most Salesforce security questions are not about code. Who holds Modify All Data? Which connected apps still have refresh tokens? Can a guest on the public site register an account? Is MFA enforced for everyone, or for everyone except the integration users? The answers live in the org's configuration, and the only way to get them reliably is to read the live org.

Whoever owns production will reasonably ask why a security tool needs any access at all. This post covers what a read-only audit can see, what it cannot, why "read-only" has to be a property of the connection rather than a promise, and how to set up a least-privilege audit user.

Why read-only matters for a production org

An audit tool that can write is one more admin-level identity to protect and one more change-management conversation. A tool that can only read removes most of that discussion.

Read-only should be checkable, not asserted. Three things make it checkable:

  • The tool never holds credentials. If it borrows an existing login, revoking that login revokes the tool.
  • Every read is a query or a retrieve. SOQL SELECT, Tooling API reads and Metadata API retrieves. No DML, no deploy.
  • The audit user's permissions cap the blast radius. Even a buggy tool cannot write through a user who has no write permissions.

The third point matters most: design the audit user so Salesforce enforces read-only.

What a live-org audit can see

Source on disk shows what someone committed; the live org shows what is configured now and who holds it. The areas worth reading:

Area What to look for
Identity Profiles, permission sets, permission set groups and who is actually assigned them. Users with Modify All Data, View All Data, Author Apex or Manage Users. Dormant admins.
Sharing Organization-wide defaults per object, sharing rules, and which objects holding personal data are Public Read/Write.
Session and MFA Session timeout, IP locking, clickjack and CSRF settings, password policy, MFA enforcement and who is exempt.
Experience Cloud Sites, their guest users, guest self-registration, Chatter guest access.
Integrations Connected apps, External Client Apps, OAuth scopes and token usage, named and external credentials.
Packages Installed packages, and which ones are unverified.
Activity LoginHistory, Setup Audit Trail and, with Event Monitoring, event log files.
Salesforce's own view The Security Health Check score and its risk categories.

The guest user deserves its own pass

An Experience Cloud guest user exposure scan is the highest-value read in most orgs, because the guest user needs no credentials at all. It should answer: which sites are live, which have an active guest user, whether guests can self-register or use Chatter, and what the guest profile can read. Our guest user checklist walks through the settings by hand.

Salesforce exposes no queryable field for some site preferences ("guest access by default" is one), so an honest audit reports that setting as not checked, not clean.

What a read-only audit cannot see

  • Business record contents. A posture audit should not read your accounts, contacts or cases. Counting records with SELECT COUNT() is enough to size an exposure.
  • What a guest actually received. Reading the guest profile tells you what is permitted. It does not browse the site as a guest or replay requests.
  • Record-level usage without Event Monitoring. Without event log files, the org does not record who read or exported which records. Any tool that calls a permission "unused" without that evidence is guessing.
  • Code paths. Whether an @AuraEnabled method running without sharing hands a guest someone else's records is a source analysis question. The live org tells you who can call it; the source tells you what it does. You want both.
  • Anything the audit user cannot read. A read that fails for lack of permission must be reported as not covered, never as a pass.

How Vulkro for Salesforce reads a live org

vulkro-sf org commands borrow your Salesforce CLI login. They run sf data query, sf project retrieve start and sf api request rest against the alias you name, so the OAuth token stays in the sf CLI's own store and Vulkro never handles credentials. The analysis runs on your machine; org data is not sent to us.

sf org login web --alias my-prod
vulkro-sf org status     --target-org my-prod
vulkro-sf org report     --target-org my-prod -O posture.html
vulkro-sf org guest-live --target-org my-prod

org guest-live reports guest self-registration (SF-GUEST-LIVE-001), active guest users (SF-GUEST-LIVE-002) and Chatter guests (SF-GUEST-LIVE-003). A site whose guest user cannot be resolved is reported as unknown (SF-GUEST-LIVE-004), not as clean. Other audits include org perms, org effective-perms, org sharing-rules, org session, org mfa, org connected-apps, org oauth-risk, org packages, org integration-users and org health-check. With the source checked out, vulkro-sf scan ./force-app --org my-prod adds live-org facts to the attack paths: a path whose grant nobody holds drops one severity step, and a path whose grant is widely held, whose guest user owns records, or whose app is in use goes up one step.

Setting up a least-privilege audit user

Do not audit as your own System Administrator. A dedicated user makes the scope reviewable and revocable.

  1. Create a dedicated user, for example vulkro-audit@yourcompany.com, with the Minimum Access - Salesforce profile.
  2. Assign one permission set holding only what the reads need. Vulkro's Vulkro read permission set contains:
    • API Enabled
    • View Setup and Configuration
    • View Roles and Role Hierarchy (Salesforce requires it alongside View Setup)
    • View All Users
    • View Health Check
    • View Event Log Files
    • Modify Metadata Through Metadata API Functions
  3. Leave out Manage Users unless you want the login history checks. It is a powerful permission and most audits do not need it.
  4. Sign in to sf as that user and point every org command at that alias.

The Metadata API item looks odd on a read-only list: Salesforce requires it for any Metadata API retrieve, even when nothing is deployed. Vulkro only retrieves, and the minimum-access profile grants no object or field writes, which is the actual guarantee.

You can create Vulkro read by hand, or install the Vulkro Connect package, an unmanaged package with that permission set, a Vulkro tab and no code.

Because the login goes through Salesforce's normal browser flow, your existing MFA and login IP rules apply to the audit user like any other. To end access, revoke the Salesforce CLI's OAuth session in Setup or deactivate the user.

Making the audit repeatable

A one-off audit goes stale the next time someone edits a profile. Keep the org report file as evidence for the control owner, and re-run after change windows, because settings drift through Setup rather than source. For scheduled scans across several orgs, Vulkro Cloud uses the same permission set.

FAQ

Can a Salesforce security audit tool work with read-only access?

Yes. Every posture question (permissions, sharing, guest users, connected apps, session and MFA settings) can be answered with queries and metadata retrieves. Salesforce does require Modify Metadata Through Metadata API Functions for any retrieve, so pair it with a minimum-access profile that grants no data writes.

How do I scan Experience Cloud guest user exposure?

Read each site's guest user, self-registration and Chatter guest settings, then review the guest profile's object and field access. vulkro-sf org guest-live --target-org <alias> covers the first part from the live org; a source scan covers which Apex a guest can call.

Is a live-org scan an offline audit tool?

The analysis is local and nothing is sent to Vulkro, but reading a live org needs a network path to Salesforce. For a machine with no network at all, scan retrieved metadata and source on disk instead.

Read the live-org setup guide or see Vulkro for Salesforce, then Start a 14-day trial. Related: anatomy of a guest user data leak.