frontend-dom-xss-csp-review
Review frontend source for DOM XSS sinks (innerHTML, dangerouslySetInnerHTML, v-html, document.write, eval-class APIs), verify actual attacker-reachable taint flow, and audit Content-Security-Policy and Trusted Types enforcement for real bypasses rather than header-presence check
Install
npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/frontend-dom-xss-csp-review
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
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").
Files (vanguard-frontier-agentic)
-
references
-
csp-directive-review.md 8 KB
# CSP Directive Review Use this reference only when auditing or authoring an actual Content-Security-Policy header or `<meta http-equiv="Content-Security-Policy">` value. Grounded in the MDN CSP header reference. Do not treat header presence as a pass — every directive must be parsed individually. ## What people get wrong The naive assumption is: > "The response has a `Content-Security-Policy` header, so CSP is handled." Wrong. A CSP header is a set of independent directives, each restricting a different resource category, and a permissive value in any single directive can defeat the protection the other directives appear to provide. The recurring real failure mode: a policy that looks comprehensive (many directives listed) but includes `'unsafe-inline'` or a wildcard in `script-src`, which alone reduces the script-injection protection to near zero regardless of how strict the other directives are. ## Officially grounded directive semantics (MDN) - **`script-src`** — controls valid sources for `<script>` elements, inline event handlers, and `javascript:` URLs. `'unsafe-inline'` allows inline `<script>` blocks and inline event-handler attributes, which defeats the primary purpose of CSP against DOM XSS: an injected `<script>` tag or `onerror` attribute would otherwise be blocked by the browser, but with `'unsafe-inline'` present it executes normally. `'unsafe-eval'` permits `eval()`, `Function()`, and string-argument `setTimeout`/`setInterval` to execute — combining this with an `eval`-class sink finding from `references/dom-xss-sink-source-taxonomy.md` means CSP provides zero mitigation for that specific finding. - **`strict-dynamic`** — when present, propagates trust from a script loaded with a valid nonce or hash to the scripts it dynamically inserts, and (per spec behavior) causes host-source and scheme-source expressions in `script-src` to be ignored by browsers that support it. Effective only when paired with a per-response, unpredictable nonce or a hash allowlist; a static, hardcoded nonce value defeats it because an attacker can read and reuse the same nonce. - **`object-src`** — controls `<object>`, `<embed>`, and `<applet>` sources. MDN explicitly recommends `object-src 'none'` as a baseline hardening step for most applications, because plugin content can execute script outside the page's normal script-loading path and is rarely needed by modern applications. - **`base-uri`** — restricts URLs usable in a `<base>` element. A missing `base-uri` directive lets an attacker who can inject any HTML (even in a context otherwise well-protected by `script-src`) insert a `<base href="https://attacker.example/">` tag that silently rewrites all relative URLs on the page, redirecting script/resource loads to an attacker-controlled origin. - **`default-src`** — the fallback for any resource-type directive not explicitly specified. A restrictive `default-src` (e.g., `'self'`) is not sufficient on its own if `script-src` is separately specified with a permissive value — explicit directives always override `default-src` for their resource type, so `default-src` alone does not prove `script-src` is safe. - **`frame-ancestors`** — controls which origins may embed the page in a `<frame>`/`<iframe>`/`<object>`. Distinct from clickjacking-only `X-Frame-Options`; `frame-ancestors 'none'` or a specific allowlist is the CSP-native replacement and takes precedence when both are present. - **`require-trusted-types-for`** and **`trusted-types`** — CSP-level enforcement hooks for the Trusted Types API; see `references/trusted-types-enforcement.md` for the full review of these two directives specifically. ## Non-negotiable design rules ### 1. Parse every directive value individually — do not grade the policy as a whole A policy with 8 well-configured directives and 1 permissive `script-src 'unsafe-inline'` is not "mostly good." State the single permissive directive as its own confirmed finding at the severity that directive's gap implies, independent of how strict the others are. ### 2. `'unsafe-inline'` in `script-src` is a confirmed finding whenever DOM XSS sinks are also in scope If the review scope includes both a CSP audit and a DOM-sink review, and the CSP has `'unsafe-inline'` in `script-src`, note explicitly that CSP provides no mitigation for any confirmed HTML-context sink finding in the same review — the two findings compound rather than one substituting for the other. ### 3. A wildcard or overly broad `script-src` source is equivalent to no restriction for practical purposes `script-src https:` (any HTTPS origin) or `script-src *` allows loading and executing script from effectively any external origin, including an attacker-controlled one reachable over HTTPS. Treat this the same severity as `'unsafe-inline'` for a script-injection finding. ### 4. `strict-dynamic` correctness depends on nonce generation, not directive presence Do not clear a `strict-dynamic` configuration as a pass without confirming the nonce is generated fresh per response (typically server-side, per-request) rather than hardcoded in a template or CSP meta tag. A static nonce in source is trivially readable and reusable by an attacker, providing no protection despite `strict-dynamic` being present. ### 5. CSP is a defense-in-depth layer, not a substitute for fixing a confirmed DOM XSS sink Never recommend "add CSP" as the sole remediation for a confirmed unsanitized sink finding from `references/dom-xss-sink-source-taxonomy.md`. The sink itself must be fixed (sanitizer call or removal of the dynamic-code-execution pattern); CSP reduces blast radius if a sink is missed elsewhere, it does not replace fixing the one found. ## Minimal safe policy shape (illustrative, not a template to copy verbatim) ``` Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{PER-RESPONSE-RANDOM-VALUE}' 'strict-dynamic'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; require-trusted-types-for 'script'; ``` Anti-pattern (common in the wild — do not approve): ``` Content-Security-Policy: default-src * 'unsafe-inline' 'unsafe-eval'; ``` This is functionally equivalent to no CSP for script-injection purposes: it permits inline scripts, `eval`-class execution, and loading script from any origin. ## Verification targets - Locate the effective policy: grep server/middleware config, framework security headers config, or the rendered HTML `<meta http-equiv="Content-Security-Policy">` tag. - Split the policy string on `;` and parse each directive-value pair independently; flag every occurrence of `'unsafe-inline'`, `'unsafe-eval'`, a bare wildcard `*`, or a scheme-only source (`https:`) in `script-src` or `default-src`. - Confirm `object-src 'none'` and `base-uri` are present; if absent, this is a confirmed finding even if no other directive is misconfigured. - If `strict-dynamic` is present, grep the server-side code that renders the nonce value to confirm it is generated per-request (e.g., via a cryptographically random value), not a fixed string. - If a CSP `report-to`/`report-uri` endpoint is configured, note that browser-native CSP violation reports are a distinct mechanism from typical observability instrumentation (`documentation-based, gap confirmed via Context7`: OpenTelemetry's browser SDK/instrumentation packages have no built-in CSP-violation-report receiver as of current docs) — a custom collector endpoint must ingest the browser's native report POST; do not assume an existing OpenTelemetry pipeline captures these without a purpose-built receiver. ## When to push back Push back if the user asks to: - treat CSP header presence as sufficient without directive-level review, - add `'unsafe-inline'` "temporarily" to unblock a deploy without a tracked follow-up to remove it — this is a confirmed finding regardless of stated intent to revisit it, - rely on `X-Frame-Options` alone in a CSP review scope when `frame-ancestors` is unset — the two are not equivalent and `frame-ancestors` should be reviewed explicitly, - treat "we added CSP" as the fix for a confirmed unsanitized DOM XSS sink instead of fixing the sink directly. -
dom-xss-sink-source-taxonomy.md 7.8 KB
# DOM XSS Sink and Source Taxonomy Use this reference only when the review scope includes a specific sink match (`innerHTML`, `dangerouslySetInnerHTML`, `v-html`, `document.write`, an `eval`-class API, or a dynamic attribute/URL binding). Grounded in the OWASP DOM-Based XSS Prevention Cheat Sheet's source/sink model. ## What people get wrong The naive assumption is: > "I found `innerHTML` in a grep, so this is an XSS finding." Wrong. OWASP's own DOM XSS model requires two confirmed elements, not one: a **source** (attacker-reachable input) and a **sink** (a DOM API that can execute the value as code or markup), connected by an actual data-flow path with no sanitization in between. A sink with no reachable source is not exploitable. A source with no sink it reaches is not exploitable either. The recurring real failure mode is treating the sink match alone as the finding, or treating "we sanitize somewhere" as proof the specific traced path is clean. ## Officially grounded sink classes (OWASP DOM XSS Cheat Sheet) - **HTML-context sinks** — assignment to `innerHTML`, `outerHTML`, `insertAdjacentHTML`, jQuery's `.html()`, React's `dangerouslySetInnerHTML`, Vue's `v-html` or an `innerHTML` render-function/JSX binding, Angular's `[innerHTML]` binding without going through `DomSanitizer`. These parse the assigned string as HTML/DOM, so any embedded `<script>`, event-handler attribute (`onerror`, `onload`), or `javascript:` URL in an unsanitized value executes. - **JavaScript-execution sinks** — `eval()`, `new Function(...)`, `setTimeout(string, ...)`, `setInterval(string, ...)`, `execScript` (legacy IE). These execute the string argument directly as code; there is no markup step to sanitize around, so any confirmed attacker-reachable string reaching these is a direct code-execution finding. - **URL-context sinks** — assignment to `location`, `location.href`, `location.replace()`, `window.open()`, dynamic `<a href>`/`<script src>`/`<iframe src>` bindings. A `javascript:` or `data:` scheme in an unvalidated attacker-reachable value executes on interaction (click, navigation, or load, depending on the element). - **Document-write sinks** — `document.write()`, `document.writeln()`. Parses the argument as HTML into the document at call time; same risk class as HTML-context sinks but with the added hazard of executing during initial page parse, before most runtime sanitization layers are wired up. - **jQuery/legacy DOM sinks** — `.html()`, `.append()`, `.prepend()`, `.after()`, `.before()`, `.replaceWith()` when passed a string (these route through the same HTML-parsing path as `innerHTML`); `.attr()` when setting an event-handler or `href`/`src` attribute from an attacker-reachable value. ## Officially grounded source classes (OWASP DOM XSS Cheat Sheet) Treat all of the following as attacker-reachable sources unless proven otherwise for the specific application: - `location.*` (`href`, `search`, `hash`, `pathname`) — URL components are fully attacker-controlled; a victim can be sent a crafted link. - `document.referrer` — controlled by whatever page linked to the current one. - `document.cookie` — if the application itself writes attacker-influenceable values into cookies (e.g., echoing a query param into a cookie), reading it back is a source. - `window.name` — persists across navigations within a tab and is attacker-settable by a page that opens or navigates the target window. - `postMessage` payloads (`event.data`) — attacker-reachable from any origin unless the receiving handler validates `event.origin`. - `localStorage`/`sessionStorage` — attacker-reachable if written by another script/extension in the same origin, or if the value itself originated from one of the sources above and was persisted. - API/network responses that echo user-submitted or third-party-submitted content (a comment system, a CMS with contributor accounts, a product-review feed, any endpoint whose stored data originated from user input at some point upstream) — not automatically trusted just because the immediate call site is "just an API response." Not attacker-reachable by default: literal strings in source files, values from the application's own build-time configuration with no runtime mutation path, and content authored exclusively through a trusted internal CMS with no public or user-submission path anywhere upstream. ## Non-negotiable design rules ### 1. A sink match without a completed source trace is a pattern-only observation, not a finding State this distinction explicitly in every response. Do not let a sink count (e.g., "found 12 uses of `dangerouslySetInnerHTML`") stand in for 12 findings. ### 2. Trace through every intermediate transform, not just the assignment site A value can pass through a template-string concatenation, a markdown renderer, a `JSON.parse`, or a component-prop chain before reaching the sink. Each hop must be checked: does it neutralize the attacker-controlled portion, or does it pass it through unmodified (or re-introduce it, e.g., a markdown renderer that itself allows raw HTML passthrough)? ### 3. A sanitizer call must sit on the exact traced path "This codebase imports DOMPurify" is not evidence for a specific finding. The sanitizer call must be reachable on the specific hop-by-hop path between the confirmed source and the confirmed sink. ### 4. JavaScript-execution sinks (`eval`, `Function()`, string-arg `setTimeout`/`setInterval`) have no sanitization escape hatch Unlike HTML-context sinks, there is no "sanitize the string first" pattern that makes passing attacker-reachable data to these safe while still executing it as code. The only safe fix is removing the dynamic-code-execution pattern entirely (e.g., replacing `setTimeout(userString, 1000)` with `setTimeout(() => knownFunction(parsedArgs), 1000)`). ### 5. Do not conflate framework auto-escaping with sink safety React's JSX text interpolation, Vue's `{{ }}` interpolation, and Angular's default property binding all auto-escape by design — but this protection applies only to the framework's normal rendering path, not to any of the explicit escape-hatch sinks listed above. Confirming "the component mostly uses JSX text nodes" does not clear a `dangerouslySetInnerHTML` call three lines later. ## Verification targets - Grep for each sink pattern listed above across the review scope (`innerHTML`, `outerHTML`, `insertAdjacentHTML`, `dangerouslySetInnerHTML`, `v-html`, `document.write`, `document.writeln`, `eval(`, `new Function(`, string-argument `setTimeout`/`setInterval`, jQuery `.html()`/`.append()`-family calls). - For each match, grep backward through the enclosing function/component for the variable's assignment, prop origin, or API-response parsing site. - Grep for a sanitizer import (`dompurify`, `sanitize-html`, or an equivalent project-specific utility) and confirm the call site sits on the traced path, not merely present in the file or module. - Grep for `addEventListener('message'` / `.onmessage` and confirm an `event.origin` check exists inside every handler whose `event.data` reaches a sink in scope. ## Adversarial checklist Before clearing a sink as not a finding, answer these: - What is the literal origin of the value reaching the sink — a source-file literal, a prop, a computed value, store state, or an API response? - Does any hop in that trace involve content any user (current or otherwise) previously submitted, or any of the attacker-reachable sources listed above? - Is there a named sanitizer or Trusted-Types transform visible on the exact traced path, or only "a sanitizer exists somewhere in this codebase"? - For a JavaScript-execution sink specifically: is there any dynamic-code-execution path at all reachable from attacker-controlled input, regardless of sanitization — because sanitization does not clear this sink class? If any answer is unclear, the finding is confirmed and defaults to HIGH — do not soften it to "worth double-checking." -
third-party-script-governance.md 10.4 KB
# Third-Party Script Governance and Subresource Integrity Use this reference only when the review scope includes a third-party/CDN `<script src>`, a tag-manager or marketing-loader script injection path, or a dynamic `document.createElement('script')` call fed by remote/untrusted config. Grounded in the MDN Subresource Integrity (SRI) guide and the MDN CSP `script-src` reference (standard-based: W3C Subresource Integrity specification; documentation-based: MDN SRI and CSP guides). This is a supply-chain concern distinct from the sink-taint review in `references/dom-xss-sink-source-taxonomy.md` — the question here is not "does tainted data reach a sink" but "can the code that *executes* on this page be silently substituted by a compromised or malicious third party." ## What people get wrong The naive assumption is: > "It's just an analytics/marketing snippet from a reputable CDN, and it's loaded over HTTPS — that's safe enough." Wrong. HTTPS proves transport confidentiality and that the response came from the domain named in the URL; it proves nothing about what that domain currently serves. A compromised CDN account, a hijacked DNS record, a malicious dependency update on the vendor's side, or a MITM on a network the vendor's own infra trusts can all cause the *exact same URL* to serve attacker-controlled JavaScript that executes with full access to the page's origin — cookies, DOM, `localStorage`, everything. Subresource Integrity exists specifically to close this gap: it lets the browser verify the fetched bytes match a hash pinned by the page author, independent of which origin served them. ## Officially grounded facts (MDN Subresource Integrity + CSP) - **SRI is a browser-enforced hash check** (standard-based, W3C SRI spec / MDN): when a `<script>` or `<link>` element carries an `integrity` attribute, the browser computes a cryptographic digest of the fetched resource and refuses to execute/apply it if the digest does not match. The `integrity` value supports SHA-256, SHA-384, or SHA-512, expressed as `<algorithm>-<base64-hash>`, and multiple space-separated hashes can be listed as fallbacks. - **`integrity` requires a paired `crossorigin` attribute** (documentation-based, MDN SRI guide): for a cross-origin `<script src>`, the browser only performs the SRI comparison if the fetch is made in CORS mode, which requires `crossorigin="anonymous"` (or `"use-credentials"` when appropriate) on the element. An `integrity` attribute present without a `crossorigin` attribute on a cross-origin resource does not get verified as expected — the two attributes must be reviewed as a pair, not independently. - **A broad CSP `script-src` allowlist does not substitute for SRI** (documentation-based, MDN CSP `script-src` reference): CSP's `script-src` restricts *which origins* may serve script; it says nothing about whether the *content* served by an allowed origin is the content the developer intended. `script-src https://cdn.example.com` or the broader `script-src https:` both permit the browser to execute whatever the named origin(s) currently return — if that origin is compromised, CSP does not detect it, because CSP checks origin, not content hash. Only SRI (or Trusted Types origin/content enforcement) closes that gap. - **Dynamically created `<script>` elements do not get SRI verification for free** (documentation-based, MDN `HTMLScriptElement`): setting `script.integrity` on a script element created via `document.createElement('script')` is supported by the platform, but the far more common real-world pattern — a tag-manager or marketing loader that builds a `<script>` element and sets `.src` to a URL sourced from a remote config/API response, then appends it to the DOM — frequently omits `integrity` entirely, because the loader does not know the hash of a URL it only learns at runtime. In that shape, the safeguard has to shift from a hash check to an **origin allowlist check** performed in code before the `src` is ever assigned. - **Trusted Types `createScriptURL` is the platform hook for enforcing that allowlist** (standard-based, W3C Trusted Types spec): when CSP's `require-trusted-types-for 'script'` is active, the platform requires any `HTMLScriptElement.src` assignment (and other script-URL sinks) to go through a `TrustedScriptURL` produced by a registered policy's `createScriptURL` callback. A policy that validates the incoming URL's origin against a fixed allowlist before returning it — and throws or rewrites otherwise — is the documented way to make a remote-config-driven script loader safe. A policy whose callback returns the input unmodified is a permissive pass-through and provides no protection, per the general Trusted Types pass-through caveat already covered in `references/trusted-types-enforcement.md`. ## Non-negotiable design rules ### 1. Every third-party/CDN `<script src>` must carry both `integrity` and `crossorigin`, or be a confirmed finding Grep every `<script src="https://...">` (or `<link rel="...">` for stylesheets/modulepreload where applicable) pointing at a different origin than the page itself. Absence of `integrity` is a confirmed supply-chain finding regardless of the vendor's current reputation — SRI reviews the *mechanism*, not the *vendor*. `integrity` present without `crossorigin` on a cross-origin element is also a confirmed finding: the pairing is required for the check to actually run. ### 2. A dynamic `document.createElement('script')` path fed by remote config needs an origin-allowlist check, not just SRI If a config value, API response, or tag-manager payload supplies the URL assigned to `script.src`, SRI is not the applicable control (the hash is not known ahead of time). Instead confirm the code validates the URL's origin against a fixed, statically defined allowlist before assignment — ideally enforced structurally via a Trusted Types `createScriptURL` policy under `require-trusted-types-for 'script'`, not merely as an inline `if` check that a future refactor could silently drop. ### 3. Tag-manager and marketing-loader injection is a first-class supply-chain path, not an edge case Analytics tags, marketing pixels, and A/B-testing snippets routinely fetch a remote configuration document and then dynamically inject one or more `<script>` elements based on it. Treat this pattern as in-scope for review whenever present: identify where the config originates (first-party API vs. third-party vendor endpoint), whether that endpoint is authenticated/integrity-protected in its own right, and whether the resulting script URLs are constrained to an expected set of origins before injection. ### 4. A broad or wildcard `script-src` in CSP compounds an SRI gap — call out both, do not let one substitute for the other If the CSP audit (see `references/csp-directive-review.md`) finds `script-src` scoped broadly (e.g., a wildcard subdomain like `https://*.cdn.example.com`, or `https:` alone) *and* the third-party scripts loaded under that policy lack `integrity`, state both findings explicitly and note they compound: CSP is not narrowing which origins can serve script, and SRI is not verifying what those origins actually return. ### 5. `'unsafe-inline'` defeats third-party script governance the same way it defeats sink review If `'unsafe-inline'` is present in `script-src`, any injected inline `<script>` block — including one written by a compromised third-party loader — executes regardless of SRI or origin-allowlist controls applied to `<script src>` elements. Note this compounding relationship rather than treating SRI review and the `'unsafe-inline'` CSP finding as unrelated. ## Safe idiom shapes (illustrative, not a template to copy verbatim) Static third-party script with SRI: ```html <script src="https://cdn.example.com/analytics.js" integrity="sha384-BASE64_HASH_PLACEHOLDER" crossorigin="anonymous"> </script> ``` Dynamic script injection with an origin-allowlist enforced via Trusted Types: ```js const allowedScriptOrigins = ['https://cdn.example.com']; const scriptPolicy = trustedTypes.createPolicy('vendor-script-loader', { createScriptURL(url) { const parsed = new URL(url, location.href); if (!allowedScriptOrigins.includes(parsed.origin)) { throw new Error('Blocked script URL outside allowlist: ' + parsed.origin); } return url; }, }); const script = document.createElement('script'); script.src = scriptPolicy.createScriptURL(configFromRemote.scriptUrl); script.crossOrigin = 'anonymous'; document.body.appendChild(script); ``` Anti-pattern (common in the wild — do not approve): ```html <script src="https://cdn.example.com/analytics.js"></script> ``` ```js const script = document.createElement('script'); script.src = configFromRemote.scriptUrl; // no allowlist check, no Trusted Types policy document.body.appendChild(script); ``` Both anti-patterns execute whatever the named origin (or config-supplied URL) currently returns, with no mechanism to detect substitution. ## Verification targets - Grep every `<script src="http` (or `https`) across templates/rendered HTML for a cross-origin target; confirm `integrity` and `crossorigin` are both present on each match. - Grep for `document.createElement('script')` and trace every `.src =` assignment on the resulting element back to its data source; if the source is a config object, API response, or tag-manager payload, confirm an origin-allowlist check or a Trusted Types `createScriptURL` policy sits between the untrusted value and the assignment. - Cross-reference any CSP `script-src` finding from `references/csp-directive-review.md` — a broad allowlist there compounds with a missing-SRI finding here. - If `require-trusted-types-for 'script'` is active, confirm the registered policy's `createScriptURL` callback actually validates origin rather than returning the input unmodified (same pass-through caveat as `references/trusted-types-enforcement.md`). ## When to push back Push back if the user asks to: - approve a third-party `<script src>` because "the vendor is reputable" without `integrity`/`crossorigin` present — reputation is not a substitute for a browser-enforced hash check, - treat a broad CSP `script-src` allowlist as sufficient protection against a compromised or malicious third-party script served from an allowed origin, - ship a tag-manager or marketing-loader integration that injects scripts from a remote-config-supplied URL with no origin-allowlist check "because it's just analytics", - add a Trusted Types `createScriptURL` policy that returns the input URL unmodified and call the supply-chain risk mitigated. -
trusted-types-enforcement.md 8.5 KB
# Trusted Types Enforcement Review Use this reference only when the review scope includes Trusted Types policy design or `require-trusted-types-for`/`trusted-types` CSP directive enforcement. Grounded in the W3C Trusted Types specification. ## What people get wrong The naive assumption is: > "The code calls `trustedTypes.createPolicy(...)`, so Trusted Types is protecting this application." Wrong. Trusted Types is an opt-in enforcement mechanism with two independent conditions that must both hold: (1) the CSP `require-trusted-types-for 'script'` directive must actually be set so the browser enforces the requirement at all, and (2) every policy's `createHTML`/`createScript`/`createScriptURL` callback must perform a real transformation (sanitize or reject), not pass the input through unmodified. Code that creates a policy but never sets the enforcing CSP directive provides no protection — Trusted Types objects become optional convenience wrappers, and raw strings can still reach `innerHTML` and other sinks unchecked. A policy whose callback returns its input unchanged provides no protection either, even with enforcement active. ## Officially grounded rules (W3C Trusted Types spec) - **Trusted Types only restricts assignment to specific "injection sink" DOM APIs** (e.g., `Element.innerHTML`, `Element.outerHTML`, `Document.write`, `Range.createContextualFragment`, `eval`-family via `TrustedScript`, and `<script src>`/similar via `TrustedScriptURL`) when `require-trusted-types-for 'script'` is set. Without that CSP directive, these sinks continue to accept plain strings exactly as before — Trusted Types objects can still be created and used, but they are not required. - **The `trusted-types` CSP directive restricts which named policies may be created**, and whether an unnamed/default policy or duplicate policy names are permitted. A missing `trusted-types` directive combined with `require-trusted-types-for 'script'` still enforces the sink restriction, but allows any script running on the page (including an injected one, before enforcement blocks further injection) to create its own policy — restricting allowed policy names closes this gap. - **A policy named `'default'` is special**: per spec, when a string is assigned directly to a Trusted-Types-guarded sink (bypassing an explicit policy call), the browser invokes the `'default'` policy if one is registered, implicitly converting the string. This is a compatibility escape hatch, not a security boundary — a permissive `'default'` policy that echoes its input unmodified reintroduces exactly the unrestricted-sink behavior Trusted Types is meant to close. - **`createHTML`, `createScript`, and `createScriptURL` are ordinary JavaScript callbacks** with no built-in sanitization. The spec does not provide a default sanitizer; the application must supply one (e.g., calling DOMPurify inside `createHTML`) or reject/throw for unsafe input. A callback that simply returns its argument is spec-compliant but provides zero security value. ## Non-negotiable design rules ### 1. Confirm the enforcing CSP directive is actually set, not just that policy-creation code exists Grep the effective CSP (see `references/csp-directive-review.md` for locating it) for `require-trusted-types-for 'script'`. If absent, every `trustedTypes.createPolicy` call in the codebase is inert for enforcement purposes — state this as a confirmed finding, not as "Trusted Types is partially implemented." ### 2. Read every policy callback body, not just the `createPolicy` call site For each `createHTML`/`createScript`/`createScriptURL` callback, confirm it performs a real transformation: a sanitizer call (e.g., DOMPurify), a strict allowlist check with rejection/throw on mismatch, or an equivalent safe transform. A callback body of `(input) => input` or equivalent pass-through is a confirmed finding regardless of enforcement being active. ### 3. Treat a permissive `'default'` policy as high severity Because the `'default'` policy silently intercepts any direct string assignment to a guarded sink, a permissive `'default'` policy re-opens every sink in the application at once — it has broader blast radius than a single permissive named policy used in one call site. Flag this distinctly and at higher severity than a single permissive named-policy finding. ### 4. Restrict allowed policy names via the `trusted-types` directive when feasible If the `require-trusted-types-for` directive is set but the `trusted-types` directive is absent or overly permissive (e.g., no allowlist), note this as a gap: any script that executes before full lockdown (or a supply-chain-compromised dependency) can register its own arbitrarily permissive policy. ### 5. Do not treat "uses a framework with built-in Trusted Types support" as sufficient without confirming the actual runtime configuration Some frameworks and bundlers offer opt-in Trusted Types integration, but it must be explicitly enabled and configured (policy name, sanitizer wiring) in the project's actual config — its mere availability in the framework does not mean the reviewed application has turned it on. Confirm via the project's actual build/runtime configuration, not the framework's general capability. ## Minimal safe implementation pattern ```javascript // Enforcing CSP directive (see csp-directive-review.md): // Content-Security-Policy: require-trusted-types-for 'script'; trusted-types app-html; import DOMPurify from 'dompurify'; const htmlPolicy = trustedTypes.createPolicy('app-html', { createHTML: (input) => DOMPurify.sanitize(input), createScript: () => { throw new Error('script creation not permitted by this policy'); }, createScriptURL: (input) => { const allowed = ['https://cdn.trusted-example.com/']; if (!allowed.some((prefix) => input.startsWith(prefix))) { throw new Error('script URL not on allowlist'); } return input; }, }); element.innerHTML = htmlPolicy.createHTML(untrustedInput); ``` Anti-pattern (policy exists but provides no protection — do not approve): ```javascript // No 'require-trusted-types-for' directive set anywhere — this policy is never enforced. const policy = trustedTypes.createPolicy('default', { createHTML: (input) => input, // pass-through: zero sanitization }); ``` ## Adversarial checklist Before clearing Trusted Types as properly enforced, answer these: - Is `require-trusted-types-for 'script'` actually present in the effective CSP (header or meta tag), or does only application code reference the Trusted Types API? - For every `createPolicy` call in scope, does the `createHTML`/`createScript`/`createScriptURL` callback perform a real sanitize-or-reject transform, or does it return its input unmodified? - Is there a `'default'` policy registered? If so, is its transform equally strict as the named policies, given its broader implicit-invocation blast radius? - Is the `trusted-types` directive present to restrict which policy names may be created, or can any script on the page register an arbitrarily permissive policy? - Does this confirmed enforcement actually cover the specific sink(s) flagged in a co-occurring `references/dom-xss-sink-source-taxonomy.md` finding, or is the policy scoped to a different part of the application? If any answer reveals a gap, state it as a confirmed finding — do not describe partial implementation as "Trusted Types is in place." ## Verification targets - Grep the effective CSP for `require-trusted-types-for` and `trusted-types` directive values. - Grep the codebase for `trustedTypes.createPolicy` and read every callback function body in full. - Grep specifically for a policy named `'default'` and treat any match as requiring the same scrutiny as every named policy, plus the broader-blast-radius note above. - Cross-reference confirmed Trusted-Types-guarded sinks against the sink list in `references/dom-xss-sink-source-taxonomy.md` to confirm enforcement scope actually matches the sinks the review is concerned with. ## When to push back Push back if the user asks to: - treat the presence of `trustedTypes.createPolicy` calls as sufficient without confirming the enforcing CSP directive is set, - approve a policy callback that passes input through unmodified because "we'll add sanitization later," - register a permissive `'default'` policy as a convenience to avoid updating call sites — this is a confirmed high-severity finding given its implicit, page-wide invocation, - skip verifying the `trusted-types` directive's policy-name allowlist because "only our own code creates policies" — a supply-chain-compromised dependency is exactly the scenario this allowlist defends against. -
workflow-and-output.md 6.8 KB
# Review Workflow and Findings Contract Use this reference for the step-by-step review procedure and the required output shape. Load the sink-taxonomy, CSP, and Trusted Types references only for the specific concern the code under review actually raises. ## Prerequisites - Read `package.json` to confirm the framework(s) and major version(s) in scope. Sink APIs, sanitizer defaults, and CSP/Trusted Types tooling (e.g., a framework's CSP-nonce middleware) differ by framework and version — do not apply one framework's semantics to another's codebase. - Identify whether the review scope is sink-focused (specific file/component), CSP-focused (a header/meta value or server config emitting one), Trusted Types-focused, or all three. Scope the review to what was actually asked; do not expand a single-sink review into a full CSP audit unless requested. ## Workflow 1. **Enumerate every candidate sink in scope.** Grep for `innerHTML`, `outerHTML`, `dangerouslySetInnerHTML`, `v-html`, `document.write`, `document.writeln`, `eval(`, `new Function(`, and `setTimeout`/`setInterval` calls whose first argument is a string literal built from a variable (not a function reference). See `references/dom-xss-sink-source-taxonomy.md`. 2. **Trace each candidate sink's data source backward.** Follow the value through props, variables, function returns, and API responses to its origin. Classify the origin as attacker-reachable (URL/query params, `postMessage`, request bodies, third-party API responses that echo user or third-party input, `document.referrer`, `window.name`, `localStorage`/`sessionStorage` writable by another origin or script context) or fully origin-controlled (a literal in source, a value from the app's own trusted build-time config with no runtime mutation path). 3. **For each attacker-reachable sink, check for a sanitizer call on the exact traced path.** A sanitizer import or call present elsewhere in the codebase does not clear the finding — it must sit between the untrusted origin and the sink on the specific path reviewed. 4. **If the review scope includes CSP, locate the effective policy.** Find the `Content-Security-Policy` HTTP header, the `<meta http-equiv="Content-Security-Policy">` tag, or the server/framework config that emits either. Parse directive-by-directive per `references/csp-directive-review.md`. 5. **If the review scope includes Trusted Types, locate the enforcement mode and default policy.** Check for `require-trusted-types-for 'script'` and `trusted-types` directives, and read every `trustedTypes.createPolicy(...)` call's callback bodies per `references/trusted-types-enforcement.md`. 6. **Check every `postMessage` listener in scope for origin validation.** An `addEventListener('message', handler)` with no `event.origin` check inside `handler` is itself an untraced taint source for any sink it feeds. 7. **Produce ranked findings** using the output contract below. ## Decision tree (taint confirmation) - Sink match, traced data source is attacker-reachable, no sanitizer/Trusted-Types call on the exact path → **confirmed finding**, severity per sink class (script-execution sinks — `eval`, `Function()`, `innerHTML`/`dangerouslySetInnerHTML`/`v-html` with script-capable markup, `document.write` — default HIGH; URL/attribute-injection sinks depend on reachability, MEDIUM-to-HIGH). - Sink match, traced data source is attacker-reachable, but a named sanitizer call (e.g., DOMPurify) or a Trusted Types policy transformation visibly sits on the exact path → **not a finding**; state this explicitly with the sanitizer/policy name and location rather than omitting the sink from the review. - Sink match, traced data source is fully origin-controlled with no attacker-reachable input anywhere in the trace → **not a finding**, but record it explicitly ("reviewed, not a finding, because X") rather than silently dropping it — a later code change could introduce a reachable path into the same sink. - Sink match, trace could not be completed within the review scope (e.g., the origin is in a third-party dependency not under review, or requires runtime state unavailable statically) → **pattern-only observation**, not a confirmed finding; state exactly what would be needed to complete the trace. - CSP directive parsed and found permissive (`unsafe-inline`, `unsafe-eval`, missing `object-src 'none'`, missing `base-uri`, wildcard `script-src`) → **confirmed finding** regardless of whether a sink finding co-occurs; CSP gaps are findings in their own right. - Trusted Types default policy callback returns input unmodified or is absent while `require-trusted-types-for 'script'` is not set → **confirmed finding**; enforcement is not active even if policy-creation code exists. ## Output contract Every response from this skill must return: 1. **Scope** — the file(s)/sink(s), CSP surface, and/or Trusted Types surface actually reviewed. 2. **Ranked findings** — each with file:line (or CSP directive name / policy name), defect category (`dom-xss-sink`, `postmessage-origin`, `csp-directive-gap`, or `trusted-types-gap`), the concrete source-to-sink trace or directive-level gap, and a fix sketch matching the confirmed framework's documented pattern. 3. **Confirmed-taint vs. pattern-only status** for every sink finding — never presented as equivalent to a confirmed finding. 4. **Sanitizer/Trusted-Types status per sink finding** — an explicit statement of whether a sanitizer or Trusted Types transform is present on the traced path; never inferred. 5. **OWASP category id** for every confirmed finding (e.g., A03:2021-Injection, A05:2021-Security Misconfiguration). 6. **Evidence level per finding** — `repo evidence`, `documentation-based`, or `inference`. 7. **Verdict** — approve / approve-with-notes / block. 8. **Explicit no-live-exploit statement** — confirm no exploit payload was run against any live or staging target, and state what remains to be manually confirmed (e.g., "confirming exploitability in production requires a controlled, authorized penetration test"). 9. **Open questions or out-of-scope items** not covered by this review pass. ## When to push back Push back if the user asks to: - treat a sink match as a confirmed finding without a completed source-to-sink trace — a grep hit is not a finding, - clear a sink finding because "we sanitize elsewhere in the app" with no sanitizer call visible on the specific traced path, - treat CSP header presence alone as sufficient without directive-level parsing, - run an exploit payload against a live or staging system to "prove" a finding — this skill does not perform live penetration testing; confirm via static trace and recommend authorized live testing separately if needed, - downgrade a confirmed script-execution sink finding to informational because "it's probably fine" — this skill's default for confirmed script-execution sinks is HIGH.
-
-
metadata.json 1.8 KB
{ "id": "frontend-dom-xss-csp-review", "name": "Frontend DOM XSS & CSP Review", "type": "skill", "provider": "frontend", "harnesses": [ "claude-code", "cursor", "codex", "gemini", "kiro", "other" ], "summary": "Skill for hunting DOM XSS sinks and reviewing CSP/Trusted Types enforcement in frontend code, mapping every finding to source-to-sink taint flow and an OWASP category with framework-specific remediation; also reviews third-party/CDN script inclusion for Subresource Integrity gaps and supply-chain injection risk.", "source_type": "original", "official_docs": [ "https://owasp.org/www-project-top-ten/", "https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html", "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy", "https://w3c.github.io/trusted-types/dist/spec/", "https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity" ], "security_notes": "This skill's entire scope is security-critical: DOM XSS sinks are the dominant client-side injection vector and CSP/Trusted Types misconfiguration provides false confidence when treated as header-presence-only. Every finding requires confirmed source-to-sink taint evidence or is labeled pattern-only, never treated as a confirmed finding on pattern match alone. Never reproduces discovered secrets/tokens verbatim in output. Never executes exploit payloads against live/staging systems; source-to-sink findings are static taint analysis plus manual confirmation notes, not live penetration testing. Static-review-only skill: Read/Grep/Glob/local-only Bash, no network egress to any target.", "last_verified": "2026-07-02", "path": "skills/frontend/frontend-dom-xss-csp-review", "author": "github: VincentChuWaiChow", "version": "0.1.0" } -
SKILL.md 9.7 KB
--- name: frontend-dom-xss-csp-review description: Review frontend source for DOM XSS sinks (innerHTML, dangerouslySetInnerHTML, v-html, document.write, eval-class APIs), verify actual attacker-reachable taint flow, and audit Content-Security-Policy and Trusted Types enforcement for real bypasses rather than header-presence checks, with framework-specific sink guidance loaded progressively. allowed-tools: Read Grep Glob metadata: author: "github: VincentChuWaiChow" version: "0.1.0" updated: "2026-07-02" category: security --- # 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()`, or `setTimeout`/`setInterval` called 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-types` directive, or a `TrustedTypePolicyFactory.createPolicy` call), - 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-id` before citing any sink-specific or sanitizer-specific claim (React: `/websites/react_dev_reference`; Vue: `/websites/vuejs_guide`; Angular: `/websites/angular_dev` or `/websites/angular_dev_guide`). - Read `package.json` first to confirm the actual framework and major version in use — sink APIs and sanitizer defaults changed across major versions (e.g., Angular's `DomSanitizer.bypassSecurityTrust*` methods, React's `dangerouslySetInnerHTML` contract, Vue's automatic template escaping vs. explicit `v-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_docs` URLs in this skill's `metadata.json` (MDN CSP header reference, W3C Trusted Types spec) and label it `documentation-based`. - OpenTelemetry browser instrumentation (`/websites/opentelemetry_io`) has no built-in CSP-violation-report receiver or native `report-to`/`report-uri` ingestion 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_docs` URLs in this skill's `metadata.json` and label the claim `documentation-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`/`sessionStorage` written 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 missing `object-src 'none'`, a missing `base-uri`, a wildcard or overly broad `script-src` (e.g., `https:` alone, `*`), and `strict-dynamic` combined 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`/`createScriptURL` callback returns the input unmodified (a permissive pass-through) defeats the enforcement even though `trustedTypes.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 (React `dangerouslySetInnerHTML`, Angular `DomSanitizer.bypassSecurityTrust*`, Vue `v-html`/`innerHTML` render-function binding). - Check every `postMessage` event listener (`window.addEventListener('message', ...)`) for explicit `event.origin` validation before treating the handler as safe; an unchecked origin turns any cross-origin `postMessage` sender into an untraced taint source. - Verify every `<script src>` targeting a third-party/CDN origin carries a paired `integrity` hash and `crossorigin="anonymous"` attribute (Subresource Integrity); a `<script>` tag loading from an external origin with `integrity` absent 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 by `script.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 Types `createScriptURL` policy — before treating the injection path as safe, since SRI does not apply to dynamically assigned `src` values 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](references/workflow-and-output.md) — use for the step-by-step review procedure, the taint-confirmation decision tree, and the required output shape. - [DOM XSS sink and source taxonomy](references/dom-xss-sink-source-taxonomy.md) — 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](references/csp-directive-review.md) — 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](references/trusted-types-enforcement.md) — load only when the review scope includes Trusted Types policy design or `require-trusted-types-for` enforcement mode, grounded in the W3C Trusted Types specification. - [Third-party script governance and Subresource Integrity](references/third-party-script-governance.md) — load only when the review scope includes third-party/CDN `<script src>` inclusion, tag-manager/marketing-loader script injection, or a dynamic `document.createElement('script')` path fed by remote config, grounded in the MDN Subresource Integrity guide and the MDN CSP `script-src` reference. ## 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`/`crossorigin` present-and-matched, or absent and flagged) plus origin-allowlist status for any config-driven `src` value, - 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`, or `inference`), - 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").
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.