Frontend DOM XSS & CSP Review
Purpose
DOM XSS and CSP misconfiguration remain the dominant client-side attack surface (OWASP A03: Injection and A05: Security Misconfiguration). Most reviews stop at grep-for-innerHTML or "a CSP header exists, ship it." Neither proves anything: a sink match without confirmed taint is noise, and a CSP header with unsafe-inline, a wildcard script-src, or a permissive Trusted Types default policy provides false confidence while remaining fully bypassable. This skill exists so the review stays anchored to confirmed source-to-sink taint flow and directive-level CSP/Trusted Types analysis instead of drifting into either a shallow pattern-match report or a full framework-architecture review.
When to use
Use this skill when the user asks to:
- review code touching
innerHTML,outerHTML,dangerouslySetInnerHTML,v-html,document.write/document.writeln,eval,Function(), orsetTimeout/setIntervalcalled with a string argument, - audit or design a Content-Security-Policy header or
<meta>tag, - roll out or review Trusted Types enforcement (
Content-Security-Policy: require-trusted-types-for 'script',trusted-typesdirective, or aTrustedTypePolicyFactory.createPolicycall), - review third-party script inclusion,
<script src>origins, or subresource integrity for supply-chain/injection risk, - triage a reported XSS finding, bug-bounty submission, or
postMessage-based injection report.
Do not use this skill for:
- server-side template injection or SSRF review with no DOM-sink or CSP component — those are different vulnerability classes outside this skill's scope,
- confirming a finding is actually exploited in production — that requires live penetration testing or a captured exploit, which this skill explicitly does not perform (see Non-negotiables below),
- general component-architecture or state-management review with no security angle — use the relevant framework-architecture skill instead.
Context7 Documentation Protocol
- Resolve each in-scope framework's library ID with
resolve-library-idbefore citing any sink-specific or sanitizer-specific claim (React:/websites/react_dev_reference; Vue:/websites/vuejs_guide; Angular:/websites/angular_devor/websites/angular_dev_guide). - Read
package.jsonfirst to confirm the actual framework and major version in use — sink APIs and sanitizer defaults changed across major versions (e.g., Angular'sDomSanitizer.bypassSecurityTrust*methods, React'sdangerouslySetInnerHTMLcontract, Vue's automatic template escaping vs. explicitv-html). Do not apply one framework's sink semantics to another framework's codebase. - For CSP and Trusted Types directive semantics, Context7 does not reliably surface a maintained CSP-specific library; ground every directive claim in the
official_docsURLs in this skill'smetadata.json(MDN CSP header reference, W3C Trusted Types spec) and label itdocumentation-based. - OpenTelemetry browser instrumentation (
/websites/opentelemetry_io) has no built-in CSP-violation-report receiver or nativereport-to/report-uriingestion pipeline as of the current docs — if a user asks how to observe CSP violations, state this gap explicitly (documentation-based, gap confirmed via Context7) rather than inventing an integration; a custom collector endpoint receiving the browser's native CSP report POST is the documented pattern, not an OpenTelemetry-specific feature. - If Context7 is unavailable for a library in scope, fall back to the
official_docsURLs in this skill'smetadata.jsonand label the claimdocumentation-based, unverified against current release.
Lean operating rules
- First trace whether the sink actually receives attacker-influenceable data (URL params, query strings, API responses that render user-submitted or third-party content,
postMessage,localStorage/sessionStoragewritten by another origin or execution context,document.referrer,window.name) before flagging it as a blocker. A sink match with no confirmed taint path is a lower-severity pattern-only observation, not a finding — state the distinction explicitly in every response. - Never treat CSP header presence alone as a pass. Parse the actual directive values: flag
unsafe-inline,unsafe-eval, a missingobject-src 'none', a missingbase-uri, a wildcard or overly broadscript-src(e.g.,https:alone,*), andstrict-dynamiccombined with a static nonce/hash misconfiguration that defeats its purpose. - Check the Trusted Types default policy's actual transformation logic, not just whether the API is referenced. A default policy whose
createHTML/createScript/createScriptURLcallback returns the input unmodified (a permissive pass-through) defeats the enforcement even thoughtrustedTypes.createPolicy('default', ...)is present in the code. - Use framework-current sink and escape-hatch APIs verified against the project's actual framework version from
package.json, not assumed from memory or from a different framework's conventions (ReactdangerouslySetInnerHTML, AngularDomSanitizer.bypassSecurityTrust*, Vuev-html/innerHTMLrender-function binding). - Check every
postMessageevent listener (window.addEventListener('message', ...)) for explicitevent.originvalidation before treating the handler as safe; an unchecked origin turns any cross-originpostMessagesender into an untraced taint source. - Verify every
<script src>targeting a third-party/CDN origin carries a pairedintegrityhash andcrossorigin="anonymous"attribute (Subresource Integrity); a<script>tag loading from an external origin withintegrityabsent is a confirmed supply-chain finding regardless of whether the origin is currently trustworthy. Also treat any dynamic script-injection path (document.createElement('script')followed byscript.src = <value>) as needing the same scrutiny: if<value>derives from an untrusted/remote config (a tag-manager loader, analytics config endpoint, or any API response), confirm it is checked against an origin allowlist — ideally via a Trusted TypescreateScriptURLpolicy — before treating the injection path as safe, since SRI does not apply to dynamically assignedsrcvalues at all. - Never write, generate, or execute a working exploit payload against a live, staging, or any networked environment. Confirm taint exclusively via static analysis, code reading, and (when available) offline/local reproduction that sends no traffic to a real target — this is a static-review skill (Read/Grep/Glob/local-only Bash).
- Never print, log, or reproduce a discovered secret, token, session identifier, or credential-shaped string in findings output; flag its presence and location, and redact the value itself.
- Load only the reference needed for the concern in scope.
References
Load these only when needed:
- Review workflow and findings contract — use for the step-by-step review procedure, the taint-confirmation decision tree, and the required output shape.
- DOM XSS sink and source taxonomy — load only when the review scope includes a specific sink (
innerHTML,dangerouslySetInnerHTML,v-html,document.write,eval-class API) and needs source-to-sink classification, grounded in the OWASP DOM-Based XSS Prevention Cheat Sheet. - CSP directive review — load only when auditing or authoring an actual CSP header/meta value, directive by directive, grounded in the MDN CSP header reference.
- Trusted Types enforcement review — load only when the review scope includes Trusted Types policy design or
require-trusted-types-forenforcement mode, grounded in the W3C Trusted Types specification. - Third-party script governance and Subresource Integrity — load only when the review scope includes third-party/CDN
<script src>inclusion, tag-manager/marketing-loader script injection, or a dynamicdocument.createElement('script')path fed by remote config, grounded in the MDN Subresource Integrity guide and the MDN CSPscript-srcreference.
Response minimum
Return, at minimum:
- the sink(s) and/or CSP/Trusted Types surface in scope, with confirmed-taint vs. pattern-only status stated explicitly for every sink finding,
- the exact OWASP category id (e.g., A03:2021-Injection) for each confirmed finding,
- CSP/Trusted Types directive-level gap analysis — never a header-presence-only verdict,
- for any third-party/CDN
<script src>or dynamic script-injection path in scope, an explicit SRI gap analysis (integrity/crossoriginpresent-and-matched, or absent and flagged) plus origin-allowlist status for any config-drivensrcvalue, - framework-correct remediation code (sanitizer call, Trusted Types policy definition, or specific CSP directive change) matching the project's confirmed framework/version,
- evidence level per finding (
repo evidence,documentation-based, orinference), - an explicit statement that no live exploit was executed, plus what remains to be manually confirmed (e.g., "confirming this is exploitable in production requires a controlled penetration test, not static review").