sf-connected-app-compliance
v0.3.0
Published
Read-only audit of every Connected App and External Client App in a Salesforce org. Tells you which will fail to authenticate when Salesforce hardens PKCE, refresh-token rotation, and refresh-token validity — and whose publisher owns each fix. Includes a
Maintainers
Readme
sf-connected-app-compliance
Read-only audit of every Connected App and External Client App in a Salesforce org. Tells you which ones will fail to authenticate when Salesforce hardens PKCE, refresh-token rotation, and refresh-token validity — and whose publisher owns each fix.
Why this exists
Salesforce is rolling out mandatory hardening across the OAuth surface:
- PKCE enforcement for the authorization-code flow (docs). Org-wide toggle in Setup → OAuth and OpenID Connect Settings; per-app toggle on each Connected App.
- Refresh Token Rotation as a per-app requirement (docs).
- Refresh-token validity policy — tokens with no expiry will be force-revoked. Configure under Setup → Manage Connected Apps → [app] → OAuth Policies.
- AppExchange partner mandate (May 11, 2026) — partner-owned Connected Apps that don't ship PKCE + refresh-token rotation by that date can be delisted or have their Salesforce interop suspended (Salesforce ISV Partner Community announcement). Their failure becomes your outage.
The tooling Salesforce ships out-of-the-box is a series of Setup pages — each correct in isolation, but none of them together tell you "which of my 100+ apps will actually break, who owns the fix, and how active is each one."
Salesforce's own audit pages
The tool covers the same ground as these Setup pages, plus joins them together:
| Salesforce Setup page | What this tool reads | Link |
|---|---|---|
| Manage Connected Apps | ConnectedApplication (Tooling API) | docs |
| External Client Apps Manager | ExternalClientApplication + ExtlClntAppOauthPlcyCnfg | docs |
| Connected Apps OAuth Usage | OauthToken (last 90d activity) | docs |
| Installed Packages | InstalledSubscriberPackage (publisher attribution) | docs |
| OAuth and OpenID Connect Settings | Settings:OauthOidc metadata | docs |
Further Salesforce reading
- All Release Updates (the auto-enforcement queue) — Setup help
- Connected App OAuth flows reference — docs
- External Client Apps overview — docs
- Spring '26 Release Notes — Connected Apps & OAuth — release notes
What it produces
Will-Break Scan (active in last 90d + break-condition rules failing)
WILL_BREAK 3 (authentication will fail at PKCE / refresh-token enforcement)
UNTRACKED_ACTIVE 27 (active OAuth traffic from apps not in the CA/ECA inventory)
ACTIVE_OK 29 (active and clean)
DORMANT 76 (no OAuth activity in window — breakage moot)
org-wide Require PKCE: OFF
WILL_BREAK MyInternalApp [CA] My_Internal_App authcode active (16 tokens, 3,910 uses, last 2026-04-24)
publisher: Org-internal (custom-built) (match: org-internal)
contact: [email protected]
✗ [BREAKS AUTH] PKCE required for OAuth — isPkceRequired=false (will fail when org-wide or per-app PKCE enforces)
✗ [BREAKS AUTH] Refresh-token rotation enabled — isRefreshTokenRotationEnabled=falseEach app gets:
- A verdict (
COMPLIANT/NEEDS_REVIEW/NON_COMPLIANT) on hardening posture - A will-break call (
WILL_BREAK/ACTIVE_OK/DORMANT) cross-referenced withOauthTokenactivity in the last N days - A publisher attribution (
org-internal,salesforce-native,namespace-exact,namespace-prefix,name-fuzzy, orunknown) — so you know who owns the fix - A rule-by-rule breakdown with break-condition findings flagged
Compliance rules
| Rule | What it checks | Source | Salesforce docs |
|------|---|---|---|
| pkce_required | App has isPkceRequired=true (or auth-code flow not in use, in which case skipped) | <isPkceRequired> in .connectedApp-meta.xml | PKCE |
| refresh_token_rotation_enabled | App has isRefreshTokenRotationEnabled=true (skipped if refresh_token not in scopes) | <isRefreshTokenRotationEnabled> in metadata | Rotate Refresh Tokens |
| refresh_token_policy_explicit | App has an explicit refresh-token policy (not infinite) | <refreshTokenPolicy> in metadata, or RefreshTokenPolicyType for ECAs | Refresh Token Policies |
| permitted_users_admin_approved | App restricts to admin-approved users | OptionsAllowAdminApprovedUsersOnly (CA) / PermittedUsersPolicyType (ECA) | Manage Permitted Users |
| no_guest_code_credential_flow | Guest code-credential flow disabled | OptionsCodeCredentialGuestEnabled / IsGuestCodeCredFlowEnabled | OAuth Authorization Code & Credentials Flow |
| ip_relaxation_enforced | IP relaxation not bypassed | <ipRelaxation> in metadata / IpRelaxationPolicyType | Manage IP Restrictions |
| client_creds_user_set_when_enabled | Client-credentials flow has a designated run-as user | ClientCredentialsFlowUser (ECA) | OAuth Client Credentials Flow |
pkce_required, refresh_token_rotation_enabled, refresh_token_policy_explicit,
and client_creds_user_set_when_enabled are break-condition rules — failure
on any of these means the app's OAuth will fail under enforcement. Other rules
are best-practice posture.
Beyond Connected Apps: --readiness
The Connected App / ECA mandate is one of eight Salesforce security controls
enforced between April and July 2026. The --readiness mode (companion module
mandate-readiness.mjs) assesses the other seven — for the ones that are
measurable from SOQL/metadata — and honestly marks the runtime/client-side ones
as MANUAL with the exact Setup location instead of inventing a check.
node connected-app-compliance.mjs --readiness --org myorg
node connected-app-compliance.mjs --readiness --org myorg --export-csv # per-user remediation list
node connected-app-compliance.mjs --readiness --org myorg --html # executive scorecard
node connected-app-compliance.mjs --readiness --org prod,uat # multi-org verdict diff
node connected-app-compliance.mjs --all --org myorg --check-metadata # both scans in one run
node connected-app-compliance.mjs --doctor --org myorg # preflight: what's queryable| Mandate | Enforce (prod) | How it's assessed | Verdict |
|---|---|---|---|
| Phishing-Resistant MFA for Admins | Jul 1 2026 | Privileged users (MAD/VAD/CustomizeApp/AuthorApex) vs. registered U2F/passkey/built-in methods | READY / GAP |
| MFA for All Internal Users | Jul 20 2026 | Active internal users with no registered verification method (TwoFactorMethodsInfo) | READY / GAP |
| SSO IdP MFA signal (AMR/ACR) | gates M1/M2/M3 | Live LoginHistory.AuthMethodReference / AuthContextClassRef classified into Salesforce's 3-tier model | READY / REVIEW / GAP |
| Bypass-MFA exemption holders | — | Assignments of PermissionsBypassMFAForUiLogins | READY / REVIEW |
| Step-Up Auth on Report Actions | Jul 1 2026 | Report-permission users lacking a native verification method | READY / GAP |
| Step-Up on Anomalous Exports | Jul 13 2026 | ReportExport EventLogFile footprint (ML control is unscoreable) | INFORM |
| Transaction Security Policy (Reports) | Jul 13 2026 | Whether a qualifying ReportEvent TSP exists | READY / DEFAULT_WILL_APPLY |
| Email Domain Verification | live | OrgWideEmailAddress.IsVerified + active DKIM (EmailDomainKey) | READY / GAP |
| Anonymizing-IP block · login-anomaly containment · Mobile SDK | live / Jul | Runtime/client-side — not queryable | MANUAL |
Interpretation caveats baked in: in SSO orgs an IdP AMR/ACR signal can
satisfy MFA for All Users, but not phishing-resistant MFA for admins or
report step-up (those need a Salesforce-native method per user). The admin set
may also include API/service accounts that authenticate via OAuth/JWT rather
than interactive MFA — so the tool splits the M1 gap into interactive admins
vs. API/service accounts (the Interactive column in --export-csv); the
interactive count is the true Jul-1 deadline. --export-csv output contains
usernames and is gitignored by default.
Zero-install, CI, and config
Run without cloning (once published to npm):
npx sf-connected-app-compliance --org myorg --check-metadata --will-break
npx sf-connected-app-compliance --readiness --org myorg --htmlDoctor / graceful degradation. --doctor reports which objects are
queryable before a run. License-gated checks (Event Monitoring for export
footprint, Shield for TSP) degrade to a SKIPPED (reason) verdict instead of
erroring, so the tool runs in any edition. All sf CLI failures surface the
real error (from the CLI's stdout JSON), not the masking update/transpile
warning.
CI gate. A ready-to-use GitHub Action lives at
.github/workflows/compliance.yml: a weekly
scan that fails the build on WILL_BREAK/GAP (the tool's exit code is 2 when
anything will break) and uploads HTML scorecards as artifacts. Store an
sfdxAuthUrl as the SF_AUTH_URL secret.
Config / allowlist. Drop a .sfcompliance.json (or pass --config <file>)
to suppress known-accepted items and tune the IdP token lists — see
.sfcompliance.example.json:
{
"allowlistApps": ["Salesforce CLI"],
"allowlistUsers": ["[email protected]"],
"amrPhishingResistant": ["hwk", "fido", "x509", "passkey", "webauthn"]
}Requirements
Node.js 18+
Salesforce CLI (
sf) installed and authenticated against the target org:sf org login web --alias myorgRead access to:
ConnectedApplication(Tooling API)ExternalClientApplication,ExtlClntAppOauthPlcyCnfgOauthTokenInstalledSubscriberPackage(Tooling API)Settings:OauthOidcmetadata (for org-wide PKCE toggle)ConnectedAppmetadata retrieval (for--check-metadatamode)
A user with
View Setup and Configuration+Customize Applicationtypically has all of these. A Salesforce System Administrator always does.
Install
Option 1 — npx (no install, recommended for one-off scans):
npx sf-connected-app-compliance --org myorg --check-metadata --will-breaknpx will fetch the package the first time and cache it. No global install required.
Option 2 — global npm install (for repeated runs):
npm install -g sf-connected-app-compliance
sf-connected-app-compliance --org myorg --check-metadata --will-breakOption 3 — clone (for contributors):
git clone https://github.com/eddieprignano-ai/sf-connected-app-compliance.git
cd sf-connected-app-compliance
node connected-app-compliance.mjs --org myorgOption 4 — single-file download (no Node package manager):
curl -O https://raw.githubusercontent.com/eddieprignano-ai/sf-connected-app-compliance/main/connected-app-compliance.mjs
node connected-app-compliance.mjs --org myorgUsage
# Quick SOQL-only pass — fast, but PKCE/rotation rules will be skipped
node connected-app-compliance.mjs --org myorg
# Full scan with metadata retrieval — answers "what will break"
node connected-app-compliance.mjs --org myorg --check-metadata
# Just the will-break list (active apps + break-condition fails)
node connected-app-compliance.mjs --org myorg --check-metadata --will-break
# Export to CSV for spreadsheet-driven triage
node connected-app-compliance.mjs --org myorg --check-metadata --csv > report.csv
# Machine-readable JSON for downstream tooling
node connected-app-compliance.mjs --org myorg --check-metadata --jsonFlags
| Flag | Purpose |
|------|---|
| --org <alias> | Target Salesforce org alias (default: $DEFAULT_SALESFORCE_ORG env var) |
| --check-metadata | Retrieve ConnectedApp metadata XML for full PKCE / rotation / refresh-token-policy / OAuth-flow detection. Strongly recommended — without this, the most important rules return SKIPPED. |
| --will-break | Filter output to apps with recent OAuth activity AND a break-condition rule failing |
| --days N | OauthToken usage window (default: 90) |
| --filter <verdict> | Show only COMPLIANT / NEEDS_REVIEW / NON_COMPLIANT |
| --include-managed | Include managed-package ECAs (default: skip) |
| --csv | CSV output |
| --json | JSON output (machine-readable) |
| -h, --help | Show help |
Sample run
$ node connected-app-compliance.mjs --org production --check-metadata --will-break
Connected App + ECA scan -- org: production
found 76 legacy CAs, 5 ECAs
querying OauthToken activity in the last 90 days...
32 distinct apps had OAuth activity
querying InstalledSubscriberPackage inventory...
47 managed packages installed
retrieving org-wide OAuth/OIDC settings...
org-wide OAuth/OIDC settings:
Require PKCE OFF
Block OAuth username/pw OFF
Block OAuth user-agent OFF
Allow code-credential flow OFF
retrieving ConnectedApp metadata for 76 apps...
Will-Break Scan (active in last 90d + break-condition rules failing)
WILL_BREAK 3
UNTRACKED_ACTIVE 27
ACTIVE_OK 29
DORMANT 76
org-wide Require PKCE: OFFHow it answers "will it break"
Two distinct enforcement mechanisms are in play, and the tool models both:
Per-org Release Update (automatic) — Salesforce auto-enforces the PKCE / refresh-token-policy / rotation Release Updates in your org on a published date. Apps with the wrong per-app config fail at
/services/oauth2/tokenwithinvalid_grant. The tool flags these asWILL_BREAKif the app has had recent OAuth activity (no point worrying about an app no one uses).AppExchange partner contractual (May 11, 2026) — Salesforce can suspend non-compliant partner apps' interop with your org. The tool surfaces the publisher of each app via
InstalledSubscriberPackage, plus a fuzzy-match strategy for apps not in the package surface, so you know which partners need outreach.
Publisher attribution
For each app, the tool tries to match a publisher using these strategies in order:
org-internal— App is in yourConnectedApplication/ExternalClientApplicationSOQL surface but has noNamespacePrefix. You built it.namespace-exact— App'sNamespacePrefixmatches an installed package's namespace exactly.name-fuzzy— App name contains an installed package name (e.g., "Adobe Acrobat Sign" OAuth app → "Adobe Acrobat Sign" package).namespace-prefix— App name starts with${namespace}__or${namespace}.salesforce-native— App name matches a known Salesforce-published first-party pattern (Salesforce CLI, Heroku Connect, Customer 360 Data Platform, AI Platform Auth, etc.).unknown— None of the above. These are typically partner-published OAuth-only apps (no managed package), or apps authorized from another Salesforce org. These are your partner-outreach targets for the May 11 deadline.
Limitations
- OauthToken captures user-grant tokens with refresh tokens. JWT-bearer and
client-credentials flows may not always populate it; an app that runs
exclusively as a JWT-bearer integration could appear as
DORMANTeven when active. Cross-reference with your own integration-monitoring telemetry. Settings:OauthOidcis the org-wide PKCE toggle source. On older API versions or with insufficient permissions, retrieval can fail; the tool reports "could not determine" and continues.- Refresh-token rotation for ECAs is not exposed via any queryable field at the time of writing. The tool marks the rule as SKIPPED for ECAs and prompts the user to verify in Setup. If you find a queryable field for this on a newer API version, please file an issue or PR.
- Org-wide settings retrieval requires an SFDX project root. The tool
generates a temp directory for retrieval; if your
sfCLI version doesn't support--output-diroutside a project, you may need to run the tool from inside an SFDX project directory. - Rule heuristics are conservative. When in doubt, the tool prefers SKIPPED over false-FAIL — so the verdict count may understate posture risk. The audit was specifically designed to avoid noise.
Development & contributing
This is a single-file tool — easy to fork, audit, and extend.
git clone https://github.com/eddieprignano-ai/sf-connected-app-compliance.git
cd sf-connected-app-compliance
node connected-app-compliance.mjs --helpPull requests welcome. If you find:
- A new compliance rule worth adding
- A queryable field for ECA refresh-token rotation
- A publisher pattern that misclassifies (false
salesforce-nativeor falseunknown) - An additional org-wide enforcement toggle (e.g., for new Release Updates)
…please file an issue or PR.
Security & privacy
- The tool is read-only. It runs SOQL queries (
sf data query) and retrieves Salesforce metadata (sf project retrieve start). It does not modify any Connected App, ECA, setting, or org configuration. - The tool never sends data anywhere. All output is local stdout / files.
- The tool runs against the org alias you authenticate via
sf org login. It cannot exceed the permissions of the user it runs as. - CSV / JSON output may contain Connected App labels, contact emails, and metadata flags. Treat the output the way you would treat a Setup-page screenshot — internal use unless you've redacted accordingly.
License
MIT — use it freely. Attribution appreciated but not required.
Acknowledgments
Built originally to answer one specific operational question: "if Salesforce auto-enforces PKCE next quarter, how many of our integrations break?" The "will it break" framing — usage signal cross-referenced with break- condition rules — turned out to be the only framing that produced an actionable list rather than a 200-line compliance audit no one would read.
