LWC and Aura
Vulkro walks every lwc/<name>/<name>.html, lwc/<name>/<name>.js, and
aura/<name>/<name>.cmp / <name>Controller.js /
<name>Helper.js file in the project. The HTML and template files are
parsed for the framework-specific directives (lwc:dom="manual",
aura:html, aura:unescapedHtml), and the matching JS controllers are
walked for the sinks those directives expose. The detectors cover five
of the AppExchange Top-20 entries (9, 10, 11, 12, 17).
What Vulkro detects
- DOM XSS in LWC: a
<template lwc:dom="manual">block (or any element rendered throughlwc:dom="manual") wired to JS that writeselement.innerHTML = userInputor usesinsertAdjacentHTML(...)without sanitisation. The default LWC template engine escapes interpolated text;lwc:dom="manual"opts out and re-introduces the entire DOM XSS surface. - DOM XSS in Aura:
<aura:unescapedHtml value="{!v.userInput}" />in a.cmpfile. Theaura:unescapedHtmltag bypasses Aura's default escaping; an attacker-controlled attribute renders as HTML. - Lightning Web Security policy gaps: components that bypass the LWS sandbox via
lightning-permission-set-recommendations, an unrestricted<iframe>to a non-Salesforce origin, or a Lightning Message Channel declaredisExposed=truewithout a documented cross-namespace consumer. - CSRF via Aura actions:
@AuraEnabled(cacheable=false)Apex methods that perform DML and are reachable from an Aura controller without an Authorization header or origin check. (Thecacheable=trueshape is read-only and is not flagged.) - PostMessage handler trust:
window.addEventListener('message', handler)in an LWC or Aura JS file where thehandlerbody does not validateevent.originagainst an allowlist before acting onevent.data. - Insecure third-party JS:
<script src="...">referencing a non-Salesforce CDN domain. AppExchange Security Review requires all JS to ship as aStaticResource. - Hardcoded secrets in client code: API keys, OAuth tokens, JWT keys, or service-account passwords as JS string literals in
.jscontroller / helper files. Client-side secrets are shipped to every visitor's browser by definition.
Risk anchors
- AppExchange Top-20 rule 9 (JavaScript not in static resources): the insecure third-party JS detector.
- AppExchange Top-20 rule 10 (SOQL injection): handled in Apex; the Aura controller side is detected when the action method is reachable from an Aura attribute that flows into a dynamic SOQL string.
- AppExchange Top-20 rule 11 (Lightning improper CSS load): a
<link>element loading a CSS file from outside the component, or an inline<style>block in an Aura.cmpis flagged here. - AppExchange Top-20 rule 12 (JavaScript in Salesforce DOM, Classic only): legacy Aura
.cmpfiles that attemptdocument.querySelector('body')outside the component sandbox. - AppExchange Top-20 rule 17 (insecure endpoint): the third-party JS rule plus any LWC
fetch('http://...')call.
The DOM XSS shapes also map to OWASP API Security Top 10 entries API1 and API8, and are the client-side half of the Salesforce reflected-XSS class that the Visualforce page covers on the legacy surface.
Example positive (code that triggers a finding)
<!-- inboxPreview.html -->
<template>
<template lwc:dom="manual">
<div class="preview"></div>
</template>
</template>
// inboxPreview.js
import { LightningElement, api } from 'lwc';
export default class InboxPreview extends LightningElement {
@api emailBody;
renderedCallback() {
this.template.querySelector('.preview').innerHTML = this.emailBody;
}
}
The @api attribute lets any parent component pass a string into
emailBody; the lwc:dom="manual" template opts out of LWC's default
escaping; the renderedCallback writes that string into the DOM via
innerHTML. Vulkro emits at High severity because the source is a
component public attribute (effectively attacker-reachable through any
parent surface).
Example negative (code that does not trigger)
<!-- inboxPreview.html -->
<template>
<template lwc:if={emailBody}>
<div class="preview">{emailBody}</div>
</template>
</template>
// inboxPreview.js
import { LightningElement, api } from 'lwc';
export default class InboxPreview extends LightningElement {
@api emailBody;
}
The template renders emailBody through the LWC engine's default
interpolation (which HTML-escapes the value at runtime) and gates the
render with lwc:if. There is no lwc:dom="manual" block and no
innerHTML write, so no DOM XSS finding emits.
Tuning
-
Confidence is High when the source is
@apior a@wirereturn value and the sink is in the same component file. Medium when the source is read fromlocalStorageorsessionStorage(an attacker who has already executed JS on the origin can populate those). Low when the source is a constant imported from a sibling module. -
Common false positives: documentation components that demonstrate
lwc:dom="manual"with hardcoded HTML. Suppress with:// vulkro:disable-next-line LWC_DOM_XSS reason="static demo content only"this.template.querySelector('.preview').innerHTML = HELLO_HTML; -
PostMessage handler trust does not fire if the handler immediately returns when
event.origin !== 'https://your-org.lightning.force.com'(or a configurable allowlist).
Where to go next
- Apex detectors for the server side of the Aura / LWC pair.
- Visualforce for the legacy escape rules.
- Methodology, section 4 for the full LWS / LWC / Aura rule taxonomy.