lwc-security
Use when designing or reviewing Lightning Web Components for DOM safety, Lightning Web Security boundaries, third-party library handling, and secure server-side data access from LWC. Triggers: 'innerHTML in lwc', 'Lightning Web Security', 'document.querySelector', 'light DOM security', 'secure apex class for lwc'. NOT for org-wide sharing architecture or Apex-only security reviews when no LWC surface is involved.
Best use case
lwc-security is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when designing or reviewing Lightning Web Components for DOM safety, Lightning Web Security boundaries, third-party library handling, and secure server-side data access from LWC. Triggers: 'innerHTML in lwc', 'Lightning Web Security', 'document.querySelector', 'light DOM security', 'secure apex class for lwc'. NOT for org-wide sharing architecture or Apex-only security reviews when no LWC surface is involved.
Teams using lwc-security should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/lwc-security/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How lwc-security Compares
| Feature / Agent | lwc-security | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when designing or reviewing Lightning Web Components for DOM safety, Lightning Web Security boundaries, third-party library handling, and secure server-side data access from LWC. Triggers: 'innerHTML in lwc', 'Lightning Web Security', 'document.querySelector', 'light DOM security', 'secure apex class for lwc'. NOT for org-wide sharing architecture or Apex-only security reviews when no LWC surface is involved.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
Use this skill when the security question crosses the LWC boundary between browser behavior and Salesforce data access. The component may look harmless in markup, but the real risk often lives in direct DOM access, unsafe third-party library integration, or Apex controllers that expose more data than the UI should ever receive.
## Before Starting
- Does the component rely on Lightning Data Service, custom Apex, third-party libraries, or all three?
- Is it using `innerHTML`, `lwc:dom="manual"`, light DOM, or direct DOM queries?
- Will the component run in Experience Cloud, guest-user contexts, or any surface where relaxed assumptions become dangerous?
## Core Concepts
### Template Rendering Is Safer Than Manual DOM Work
Standard LWC template bindings are the safest default because the framework handles rendering and escaping for you. Risk rises sharply when the component bypasses declarative rendering through `innerHTML`, manual DOM insertion, or broad DOM queries.
### Lightning Web Security Protects Isolation, Not Business Logic
LWS exists to isolate namespaces and reduce unsafe access to DOM and globals, but it does not replace application security design. Data exposure can still happen if the component calls Apex that ignores sharing, CRUD, or FLS, or if the app deliberately exposes too much information to the client.
### Light DOM Is A Conscious Security Tradeoff
Light DOM can be appropriate for very specific use cases, but it relaxes encapsulation. Top-level light DOM components are not protected by Lightning Locker or LWS in the same way shadow DOM components are, so the placement and hierarchy of light DOM components matter.
### Secure Apex Is Part Of LWC Security
LWC security is not only about the browser. Apex called by the component must still enforce sharing and object/field access intentionally. Salesforce recommends Lightning Data Service for standard record access because it handles sharing, CRUD, and FLS for you; when Apex is required, the controller must enforce security explicitly.
## Common Patterns
### Pattern 1: Unsafe innerHTML vs Safe Template Rendering
**When to use:** Any time the component needs to display dynamic content — especially content from Apex, user input, or API responses.
**Vulnerable code:**
```javascript
// WRONG — XSS risk: unsanitized HTML injected into DOM
renderedCallback() {
this.template.querySelector('.output').innerHTML =
`<div>${this.recordDescription}</div>`;
}
```
**Fixed code:**
```html
<!-- CORRECT — template binding handles escaping automatically -->
<template>
<div class="output">{recordDescription}</div>
</template>
```
If rich HTML rendering is genuinely required (e.g., Knowledge article bodies), use `lightning-formatted-rich-text` which sanitizes HTML:
```html
<lightning-formatted-rich-text value={articleBody}>
</lightning-formatted-rich-text>
```
**Why not innerHTML:** The LWC template compiler escapes content automatically. `innerHTML` bypasses this entirely. Even if the data looks safe today, a future change to the data source could introduce XSS.
---
### Pattern 2: Secure Apex Controller for LWC
**When to use:** The component needs server-side data that LDS cannot provide (aggregations, cross-object logic, external callouts).
**Vulnerable code:**
```java
// WRONG — no sharing enforcement, no FLS check, exposes all fields
public without sharing class AccountController {
@AuraEnabled
public static List<Account> getAccounts(String searchTerm) {
return [SELECT Id, Name, AnnualRevenue, OwnerId
FROM Account
WHERE Name LIKE :('%' + searchTerm + '%')];
}
}
```
Three problems: `without sharing` ignores record-level access, no FLS check on `AnnualRevenue`, and the `searchTerm` is safe here (bind variable) but the pattern invites SOQL injection if ever refactored to dynamic SOQL.
**Fixed code:**
```java
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List<Account> getAccounts(String searchTerm) {
String safeTerm = '%' + String.escapeSingleQuotes(searchTerm) + '%';
List<Account> results = [
SELECT Id, Name, AnnualRevenue, OwnerId
FROM Account
WHERE Name LIKE :safeTerm
];
// Strip fields the running user cannot see
SObjectAccessDecision decision = Security.stripInaccessible(
AccessType.READABLE, results
);
return decision.getRecords();
}
}
```
**Why this matters:** Every `@AuraEnabled` method is callable by any user who has access to the component's namespace — including Experience Cloud guest users if the component is exposed. The Apex class is the last line of defense.
---
### Pattern 3: DOM Access — Component-Scoped Only
**When to use:** The component needs to read or manipulate DOM elements (focus, scroll, measure).
**Vulnerable code:**
```javascript
// WRONG — reaches outside the component's shadow boundary
handleClick() {
document.querySelector('.slds-page-header').classList.add('hidden');
document.getElementById('globalSearch').value = '';
}
```
**Fixed code:**
```javascript
// CORRECT — queries only within the component's own shadow DOM
handleClick() {
const myHeader = this.template.querySelector('.my-header');
if (myHeader) {
myHeader.classList.add('hidden');
}
}
```
**Why not document.querySelector:** LWS blocks cross-namespace DOM access. Even if it works today in a development org, it will break silently in production when LWS is enforced. Components should only touch elements they own.
---
### Pattern 4: Third-Party Library Loading
**When to use:** A business requirement needs a library (Chart.js, PDF.js, a rich text editor) that is not available as a base component.
**Vulnerable code:**
```javascript
// WRONG — dynamic script injection blocked by CSP; no integrity check
connectedCallback() {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/chart.js';
document.head.appendChild(script);
}
```
**Fixed code:**
```javascript
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import CHARTJS from '@salesforce/resourceUrl/ChartJS';
export default class SalesChart extends LightningElement {
_chartInitialized = false;
async renderedCallback() {
if (this._chartInitialized) return;
this._chartInitialized = true;
try {
await loadScript(this, CHARTJS + '/chart.min.js');
this.initializeChart();
} catch (error) {
this.dispatchEvent(new ShowToastEvent({
title: 'Error', message: 'Chart library failed to load',
variant: 'error'
}));
}
}
}
```
**Why static resources:** Salesforce Content Security Policy blocks inline script creation and external CDN loading. Static resources are scanned during upload and served from the Salesforce domain.
---
### Pattern 5: Apex with `WITH SECURITY_ENFORCED` vs `Security.stripInaccessible`
**When to use:** Choosing between the two FLS enforcement mechanisms.
```java
// Option A: WITH SECURITY_ENFORCED — throws exception if ANY field is inaccessible
@AuraEnabled(cacheable=true)
public static List<Contact> getContacts(Id accountId) {
return [SELECT Id, Name, Email, Phone
FROM Contact
WHERE AccountId = :accountId
WITH SECURITY_ENFORCED];
// If user lacks access to Phone, the ENTIRE query fails with InsufficientAccessException
}
// Option B: stripInaccessible — silently removes inaccessible fields, returns the rest
@AuraEnabled(cacheable=true)
public static List<Contact> getContacts(Id accountId) {
SObjectAccessDecision decision = Security.stripInaccessible(
AccessType.READABLE,
[SELECT Id, Name, Email, Phone
FROM Contact WHERE AccountId = :accountId]
);
return decision.getRecords();
// If user lacks access to Phone, results are returned without Phone — no exception
}
```
Use `WITH SECURITY_ENFORCED` when all queried fields are required for the feature to work. Use `stripInaccessible` when the component should gracefully degrade (show fewer columns).
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Standard record access for forms or detail views | LDS or UI API | Platform-managed sharing, CRUD, and FLS — zero custom security code needed |
| Custom Apex for LWC — all fields required | `with sharing` + `WITH SECURITY_ENFORCED` | Fail-fast; no partial data that confuses users |
| Custom Apex for LWC — graceful degradation | `with sharing` + `Security.stripInaccessible` | Returns what the user can see; component hides missing columns |
| Dynamic content display | Template bindings or `lightning-formatted-rich-text` | Auto-escaping; `innerHTML` is never the answer |
| DOM interaction needed | `this.template.querySelector` only | Scoped to shadow DOM; future-proof for LWS enforcement |
| Third-party library required | Static resource + `platformResourceLoader` | CSP-compliant; version-controlled; auditable |
| Light DOM for Experience Cloud theming | Nest inside a shadow DOM parent; never top-level for sensitive data | Limits exposure surface while enabling CSS penetration |
## Recommended Workflow
Step-by-step instructions for an AI agent or practitioner activating this skill:
1. **Inventory the component surface** — list every data source (LDS, Apex, API callout, static data), every DOM manipulation method used, and whether the component runs in Lightning Experience, Experience Cloud, or both. Flag any `innerHTML`, `lwc:dom="manual"`, `document.querySelector`, or `static renderMode = 'light'` usage.
2. **Audit Apex controllers** — for every `@AuraEnabled` method called by this component, verify: (a) the class declares `with sharing`, (b) FLS is enforced via `WITH SECURITY_ENFORCED` or `Security.stripInaccessible`, (c) dynamic SOQL uses bind variables or `String.escapeSingleQuotes`. If the component is exposed to Experience Cloud, verify guest user access assumptions.
3. **Replace unsafe DOM patterns** — convert any `innerHTML` usage to template bindings or `lightning-formatted-rich-text`. Replace `document.querySelector` / `document.getElementById` with `this.template.querySelector`. Ensure third-party libraries load via `platformResourceLoader` from static resources.
4. **Evaluate light DOM usage** — if `static renderMode = 'light'` is set, confirm it is required (Experience Cloud CSS interop is the main valid reason). Ensure the component does not handle PII, auth tokens, or financial data. Nest light DOM components inside shadow DOM parents.
5. **Test in the deployment context** — verify the component in the actual deployment surface (Lightning app page, Experience Cloud site, mobile). Guest user contexts and community pages have different security boundaries than internal Lightning pages.
6. **Run the checker script** — execute `python3 skills/lwc/lwc-security/scripts/check_security.py` against the component source. Review any flagged patterns.
7. **Cross-check against llm-anti-patterns.md** — before marking complete, verify your output does not match any of the 6 documented anti-patterns in this skill's references.
---
## Review Checklist
- [ ] No `innerHTML` assignments exist outside of a justified, sanitized `lwc:dom="manual"` container.
- [ ] No `document.querySelector` or `document.getElementById` — only `this.template.querySelector`.
- [ ] All `@AuraEnabled` Apex classes declare `with sharing`.
- [ ] All `@AuraEnabled` Apex methods enforce FLS via `WITH SECURITY_ENFORCED` or `Security.stripInaccessible`.
- [ ] No dynamic SOQL without bind variables or `String.escapeSingleQuotes`.
- [ ] Third-party libraries loaded from static resources via `platformResourceLoader`, not CDN/script injection.
- [ ] Light DOM usage is documented, justified, nested inside shadow DOM parent, and does not handle sensitive data.
- [ ] Component tested in actual deployment surface (Lightning, Experience Cloud, mobile) — not just dev org preview.
- [ ] Experience Cloud / guest-user access paths reviewed: Apex methods are not exposing data beyond intent.
## Salesforce-Specific Gotchas
1. **`@AuraEnabled` methods are callable by any user with namespace access, including Experience Cloud guest users** — the Apex class is the security boundary, not the component placement. A component removed from a page layout is still callable if the Apex class is accessible. Use `with sharing` and explicit FLS enforcement in every `@AuraEnabled` method.
2. **`WITH SECURITY_ENFORCED` throws an exception if ANY queried field is inaccessible** — this is fail-fast behavior. If your component should gracefully hide columns the user cannot see, use `Security.stripInaccessible` instead. Mixing them up causes either silent data exposure or broken pages.
3. **`document.querySelector()` may work in dev but break under Lightning Web Security** — LWS enforces namespace-scoped DOM access. Code that reaches outside the component boundary may work today in sandboxes with LWS disabled but will fail when LWS is enforced in production. Always use `this.template.querySelector`.
4. **Light DOM components in Experience Cloud are in the page DOM with no shadow boundary** — any JavaScript on the page (other components, analytics scripts, browser extensions) can read and modify the component's markup. Never use light DOM for components that display or collect PII, authentication tokens, or financial data.
5. **`innerHTML` is not blocked by Locker Service or LWS — it just bypasses sanitization** — unlike `eval()` which Locker blocks outright, `innerHTML` works silently. This makes it more dangerous: code reviews can miss it because there is no runtime error. Grep for `innerHTML` in all LWC JavaScript as a mandatory review step.
6. **Static resources are not automatically updated when you update the library** — uploading a new version of Chart.js as a static resource with the same name replaces the file, but browser and CDN caches may serve the old version. Use versioned filenames or cache-busting strategies.
## Output Artifacts
| Artifact | Description |
|---|---|
| LWC security review | Findings on DOM access, light DOM, third-party libraries, and Apex exposure |
| Safe rendering recommendation | Guidance on template-first rendering and secure alternatives to manual DOM work |
| Server-boundary remediation plan | Changes needed in Apex or LDS usage to reduce data-exposure risk |
## Related Skills
- `apex/soql-security` — use when the supporting Apex data-access layer is the main risk.
- `security/injection-prevention` — use when the question expands into SOQL, SOSL, formula, or broader XSS prevention beyond the component surface.
- `lwc/lifecycle-hooks` — use when the issue is primarily lifecycle misuse rather than security.Related Skills
visualforce-security-and-modernization
Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).
transaction-security-policies
Transaction Security policy creation and configuration: condition builder, enhanced policies, enforcement actions (block, MFA, notification, end session), real-time monitoring mode, and policy troubleshooting. NOT for Event Monitoring log analysis or Shield Event Monitoring setup (use event-monitoring). NOT for Apex testing or debug-log analysis.
security-incident-response
When to use: active or suspected Salesforce org compromise, unauthorized access investigation, attacker containment, forensic evidence collection from EventLogFile/LoginHistory, session revocation, OAuth token cleanup, eradication of attacker persistence, and post-incident recovery verification. Trigger keywords: org compromised, suspicious login, attacker access, session revocation, forensic investigation, breach response, event log forensics, login anomaly investigation, incident response runbook. Does NOT cover general security setup, permission set design, field-level security configuration, or proactive security hardening — those are separate skills. NOT for general security setup.
security-health-check
Use when running, interpreting, or acting on Salesforce Security Health Check results — reading the score, understanding risk categories, evaluating specific settings, creating or importing a custom baseline, querying the Tooling API programmatically, or planning remediation from findings. Triggers: 'security health check score', 'health check failing settings', 'custom baseline', 'remediate health check findings', 'fix risk'. NOT for org hardening implementation, permission model design, or broad baseline config beyond what Health Check directly measures.
network-security-and-trusted-ips
Configure and audit Salesforce network security controls — trusted IP ranges (org-wide Network Access), login IP ranges on profiles, CSP Trusted Sites for Lightning components, CORS allowlists for external JavaScript, and TLS requirements — and troubleshoot login-blocked-by-IP or CSP violation errors. NOT for org-wide session settings, MFA configuration, or real-time Transaction Security Policies.
guest-user-security
Use when hardening the Experience Cloud guest user profile, controlling unauthenticated access to records and Apex, or investigating data exposure through guest SOQL. Covers object permissions, sharing model enforcement for unauthenticated users, and Apex execution context. NOT for Experience Cloud site creation (use Experience Cloud skills) or for authenticated external user security (use security/experience-cloud-security).
guest-user-security-audit
Auditing the security posture of an Experience Cloud (Community) site's Guest User. Covers the post-Spring '21 secure-by-default lockdown (object permissions removed, sharing rule grants required for any access), the Guest User profile permissions to remove (View All Data, Modify All Data, Manage Users, etc.), guest sharing rules, the Run-As-Guest test, OWASP A01 (Broken Access Control) mapping, and the standard set of leakage vectors (Apex with `without sharing`, Aura / LWC `@AuraEnabled` methods, public-site Visualforce, REST endpoints under `/services/apexrest`). NOT for Experience Cloud authenticated user setup (see experience/experience-cloud-user-management), NOT for general Salesforce profile design (see admin/profile-permset-design).
experience-cloud-security
Use when configuring access controls, sharing, or site security for authenticated or guest Experience Cloud (community) users: external OWD, Sharing Sets, Share Groups, CSP, clickjack protection, guest user record access. NOT for internal sharing model configuration (use sharing-and-visibility).
connected-app-security-policies
Managing OAuth policies, IP relaxation, session security, PKCE, and credential rotation for Salesforce Connected Apps. Use when hardening Connected App security, rotating client secrets, configuring IP restrictions, or requiring high-assurance sessions. NOT for basic Connected App setup or creation. NOT for OAuth flow implementation (use oauth-flows-and-connected-apps).
api-security-and-rate-limiting
Use when configuring, auditing, or troubleshooting API rate limits, Connected App OAuth scope restriction, Connected App IP restrictions, API session policies, or API usage monitoring in a Salesforce org. Trigger keywords: 'API rate limit', '429 error', 'OAuth scope restriction', 'Connected App IP restriction', 'API usage monitoring', 'concurrent API limits', 'Bulk API limits'. NOT for OAuth flow implementation, token exchange mechanics, or general Connected App setup — use security/oauth-flows-and-connected-apps for those.
omnistudio-security
Use when designing or reviewing OmniStudio security across OmniScripts, Integration Procedures, DataRaptors, custom LWCs, Apex actions, guest-user exposure, and outbound HTTP actions. Triggers: 'OmniStudio security', 'guest user omniscript', 'DataRaptor CRUD FLS', 'OmniStudio Apex security', 'HTTP action data exposure'. NOT for general portal identity architecture or generic Apex security reviews when OmniStudio is not the main surface.
crm-analytics-security-predicates
Row-level security in CRM Analytics datasets via security predicates — SAQL filter expressions stored on the dataset that apply at query time per running user. Covers the syntax (`'DatasetColumn' operator value`), the `$User.*` context variables, multi-level predicates (role hierarchy + team + region), the performance cost of complex predicates, and the testing discipline (admins bypass predicates by default). NOT for Salesforce Core sharing rules (different runtime), NOT for App / Dashboard / Lens-level access (that's CRM Analytics App sharing, not predicates), NOT for field-level masking inside a dataset (use Encryption + dataset transformations).