Connected Apps
Vulkro checks every *.connectedApp-meta.xml (SFDX) and *.connectedApp (MDAPI) file. A Connected App defines how an outside system gets OAuth access to your org.
vulkro-sf scan ./force-app # metadata in source
vulkro-sf org connected-apps --target-org my-prod # the live org
Full OAuth scope (High)
<oauthConfig>
<scopes>Full</scopes>
<scopes>RefreshToken</scopes>
</oauthConfig>
Full gives the token everything the authorizing user can do. Combined with a refresh token, it is the setup behind the 2025 OAuth token-theft breaches. Fix: request only the scopes the integration needs (Api, RefreshToken, OpenId), and add IP restrictions and a short token lifetime in Setup.
Cleartext callback URL (High)
<callbackUrl>http://localhost:8080/oauth/callback</callbackUrl>
The authorization code travels unencrypted and can be intercepted and exchanged for a token (CWE-319). Fix: use https://.
Hardcoded consumer secret (Critical) and key (Medium)
<consumerKey>3MVG9YDQS5WtC11oOXz7BaqUgw8</consumerKey>
<consumerSecret>1234567890ABCDEFGHIJ</consumerSecret>
A consumer secret in deployable metadata is a leaked OAuth client secret: anyone with repository or deploy-artifact access holds it. The key is the OAuth client_id, less sensitive but still per org. Fix: remove both elements, configure the app in the target org's Setup, and rotate the leaked secret now.
Deploy-time placeholders are not flagged: {{var}}, ${var}, <% var %>, REPLACE_ME, TODO, TBD, placeholder, your_secret_here, and all-x or all-0 values.
Not covered in source
- A service-to-service app using
Passwordauth where a JWT bearer flow with a certificate fits better. - IP restrictions and login policies set in Setup, not in metadata. The live-org audits (
org connected-apps,org oauth-risk) read these.
See also: Named Credentials, org connected-apps