Skip to main content

Visualforce: pages, components, and controllers

Vulkro scans every *.page and *.component file under your project, and every Apex controller class (*.cls) that is bound to one of those pages. Findings cluster around three classes of issue: reflected and stored cross-site scripting, open-redirect / iframe phishing, and sharing-rule bypass through <apex:commandButton> actions.

Reflected XSS via escape="false"

<apex:outputText escape="false" value="{!$CurrentPage.parameters.q}" />

Visualforce's default merge-field encoding is HTML-safe; opting out with escape="false" reintroduces the entire reflected-XSS surface. Vulkro emits at High severity when the merge expression is $CurrentPage.parameters.* (directly attacker-controllable) and at Medium when it is a controller property (stored XSS shape).

The rule skips merge expressions wrapped in HTMLENCODE(), JSENCODE(), or JSINHTMLENCODE(): those are escaped for their context and are safe even with escape="false".

Reflected XSS inside <script> blocks

Apex merge expressions inside <script> bodies are NOT JavaScript- escaped by the platform. A value containing a quote or </script> breaks out of the JS context.

<script>
var term = '{!$CurrentPage.parameters.term}';
doSearch(term);
</script>

Vulkro emits at High when the merge value is request-derived. Stored inputs emit at Low confidence because we cannot prove the value flows from an attacker-controlled source without dataflow analysis: surface them with vulkro scan --all-confidence.

Dynamic <apex:includeScript> and <apex:iframe>

<apex:includeScript value="{!scriptUrl}" />
<apex:iframe src="{!$CurrentPage.parameters.next}" />

A merge-field-derived script source lets an attacker load arbitrary JavaScript into the page (CWE-829). An iframe with a request-derived src is the canonical open-redirect / phishing surface on a Visualforce page (CWE-601).

For both rules, $Resource.* references are static and safe (skipped). The iframe rule also distinguishes URLENCODE(...) -wrapped values (percent-encoded, XSS-safe but still open-redirect: Low confidence) from raw merge expressions (Medium or High depending on the source).

Controller taint sources

The back-end half of a reflected-XSS pair is a controller property that pulls its value from ApexPages.currentPage().getParameters().get(...) without an escape helper. Any Visualforce page that binds this controller and renders the property inherits the taint.

public class SearchCtrl {
public String query {
get { return ApexPages.currentPage().getParameters().get('q'); }
set;
}
}

Vulkro flags the property at Medium severity when the controller is with sharing and High when the controller has no sharing keyword (the property's lifecycle and the sharing posture both feed the visiting user's session, so the combination changes the blast radius).

Wrap the request-parameter read in String.escapeHtml4(...) (or an allow-list) at the controller level. The platform's automatic merge-field encoding does not re-encode an already-tainted property.

<apex:commandButton> on a non-with sharing controller

A button action wired to a controller declared without sharing (or with no sharing keyword) runs in system context for every page visitor. Any DML inside that action bypasses the org's sharing rules.

<apex:page controller="ReportCtrl">
<apex:form>
<apex:commandButton action="{!run}" value="Run report" />
</apex:form>
</apex:page>
public class ReportCtrl {
public PageReference run() {
update [SELECT Id FROM Lead WHERE OwnerId = :userInput];
return null;
}
}

Vulkro emits at Medium with High confidence when the page-bound controller has no with sharing / inherited sharing keyword. Add the keyword to the controller declaration; if a specific action genuinely needs system context, isolate it in a small helper class, document the reason, and add a record-ownership / FLS check before the DML.

What is not covered yet

  • Composite component bindings (<c:custom>) are walked but the rule does not yet follow component-to-component property flow.
  • Lightning Web Security (LWS) policies are out of scope for the Visualforce rules; LWC-specific checks live in vulkro LWC detection.
  • Trusted Site URL validation is configured at the org level and is outside Vulkro's static-analysis surface.