Skip to main content

The Aura endpoint: how a guest profile checkbox becomes a data breach

In March 2026, Salesforce Ben reported that attackers had been scanning the internet for /s/sfsites/ endpoints using a modified open-source Aura inspection tool. The group behind it, ShinyHunters, claimed 300 to 400 organisations and said the campaign started in September 2025. Salesforce's position was that the cause was "overly permissive Experience Cloud guest user configurations" and that no platform vulnerability was found.

That position is correct, and it is also the uncomfortable part. The technique was not new. A data security vendor published the method in August 2021 and updated the write-up in July 2025. Everything the 2025 campaign did was described there four years earlier. What changed was not the platform; it was that someone automated the search for sites that had never been audited.

This post explains what the endpoint actually accepts, why one profile decides the outcome, and how to check that profile in source and in the live org before someone else does.

What the endpoint does

Every Experience Cloud site exposes an Aura endpoint at /s/sfsites/aura. It is how Lightning components talk to the server, and it is public by design because the site is public. According to the 2021 research, the endpoint accepts a message parameter containing a list of server-side actions to execute. The browser builds those messages when you click around a page. Nothing stops a script from building them directly.

Two of the actions matter. getObjectInfo returns the describe for an object: its fields, its record types, and whether the current user can read it. Record-listing actions return rows. The research showed that an anonymous caller could enumerate Account, Case, Contact, Lead and User records wherever the guest profile granted read access. The request carries token: undefined, which is what an unauthenticated guest looks like to the server.

There is no exploit in that paragraph. The server does exactly what it was configured to do. The guest user asked for records the guest user was allowed to read, and received them.

Why the guest profile decides everything

The guest user is a real user with a real profile. Everything that user can do on the site, a stranger with a script can do on the endpoint. That makes the guest profile the single configuration that matters.

Salesforce has tightened the floor over time. Since Summer '20, the Secure guest user record access setting can no longer be disabled, so record-level sharing to guests follows stricter rules by default. But the floor is not the ceiling. Object permissions on the guest profile, API Enabled, the Portal User Visibility and Site User Visibility settings, and self-registration are all still choices an admin makes, and each one widens what the endpoint returns.

The Salesforce Ben article lists the mitigations in order: audit the guest profile from zero, set the external organisation-wide defaults to Private and keep Secure guest user record access enabled, uncheck API Enabled on the guest profile, uncheck Portal User Visibility and Site User Visibility, and disable self-registration where the site does not need it. Each item is a checkbox. Each one, left checked, is a row of your CRM that a guest can list.

The same technique, four years later

It is worth sitting with why a published 2021 method worked at scale in 2025.

A guest profile is set up once, when the site launches, usually by whoever built the site rather than by whoever owns security. It then accumulates grants: a developer adds Read on Contact so a component can render a name, an integration project enables API access to debug something, a record type is added to a page. None of those changes appear in a security review because none of them look like one. The site keeps working, and nothing in the org reports that a stranger can now call getObjectInfo on Contact.

The attacker's work, by contrast, is a loop: find /s/sfsites/aura, send a message, read the response. It scales across every public site on the internet with no per-target effort. A configuration that was never listed is one that will eventually be found by someone else's script.

Auditing it in source

The guest profile and the site definition both live in metadata, so the first audit can happen in the repository with no org access.

vulkro-sf scan ./force-app reads the profile and permission set files alongside the Apex. Its least-privilege detectors include SF-GUEST-AURA-001, which fires when an @AuraEnabled method sits in a class granted to the site guest user and does DML, starts asynchronous work or runs dynamic SOQL with no authorization check. That is CWE-862, Missing Authorization, in its Salesforce form: a public method that assumes the caller is trusted because the page was.

vulkro-sf entrypoints ./force-app inventories every surface that an unauthenticated or external caller can reach, so you see the set of @AuraEnabled methods, Visualforce pages and other entry points behind each site. The attack-surface detectors add SF-ATTACK-SURFACE-001, an inventory of Sites and Experience Cloud sites, and SF-ATTACK-SURFACE-002, which flags self-registration where it is enabled.

The breach class map ties the whole item together: "Experience Cloud guest-user exposure, 2023-25" maps to the Profiles and Permission Sets detector, to org guest-live and to vulkro-sf exposure.

Auditing it in the live org

Source tells you what was deployed. The org tells you what is true today, including changes made in Setup that never reached the repository.

vulkro-sf org guest-live --target-org <alias> reads the guest profile and the related settings from the running org and reports what a guest can reach. vulkro-sf exposure gives the externally reachable view across the org. Both run through the Salesforce CLI against an alias you already authorised, so the credential stays in the sf token store and Vulkro never holds it. The live-org setup page lists the permissions the reads need; anything the connected user cannot read is reported as not covered rather than as clean.

One limit is worth stating. The scanner reports what the guest profile and sharing settings allow. It cannot tell you whether a specific record was already read by someone else; that is a question for event monitoring and login history.

What to do this week

  • List every Experience Cloud site in the org and the guest profile attached to each. If you cannot produce the list in ten minutes, that is the first finding.
  • Run vulkro-sf org guest-live --target-org <alias> and read the object permissions it reports for each guest profile.
  • Run vulkro-sf scan ./force-app and triage every SF-GUEST-AURA-001 result: each is a public method trusting its caller.
  • Walk the Salesforce Ben mitigation list against each site: external defaults Private, API Enabled off, Portal and Site User Visibility off, self-registration off where unused.
  • Add the guest profile to the change review for every site deployment, so a grant added in six months is seen.

The live-org setup docs explain the read-only connection and what each report needs. To run it against your own org, start the 14-day trial.