Skip to content

SchemaDisplayPath renders children via dangerouslySetInnerHTML without sanitisation #486

Description

@Zcamacho123

Summary

SchemaDisplayPath in schema-display.tsx renders caller-supplied content through dangerouslySetInnerHTML without escaping, so any consumer that displays a schema originating outside the app is exposed to script injection.

Where

schema-display.tsx (~line 110):

const highlightedPath = path.replaceAll(
  /\{([^}]+)\}/g,
  '<span class="text-blue-600 dark:text-blue-400">{$1}</span>'
);

return (
  <span
    className={cn("font-mono text-sm", className)}
    dangerouslySetInnerHTML={{ __html: children ?? highlightedPath }}
    {...props}
  />
);

Both operands of the ?? are unsafe:

  • children is passed straight into __html with no sanitisation at all.
  • highlightedPath interpolates path into an HTML string via regex, so any markup already in path survives into the DOM.

Why it matters

The natural use for this component is rendering an API or tool schema, and those routinely come from somewhere other than the app itself — an MCP server's tool definitions being the obvious case in an AI SDK context. A schema path is not a trust boundary anyone thinks about, which is what makes this easy to hit by accident.

Suggested fix

No dangerouslySetInnerHTML is needed here — the highlighting can be expressed as React nodes, which escapes by construction:

const parts = path.split(/(\{[^}]+\})/g);

return (
  <span className={cn("font-mono text-sm", className)} {...props}>
    {children ?? parts.map((part, i) =>
      /^\{[^}]+\}$/.test(part) ? (
        <span key={i} className="text-blue-600 dark:text-blue-400">{part}</span>
      ) : (
        part
      )
    )}
  </span>
);

That also lets children be a normal React node rather than an HTML string, which is likely what callers expect.

Version

Found in ai-elements@1.9.0, installed via the shadcn registry.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions