The sf CLI handoff
vulkro-sf does not implement its own OAuth flow against Salesforce.
Every live-org subcommand (org status, org perms, org packages)
hands off to the official Salesforce CLI (sf) for the credential
side, then performs the Metadata + Tooling API call directly against
your org with the short-lived access token sf returns. The token
never persists on disk in Vulkro's storage, and it is discarded from
memory the moment the API call completes.
This page documents exactly what happens at each hop, what vulkro-sf
reads, what it never reads, and how to revoke access.
Why we do not manage OAuth ourselves
The sf CLI is the official Salesforce credential store. Salesforce
audits it, ships security updates against it, and trust-roots it at the
platform level. Specifically:
- The OAuth refresh-token grant is stored in the OS-native secure keychain (macOS Keychain, Windows DPAPI, Linux libsecret), not in plain text on disk.
- The access-token refresh dance follows Salesforce's documented rotation policy and respects revocations issued through Setup.
- Multi-org session handling, sandbox vs production endpoint routing, and instance-URL changes are all problems Salesforce already solved.
Reimplementing this layer in vulkro-sf would be a strict downgrade:
more code to audit, a second credential store for the user to revoke
through, and a second blast radius in the event of a compromise. The
handoff to sf is the right architectural decision and the privacy
property comes for free.
What happens when you run vulkro-sf org status
Walking the call chain end-to-end, for the alias my-prod:
- You invoke
vulkro-sf org status --target-org my-prod. vulkro-sfshells out tosfwith a token-fetch command against the named alias. ThesfCLI refreshes the access token if needed (consulting its OAuth refresh-token in the keychain), then returns a short-lived access token and the instance URL.vulkro-sfcalls the Metadata + Tooling API at the instance URL with that access token. The fetch is targeted: a finite set of Metadata API queries (Profile, PermissionSet, SecuritySettings, ConnectedApplication, NamedCredential, etc.) plus Tooling API queries for the entitities the Metadata API does not expose (ApexClass.Body,Flow.Definition, installed package state).- The detectors run against the in-memory metadata payload. No intermediate file is written to disk; the response object is held only for the duration of the scan.
- The token is discarded from
vulkro-sf's memory when the scan exits. No persistence step ever writes the token to~/.vulkro/, to the SQLite findings store, or to any log file.
What gets read at runtime
The complete list of API calls vulkro-sf org may issue. Every entry
is the Metadata API or Tooling API. No SOQL Query API call against
business records. No Bulk API call. No SELECT against any
sObject that contains customer data.
Metadata API queries
Profile(withobjectPermissions,userPermissions,fieldPermissions, license-type derivation).PermissionSet(same shape).PermissionSetGroup.SecuritySettings(session timeout, clickjack, CSRF, HTTPS, password policy, login IP ranges).ConnectedApplication(OAuth scopes, callback URL, refresh-token policy, consumer key).NamedCredentialandExternalCredential.RemoteSiteSetting,CorsWhitelistOrigin,CspTrustedSite.AuthProvider,SamlSsoConfig,LoginFlow,LoginIpRange.Network(Experience Cloud),Site(Force.com Sites),ExperienceBundle.Flow,WorkflowRule,WorkflowOutboundMessage.StaticResource(file metadata only; the body fetch is opt-in).Certificate(key size, algorithm; private-key material itself stays on the server).
Tooling API queries
ApexClass(for sharing-keyword and CRUD/FLS posture).ApexTrigger.InstalledSubscriberPackage,SubscriberPackage,SubscriberPackageVersion.GenAiFunction,GenAiPlanner,GenAiPromptTemplate(for Agentforce posture).User(System Administrator subset only:Username,LastLoginDate,IsActive,Profile.Namefor the dormant-admin check; no email body, no phone number, no PII fields).
That is the complete read surface. The connector emits zero SELECT
against any sObject containing customer business data.
Token revocation
The OAuth grant vulkro-sf uses is the same grant sf itself uses.
Revoking access is the standard Salesforce CLI revocation flow:
- Setup -> Apps -> Connected Apps OAuth Usage.
- Locate Salesforce CLI in the list.
- Click Revoke (or Block).
Once revoked, both vulkro-sf org * and any other sf command against
that org return an authentication error on the next call. To resume,
re-run sf org login web --alias my-prod.
Revocation is also the right answer if a laptop is lost or stolen: the OS keychain holds the refresh token; revoking on the Salesforce side invalidates it everywhere, even if the device is later recovered.
Multi-org workflows
sf org list shows every org sf has a token for, with the alias and
default flag. vulkro-sf accepts any of those aliases as
--target-org:
sf org list
# > ALIAS USERNAME ORG ID ...
# > my-prod [email protected] 00D5g000004XXXXAAA ...
# > my-sandbox [email protected] 00D2y000007YYYYAAA ...
# > my-scratch [email protected] 00DRm0000018ZZZZAAA ...
vulkro-sf org status --target-org my-prod
vulkro-sf org perms --target-org my-sandbox
vulkro-sf org packages --target-org my-scratch
The aliases are local to each laptop's sf configuration. A CI runner
authenticates separately (see GitHub Actions and
GitLab CI for the JWT-grant pattern that fits CI).
What this buys the user
Three properties that fall out of the handoff design:
- Single revocation surface. One Setup page revokes both the
sfCLI andvulkro-sfsimultaneously. No second "where does Vulkro store credentials?" question to answer. - OS-native at-rest encryption. The refresh token lives in the
keychain Salesforce itself recommends;
vulkro-sfadds no second store with different threat properties. - Audit-trail consistency. Every API call from
vulkro-sfshows up in the org's Setup Audit Trail under the same Salesforce CLI entry an admin already knows how to interpret. No new agent identity to triage.
For the broader privacy contract (the full "what is read / what is not" wording, the egress profile, the air-gapped property), see Live-org setup.