Using the Safe Attribute

Estimated reading time: 3 minutes

Every child expression in Kita Html renders without escaping by default. To escape user input, apply the safe attribute to the nearest native element wrapping the expression.

Native elements

Add safe to the element that directly contains the dynamic content. The runtime passes all children through HTML entity escaping before concatenation.

const 
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
JSX.HtmlTag.safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
let userInput: "<script>alert('XSS')</script>"
userInput
}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<div>&lt;script>alert(&#39;XSS&#39;)&lt;/script></div>

Without safe, malicious input executes. Consider a user profile where the description field contains </div><script>fetch('/steal', {method: 'POST', body: document.cookie})</script>. Rendering this without escaping closes the container early, injects a script tag, and runs arbitrary code. The safe attribute converts the angle brackets to &lt; and &gt;, rendering the payload as harmless text.

Place safe on the innermost element that holds the untrusted value. Do not add it to a parent wrapper, as that would escape the HTML of child components too.

function UserCard({ name, bio }: { name: string; bio: string }) {
  return (
    <div class="card">
      <h2 safe>{name}</h2>
      <p safe>{bio}</p>
    </div>
  )
}

Component children

The safe attribute only applies to native elements. If you pass an unsafe value as a child to a component, you must either escape it manually or wrap it in a Fragment with the safe attribute to tell the plugin it's already escaped. This prevents components from accidentally rendering unescaped HTML when they receive dynamic content as children.

let html: JSX.Element
html
= (
<
function MyComponent({ children }: {
    children: Children;
}): JSX.Element
MyComponent
>
<
function Fragment(props: PropsWithChildren<Pick<JSX.HtmlTag, "safe">>): JSX.Element

A JSX Fragment is used to return multiple elements from a component.

@example
// renders <div>1</div> and <div>2</div> without needing a wrapper element
const html = <><div>1</div><div>2</div></>

// Html.Fragment is the same as <>...</>
const html = <Html.Fragment><div>1</div><div>2</div></Html.Fragment>
Fragment
safe?: boolean | undefined

When set to true, all inner content (html or not) of this tag will be escaped when evaluated.

Warning: This will escape even inner jsx tags. You should only use this in the most inner tag of the html tree.

@example
<div>{'<script />'}</div>
'<div><script /></div>'
<div safe>{'<script />'}</div>
'<div><script /></div>'
<div><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'

// Escapes even inner jsx tags
<div safe><div>{'<script />'}</div></div>
'<div><div><script /></div></div>'
@defaultfalse@seehttps ://github.com/kitajs/html/tree/master/packages/html#sanitization
safe
>{
let userInput: string
userInput
}</
function Fragment(props: PropsWithChildren<Pick<JSX.HtmlTag, "safe">>): JSX.Element

A JSX Fragment is used to return multiple elements from a component.

@example
// renders <div>1</div> and <div>2</div> without needing a wrapper element
const html = <><div>1</div><div>2</div></>

// Html.Fragment is the same as <>...</>
const html = <Html.Fragment><div>1</div><div>2</div></Html.Fragment>
Fragment
>
</
function MyComponent({ children }: {
    children: Children;
}): JSX.Element
MyComponent
>
) // Or escape explicitly when the component doesn't support safe
let html: JSX.Element
html
= <
function MyComponent({ children }: {
    children: Children;
}): JSX.Element
MyComponent
>{
function escapeHtml(this: void, value: any): string

Escapes a string for safe use as HTML text content. If the value is not a string, it is coerced to one with its own toString() method.

If the {@linkcode Bun } runtime is available, this function will be swapped out to

{@linkcode Bun.escapeHTML } .

@paramvalue The value to escape.@returnsThe escaped string.
escapeHtml
(
let userInput: string
userInput
)}</
function MyComponent({ children }: {
    children: Children;
}): JSX.Element
MyComponent
>
<div>User input!</div>

Template literal helper

The e tagged template escapes interpolated values while preserving literal HTML around them.

import { e } from '@kitajs/html'

const html = e`<p>Hello, ${userName}!</p>`

Suppression conventions

When the XSS detection plugin flags a value that you know is safe, you can suppress the warning without adding safe to the element, which would escape all children and potentially break components.

Danger

Suppressing warnings is dangerous because it allows unescaped HTML to slip through. Only use these techniques when you have a guarantee of safety.

Prefix the variable name with safe. The plugin treats any identifier starting with safe as pre-escaped.

const 
const safeContent: string
safeContent
=
function sanitizeElsewhere(raw: string): string
sanitizeElsewhere
(
const rawInput: "<script>alert('XSS')</script>"
rawInput
)
const
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>{
const safeContent: string
safeContent
}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
<div>&lt;script&gt;alert('XSS')&lt;/script&gt;</div>

Following the same logic, prefixing a variable with unsafe explicitly marks it as unescaped and triggers a warning if used in JSX.

const 
const unsafeContent: "My safe string"
unsafeContent
= 'My safe string' as
type const = "My safe string"
const
const
const html: JSX.Element
html
= <
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>{unsafeContent}</
JSX.IntrinsicElements.div: JSX.HtmlTag
div
>
Content may introduce an XSS vulnerability and must be marked with the `safe` attribute. See https://html.kitajs.org/TS88601
<div>My safe string</div>

Cast the expression to 'safe' inline. This tells the plugin to skip the check for that specific usage.

<div>{content as 'safe'}</div>

Call Html.escapeHtml() directly. The plugin recognizes the return value as escaped.

<div>{Html.escapeHtml(content)}</div>