[{"data":1,"prerenderedAt":775},["ShallowReactive",2],{"guide-xss-prevention-html-encoding":3},{"id":4,"title":5,"body":6,"date":766,"description":767,"extension":768,"meta":769,"navigation":153,"path":770,"readingTime":771,"seo":772,"stem":773,"__hash__":774},"guides\u002Fguides\u002Fxss-prevention-html-encoding.md","XSS Prevention with HTML Encoding",{"type":7,"value":8,"toc":744},"minimark",[9,14,18,21,25,28,39,42,58,62,65,70,115,119,188,191,195,269,272,313,317,349,356,360,392,396,487,490,494,497,501,504,510,514,517,521,528,532,535,613,622,626,673,677,705,709,729,733,741],[10,11,13],"h2",{"id":12},"what-is-xss","What Is XSS?",[15,16,17],"p",{},"Cross-site scripting (XSS) happens when an application includes untrusted data in a web page without proper escaping. An attacker injects a script into a page viewed by other users, stealing cookies, hijacking sessions, or performing actions on behalf of the victim.",[15,19,20],{},"XSS consistently ranks among the OWASP Top 10. It is not a server vulnerability — it is an output vulnerability. The problem occurs at the moment data leaves your application and enters the browser.",[10,22,24],{"id":23},"how-html-encoding-stops-xss","How HTML Encoding Stops XSS",[15,26,27],{},"HTML encoding converts characters that have structural meaning in HTML into safe entity references:",[29,30,35],"pre",{"className":31,"code":33,"language":34},[32],"language-text","\u003Cscript>alert('xss')\u003C\u002Fscript>\n→ &lt;script&gt;alert(&#39;xss&#39;)&lt;\u002Fscript&gt;\n","text",[36,37,33],"code",{"__ignoreMap":38},"",[15,40,41],{},"The browser now renders the encoded characters as visible text instead of interpreting them as markup. The script never executes.",[15,43,44,45,49,50,53,54,57],{},"This works because entity resolution happens ",[46,47,48],"em",{},"before"," the browser builds DOM nodes. Once ",[36,51,52],{},"\u003C"," becomes ",[36,55,56],{},"&lt;",", it can never retroactively become a tag opener.",[10,59,61],{"id":60},"context-matters-one-encoding-does-not-fit-all","Context Matters: One Encoding Does Not Fit All",[15,63,64],{},"HTML entity encoding only protects you inside HTML text content and quoted attribute values. It fails in every other context.",[66,67,69],"h3",{"id":68},"html-text-content-safe-with-entity-encoding","HTML text content — safe with entity encoding",[29,71,75],{"className":72,"code":73,"language":74,"meta":38,"style":38},"language-html shiki shiki-themes github-light github-dark","\u003Cp>Hello, {{ username }}\u003C\u002Fp>\n\u003C!-- If username = \"\u003Cscript>alert(1)\u003C\u002Fscript>\" -->\n\u003C!-- After encoding: \u003Cp>Hello, &lt;script&gt;alert(1)&lt;\u002Fscript&gt;\u003C\u002Fp> -->\n\u003C!-- Renders as text. Safe. -->\n","html",[36,76,77,96,103,109],{"__ignoreMap":38},[78,79,82,85,88,91,93],"span",{"class":80,"line":81},"line",1,[78,83,52],{"class":84},"sVt8B",[78,86,15],{"class":87},"s9eBZ",[78,89,90],{"class":84},">Hello, {{ username }}\u003C\u002F",[78,92,15],{"class":87},[78,94,95],{"class":84},">\n",[78,97,99],{"class":80,"line":98},2,[78,100,102],{"class":101},"sJ8bj","\u003C!-- If username = \"\u003Cscript>alert(1)\u003C\u002Fscript>\" -->\n",[78,104,106],{"class":80,"line":105},3,[78,107,108],{"class":101},"\u003C!-- After encoding: \u003Cp>Hello, &lt;script&gt;alert(1)&lt;\u002Fscript&gt;\u003C\u002Fp> -->\n",[78,110,112],{"class":80,"line":111},4,[78,113,114],{"class":101},"\u003C!-- Renders as text. Safe. -->\n",[66,116,118],{"id":117},"html-attribute-values-safe-only-when-quoted","HTML attribute values — safe only when quoted",[29,120,122],{"className":72,"code":121,"language":74,"meta":38,"style":38},"\u003C!-- SAFE: quoted attribute -->\n\u003Cinput value=\"{{ encoded_data }}\">\n\n\u003C!-- DANGEROUS: unquoted attribute -->\n\u003Cinput value={{ encoded_data }}>\n\u003C!-- A space in the data breaks out of the value -->\n",[36,123,124,129,149,155,160,182],{"__ignoreMap":38},[78,125,126],{"class":80,"line":81},[78,127,128],{"class":101},"\u003C!-- SAFE: quoted attribute -->\n",[78,130,131,133,136,140,143,147],{"class":80,"line":98},[78,132,52],{"class":84},[78,134,135],{"class":87},"input",[78,137,139],{"class":138},"sScJk"," value",[78,141,142],{"class":84},"=",[78,144,146],{"class":145},"sZZnC","\"{{ encoded_data }}\"",[78,148,95],{"class":84},[78,150,151],{"class":80,"line":105},[78,152,154],{"emptyLinePlaceholder":153},true,"\n",[78,156,157],{"class":80,"line":111},[78,158,159],{"class":101},"\u003C!-- DANGEROUS: unquoted attribute -->\n",[78,161,163,165,167,169,171,174,177,180],{"class":80,"line":162},5,[78,164,52],{"class":84},[78,166,135],{"class":87},[78,168,139],{"class":138},[78,170,142],{"class":84},[78,172,173],{"class":145},"{{",[78,175,176],{"class":138}," encoded_data",[78,178,179],{"class":138}," }}",[78,181,95],{"class":84},[78,183,185],{"class":80,"line":184},6,[78,186,187],{"class":101},"\u003C!-- A space in the data breaks out of the value -->\n",[15,189,190],{},"Always quote your attributes. Entity encoding inside unquoted attributes is not reliable protection.",[66,192,194],{"id":193},"javascript-context-entity-encoding-does-not-work","JavaScript context — entity encoding does NOT work",[29,196,198],{"className":72,"code":197,"language":74,"meta":38,"style":38},"\u003C!-- DANGEROUS: entity encoding inside script tags -->\n\u003Cscript>\n  var name = \"{{ encoded_data }}\";\n  \u003C!-- encoded_data = \"'; alert(1); \u002F\u002F\" -->\n  \u003C!-- After encoding, the semicolons and quotes remain valid JS -->\n\u003C\u002Fscript>\n",[36,199,200,205,214,231,247,260],{"__ignoreMap":38},[78,201,202],{"class":80,"line":81},[78,203,204],{"class":101},"\u003C!-- DANGEROUS: entity encoding inside script tags -->\n",[78,206,207,209,212],{"class":80,"line":98},[78,208,52],{"class":84},[78,210,211],{"class":87},"script",[78,213,95],{"class":84},[78,215,216,220,223,225,228],{"class":80,"line":105},[78,217,219],{"class":218},"szBVR","  var",[78,221,222],{"class":84}," name ",[78,224,142],{"class":218},[78,226,227],{"class":145}," \"{{ encoded_data }}\"",[78,229,230],{"class":84},";\n",[78,232,233,236,239,241,244],{"class":80,"line":111},[78,234,235],{"class":218},"  \u003C!--",[78,237,238],{"class":84}," encoded_data ",[78,240,142],{"class":218},[78,242,243],{"class":145}," \"'; alert(1); \u002F\u002F\"",[78,245,246],{"class":218}," -->\n",[78,248,249,251,254,258],{"class":80,"line":162},[78,250,235],{"class":218},[78,252,253],{"class":84}," After encoding, the semicolons and quotes remain valid ",[78,255,257],{"class":256},"sj4cs","JS",[78,259,246],{"class":218},[78,261,262,265,267],{"class":80,"line":184},[78,263,264],{"class":84},"\u003C\u002F",[78,266,211],{"class":87},[78,268,95],{"class":84},[15,270,271],{},"For JavaScript output contexts, use JSON serialization with proper Unicode escaping:",[29,273,277],{"className":274,"code":275,"language":276,"meta":38,"style":38},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F Correct: use JSON.stringify for JS context\nvar name = \u003C%= JSON.stringify(userInput) %>;\n","javascript",[36,278,279,284],{"__ignoreMap":38},[78,280,281],{"class":80,"line":81},[78,282,283],{"class":101},"\u002F\u002F Correct: use JSON.stringify for JS context\n",[78,285,286,289,291,293,296,299,302,305,308,311],{"class":80,"line":98},[78,287,288],{"class":218},"var",[78,290,222],{"class":84},[78,292,142],{"class":218},[78,294,295],{"class":218}," \u003C%=",[78,297,298],{"class":256}," JSON",[78,300,301],{"class":84},".",[78,303,304],{"class":138},"stringify",[78,306,307],{"class":84},"(userInput) ",[78,309,310],{"class":218},"%>",[78,312,230],{"class":84},[66,314,316],{"id":315},"css-context-entity-encoding-does-not-work","CSS context — entity encoding does NOT work",[29,318,320],{"className":72,"code":319,"language":74,"meta":38,"style":38},"\u003C!-- DANGEROUS -->\n\u003Cdiv style=\"background: url({{ encoded_data }})\">\n\u003C!-- An attacker can inject expression() or url() payloads -->\n",[36,321,322,327,344],{"__ignoreMap":38},[78,323,324],{"class":80,"line":81},[78,325,326],{"class":101},"\u003C!-- DANGEROUS -->\n",[78,328,329,331,334,337,339,342],{"class":80,"line":98},[78,330,52],{"class":84},[78,332,333],{"class":87},"div",[78,335,336],{"class":138}," style",[78,338,142],{"class":84},[78,340,341],{"class":145},"\"background: url({{ encoded_data }})\"",[78,343,95],{"class":84},[78,345,346],{"class":80,"line":105},[78,347,348],{"class":101},"\u003C!-- An attacker can inject expression() or url() payloads -->\n",[15,350,351,352,355],{},"Never put user data in CSS ",[36,353,354],{},"style"," attributes. Use CSS classes instead.",[66,357,359],{"id":358},"url-context-use-url-encoding-not-html-encoding","URL context — use URL encoding, not HTML encoding",[29,361,363],{"className":72,"code":362,"language":74,"meta":38,"style":38},"\u003C!-- CORRECT: URL-encode data values -->\n\u003Ca href=\"\u002Fsearch?q={{ url_encoded_data }}\">Search\u003C\u002Fa>\n",[36,364,365,370],{"__ignoreMap":38},[78,366,367],{"class":80,"line":81},[78,368,369],{"class":101},"\u003C!-- CORRECT: URL-encode data values -->\n",[78,371,372,374,377,380,382,385,388,390],{"class":80,"line":98},[78,373,52],{"class":84},[78,375,376],{"class":87},"a",[78,378,379],{"class":138}," href",[78,381,142],{"class":84},[78,383,384],{"class":145},"\"\u002Fsearch?q={{ url_encoded_data }}\"",[78,386,387],{"class":84},">Search\u003C\u002F",[78,389,376],{"class":87},[78,391,95],{"class":84},[10,393,395],{"id":394},"the-five-output-contexts","The Five Output Contexts",[397,398,399,415],"table",{},[400,401,402],"thead",{},[403,404,405,409,412],"tr",{},[406,407,408],"th",{},"Context",[406,410,411],{},"Required Escaping",[406,413,414],{},"Example",[416,417,418,432,444,457,470],"tbody",{},[403,419,420,424,427],{},[421,422,423],"td",{},"HTML text",[421,425,426],{},"HTML entity encoding",[421,428,429],{},[36,430,431],{},"\u003Cp>{{ encoded }}\u003C\u002Fp>",[403,433,434,437,439],{},[421,435,436],{},"HTML attribute (quoted)",[421,438,426],{},[421,440,441],{},[36,442,443],{},"\u003Cinput value=\"{{ encoded }}\">",[403,445,446,449,452],{},[421,447,448],{},"JavaScript",[421,450,451],{},"JSON serialization + Unicode escape",[421,453,454],{},[36,455,456],{},"var x = \u003C%= JSON.stringify(data) %>",[403,458,459,462,465],{},[421,460,461],{},"CSS",[421,463,464],{},"Avoid user data entirely; use classes",[421,466,467],{},[36,468,469],{},"\u003Cdiv class=\"{{ encoded_class }}\">",[403,471,472,475,482],{},[421,473,474],{},"URL parameter",[421,476,477,478,481],{},"URL encoding (",[36,479,480],{},"encodeURIComponent",")",[421,483,484],{},[36,485,486],{},"href=\"\u002Fpage?q={{ url_encoded }}\"",[15,488,489],{},"If you encode for the wrong context, you have a vulnerability regardless of how thoroughly you encoded.",[10,491,493],{"id":492},"defense-in-depth","Defense in Depth",[15,495,496],{},"HTML encoding is your primary defense, but it should not be the only one.",[66,498,500],{"id":499},"content-security-policy-csp","Content Security Policy (CSP)",[15,502,503],{},"A CSP header tells the browser which scripts can execute. Even if an XSS payload slips through, a strict CSP blocks inline scripts:",[29,505,508],{"className":506,"code":507,"language":34},[32],"Content-Security-Policy: default-src 'self'; script-src 'self'\n",[36,509,507],{"__ignoreMap":38},[66,511,513],{"id":512},"input-validation","Input validation",[15,515,516],{},"Validate and sanitize input before storing it. Reject unexpected characters early. Encoding is your output defense; validation is your input defense.",[66,518,520],{"id":519},"http-only-cookies","HTTP-only cookies",[15,522,523,524,527],{},"Mark session cookies as ",[36,525,526],{},"HttpOnly"," so JavaScript cannot read them. If an XSS attack executes, the attacker cannot steal the session token.",[66,529,531],{"id":530},"framework-auto-escaping","Framework auto-escaping",[15,533,534],{},"Modern frameworks escape by default:",[397,536,537,550],{},[400,538,539],{},[403,540,541,544,547],{},[406,542,543],{},"Framework",[406,545,546],{},"Auto-escaping mechanism",[406,548,549],{},"Raw output syntax",[416,551,552,569,582,596],{},[403,553,554,557,563],{},[421,555,556],{},"Vue",[421,558,559,562],{},[36,560,561],{},"{{ }}"," interpolations",[421,564,565,568],{},[36,566,567],{},"v-html"," directive",[403,570,571,574,577],{},[421,572,573],{},"React",[421,575,576],{},"JSX expressions",[421,578,579],{},[36,580,581],{},"dangerouslySetInnerHTML",[403,583,584,587,590],{},[421,585,586],{},"Angular",[421,588,589],{},"Template bindings",[421,591,592,595],{},[36,593,594],{},"[innerHTML]"," binding",[403,597,598,601,607],{},[421,599,600],{},"Svelte",[421,602,603,606],{},[36,604,605],{},"{ }"," expressions",[421,608,609,612],{},[36,610,611],{},"{@html}"," tag",[15,614,615,616,618,619,621],{},"Use raw output directives only when you explicitly trust the content. Every ",[36,617,567],{}," or ",[36,620,581],{}," is a potential XSS vector.",[10,623,625],{"id":624},"common-xss-encoding-mistakes","Common XSS Encoding Mistakes",[627,628,629,637,647,653,667],"ul",{},[630,631,632,636],"li",{},[633,634,635],"strong",{},"Encoding once then decoding"," — if your pipeline decodes the data after encoding, the protection disappears",[630,638,639,642,643,646],{},[633,640,641],{},"Using the wrong encoding for the context"," — HTML entity encoding inside a ",[36,644,645],{},"\u003Cscript>"," block does nothing",[630,648,649,652],{},[633,650,651],{},"Trusting sanitized HTML"," — HTML sanitization libraries have bypasses; combine with CSP",[630,654,655,658,659,662,663,666],{},[633,656,657],{},"Forgetting event handler attributes"," — ",[36,660,661],{},"onclick",", ",[36,664,665],{},"onerror",", and similar attributes execute JavaScript; entity encoding inside them does not prevent execution",[630,668,669,672],{},[633,670,671],{},"Assuming your framework handles everything"," — framework auto-escaping only covers template interpolations, not dynamic CSS or URL construction",[10,674,676],{"id":675},"key-takeaways","Key Takeaways",[627,678,679,682,685,691,694,697],{},[630,680,681],{},"HTML entity encoding prevents XSS in HTML text content and quoted attributes only",[630,683,684],{},"You must match the encoding to the output context — HTML, JS, CSS, and URL each need different escaping",[630,686,687,688,690],{},"Never put user data in ",[36,689,645],{}," blocks using entity encoding; use JSON serialization instead",[630,692,693],{},"Defense in depth: combine encoding with CSP, input validation, and HttpOnly cookies",[630,695,696],{},"Use your framework's auto-escaping; avoid raw output directives unless you fully trust the content",[630,698,699,700,662,702,704],{},"Event handler attributes (",[36,701,661],{},[36,703,665],{},") remain dangerous even with entity encoding",[10,706,708],{"id":707},"related-guides","Related Guides",[627,710,711,717,723],{},[630,712,713],{},[376,714,716],{"href":715},"\u002Fguides\u002Fhtml-encoding-guide","HTML Encoding Guide",[630,718,719],{},[376,720,722],{"href":721},"\u002Fguides\u002Fhtml-entity-reference","HTML Entity Reference",[630,724,725],{},[376,726,728],{"href":727},"\u002Fguides\u002Fweb-encoding-tools","Web Encoding Tools",[10,730,732],{"id":731},"try-it-yourself","Try It Yourself",[15,734,735,736,740],{},"Encode user-supplied data safely with our free ",[376,737,739],{"href":738},"\u002Ftools\u002Fhtml-encoder","HTML Encoder\u002FDecoder",". Paste any string and get properly entity-encoded output — verify your escaping before you ship.",[354,742,743],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":38,"searchDepth":98,"depth":98,"links":745},[746,747,748,755,756,762,763,764,765],{"id":12,"depth":98,"text":13},{"id":23,"depth":98,"text":24},{"id":60,"depth":98,"text":61,"children":749},[750,751,752,753,754],{"id":68,"depth":105,"text":69},{"id":117,"depth":105,"text":118},{"id":193,"depth":105,"text":194},{"id":315,"depth":105,"text":316},{"id":358,"depth":105,"text":359},{"id":394,"depth":98,"text":395},{"id":492,"depth":98,"text":493,"children":757},[758,759,760,761],{"id":499,"depth":105,"text":500},{"id":512,"depth":105,"text":513},{"id":519,"depth":105,"text":520},{"id":530,"depth":105,"text":531},{"id":624,"depth":98,"text":625},{"id":675,"depth":98,"text":676},{"id":707,"depth":98,"text":708},{"id":731,"depth":98,"text":732},"2026-05-28","How HTML entity encoding stops cross-site scripting attacks — context-aware escaping, common mistakes, and defense-in-depth strategies.","md",{"immutable":153},"\u002Fguides\u002Fxss-prevention-html-encoding",7,{"title":5,"description":767},"guides\u002Fxss-prevention-html-encoding","mMZDfDI-JNq1L2rVYq2tgQpLag6PSApqKuzTSNR-fjk",1780401338511]