Skip to main content

Security where Salesforce developers write code

· 7 min read
Vulkro
Security research

Most Salesforce security feedback reaches a developer at the worst possible moment. It arrives in a review comment two days after the pull request was opened, in a scan report nobody owns, or in a rejection letter from the AgentExchange (formerly AppExchange) Security Review weeks after the class was written. By then the developer is three tickets further on, the context is gone, and a one-line fix has become a small project.

The cheapest moment to fix a missing field-level check is the moment the query is on the screen. That is an argument for putting security analysis in the editor. It is also a list of things the editor version has to get right, or developers will turn it off within a week.

What security in the editor has to get right​

Four properties decide whether an in-editor check helps or becomes noise:

  1. It agrees with CI. If the editor says a class is clean and the pipeline fails it, developers stop trusting the editor. If the pipeline passes what the editor underlines, they stop reading the underlines.
  2. It is quiet. A gutter full of low-confidence warnings teaches people to ignore the colour.
  3. It never edits behind your back. A tool that rewrites code on save is a tool nobody leaves enabled.
  4. It works where the code is. Offline, on a laptop, without sending a customer's source to a service.

Here is what that looks like, file type by file type, in the VS Code extension for Salesforce.

Diagnostics on open and save​

The extension is a thin shell over the same vulkro-sf engine you run from the command line, started as a language server. It covers the files a Salesforce DX project is made of:

  • Apex classes and triggers (.cls, .trigger)
  • Visualforce pages and components (.page, .component)
  • Aura markup and controllers
  • LWC JavaScript and HTML, scoped to the lwc/ folder
  • Flow, permission set and profile metadata

When you open a file, it is painted first with a fast single-file scan, and the project scan follows in the background. When you save, the saved file is analysed again and merged into the project result. Typing does not start a scan; the cached results are republished as you edit. That keeps the editor responsive on large orgs, at a known cost: a finding whose path crosses into another file is refreshed on the next project scan rather than on every save.

Every diagnostic leads with how sure the engine is: proven when the path from the entry point to the risky call is complete, unproven when a hop could not be established, not checked when that code was not analysed. Findings that would block an AppExchange submission say so. Where a finding has a traced data flow, the source, the steps and the sink appear as related information, so the whole path opens in the Problems panel and the peek view.

Hover to explain​

Hovering a finding shows the rule id, the severity, what is wrong and how to fix it, on the line itself. There is no context switch to a dashboard and no search for the rule's documentation. Hovering a class or an entry method shows how it runs and who can reach it, which is often the more useful answer.

"Runs as": the question every Apex review starts with​

Half of reviewing Apex for security is working out, for each class, what it runs as. Is it system mode or user mode? With or without sharing? Does a plain query in it enforce field-level security? Which profiles and permission sets can call it, and is the guest user one of them?

The answer depends on the sharing keyword, the entry point, the caller and, since API 67.0, the class's API version. It is easy to get wrong in your head. So the extension writes it above the class as a CodeLens:

// runs as: system mode without sharing, API 62.0
// granted by: 2 profiles / permission sets, including guest (Portal_Guest)
// API 67.0 upgrade: 3 changes before the bump
public without sharing class InvoiceController {

// runs as: system mode without sharing, API 62.0
@AuraEnabled(cacheable=true)
public static List<Invoice__c> getInvoices(Id accountId) {
return [SELECT Id, Name, Bank_Account__c FROM Invoice__c WHERE Account__c = :accountId];
}
}

(The comments stand in for the lenses the editor draws above each line.) A class that runs without sharing and is granted to the guest profile is visible as exactly that before anyone reads the method bodies. The grant line is read from the project's metadata, and says so when no profile or permission set in the project grants the class; a grant made only in the org is not visible there.

Quick fixes that you apply yourself​

The lightbulb on a finding offers what can safely be offered:

  • Explain opens the rule's long-form explainer.
  • Suppress inserts a // vulkro:disable-next-line <RULE> comment for that line, so the decision is in the code and in review.
  • A deterministic fix, where the engine has a template for the finding and recognises the line's shape completely. A template that does not fully recognise the line refuses to touch it.
  • Fix with AI (review), when you have set up a local model. The proposed patch is shown as a diff, and it is kept only when a fresh deterministic re-scan of the patched class no longer reports the finding and the file still parses.

The language server never writes to your files. Every edit is returned to the editor and applied only when you accept it. The AI runs on a local model by default; using your editor's own model instead is opt-in behind a confirmation, because prompt content can include your Apex, and it is refused when offline mode is set. Either way, AI never changes a finding, a severity or an exit code.

The same findings as CI​

The extension runs the same engine as vulkro-sf scan in your pipeline, so the editor and the build agree. Export report runs the same whole-project scan CI would and saves SARIF (for code scanning) or JSON (for scripts). A severity floor in the editor hides findings below the level you choose without changing what the scanner detects.

Your editor's AI can query Vulkro​

The extension contributes two language-model tools to agent mode, vulkro_sf_scan_file and vulkro_sf_explain_finding, so GitHub Copilot Chat can scan a Salesforce file and explain a rule. It also registers the Vulkro for Salesforce MCP server with the editor, started with offline mode set, so an MCP-aware assistant can scan the project without the scan reaching the network. The assistant gets the finding and the lines, not the whole codebase.

What it does not do yet​

Stated plainly: it does not rescan as you type, only on open and save (or continuously from disk in watch mode). The in-editor org audit covers permission sets; the wider org posture lives in the vulkro-sf org commands. And it is installed by the command line rather than from an extension marketplace.

Try it​

The extension works in VS Code, Cursor, Windsurf and VSCodium. With vulkro-sf installed, one command finds your editor and installs it:

vulkro-sf install-extension

The VS Code extension page covers both editions (one for Salesforce, one for application code), and the extension documentation lists every setting. If you have not installed anything yet, start here.