Skip to content
Acecore
Table of Contents
Safely Rendering Markdown Links in AI Chat Answers

When an AI chat returns See [Services]( /services/ ) for details, the link can fail to render and the raw Markdown can remain on screen.

Acecore hit this in the contact AI chat and adjusted the renderer in the PR that fixed Markdown link rendering.

This article uses that small fix as a starting point for safely converting AI answers into DOM nodes.

AI Answers Are Not Trusted HTML

Start by treating AI output as text, not HTML.

Links, bold text, and lists are useful in a chat UI, but placing the answer directly into innerHTML lets the browser interpret whatever string the model produced.

You do not need a complete Markdown implementation. You need a small renderer that detects the few features the chat supports and creates only safe DOM nodes.

The Problem Is Not Only Whitespace

The immediate bug was a link like this:

[Services](/services/)

A human can read it, but a regular expression that treats the URL as a string without whitespace will not match it.

The stricter pattern looked like this:

;/\[([^\]]+)\]\(([^)\s]+)\)/

[^)\s]+ rejects spaces, so ( /services/ ) is not parsed as a link. The fix is to tolerate whitespace inside the parentheses and normalize the URL afterward.

;/\[([^\]]+)\]\(\s*([^)]+?)\s*\)/

Do not stop at loosening the pattern. The next steps must normalize and validate the href.

Trim href Before Validation

The order should be fixed:

  1. Extract the label and raw href from Markdown
  2. trim() the raw href
  3. Validate the trimmed href through an allowlist
  4. Create an anchor only when the href is allowed
const href = String(rawHref || '').trim()

if (label && isSafeMarkdownHref(href)) {
  const link = document.createElement('a')
  link.href = href
  link.rel = 'noopener noreferrer'

  if (/^https?:\/\//i.test(href)) {
    link.target = '_blank'
  }

  link.textContent = label
  parent.appendChild(link)
}

Validate the same value you render. If validation checks one value and the DOM receives another, the safety check becomes weaker.

Make the Allowlist Product-Specific

Each site should decide which URLs its AI may show.

For Acecore’s contact AI, the allowed set is roughly:

TypeExampleDecision
Internal path/services/Allow
Same originhttps://acecore.net/...Allow
Official LINEhttps://lin.ee/...Allow because the purpose is clear
mailtomailto:[email protected]Allow only the fixed address
teltel:05088902788Allow only the fixed number
Other externalAny URLUsually do not link

The implementation can look like this:

function isSafeMarkdownHref(href) {
  if (href.startsWith('/')) return true

  try {
    const url = new URL(href, window.location.origin)
    if (url.origin === window.location.origin) return true
    if (url.hostname === 'acecore.net') return true
    if (url.hostname === 'lin.ee') return true
  } catch {
    return false
  }

  return href === 'mailto:[email protected]' || href === 'tel:05088902788'
}

This function should change by product. A recruiting site may allow job boards, an ecommerce site may allow payment or tracking domains, and a SaaS product may allow documentation and status pages.

Decide what happens when a link fails validation.

For a contact AI, preserving the original Markdown as text is usually better than deleting it. The user keeps the context, and developers can see what the model tried to output.

The renderer is responsible not only for creating safe links, but also for failing safely when a link cannot be created.

Prepare Test Cases Early

This kind of renderer is easy to under-test if you only check happy paths.

At minimum, test these cases:

InputExpected Result
[Services](/services/)Internal link
[Services]( /services/ )Trimmed internal link
[LINE]( https://lin.ee/example )External approved link
[Bad](javascript:alert(1))Not linked
[External](https://example.com/)Not linked if the domain is not allowed
[Broken](/services/Rendered as text
`code` and [link]( /contact/ )Code and link both render correctly

In PR #99, we confirmed that [Services]( /services/ ), [Services](/services/), and [LINE]( https://lin.ee/DjIrdqj ) resolve to the same intended URLs.

Do Not Implement All of Markdown by Default

The Markdown subset for an AI chat can stay small:

  • Paragraphs
  • Lists
  • Bold text
  • Inline code
  • Links

Tables, images, raw HTML, footnotes, and deep heading structures expand the renderer’s responsibility quickly. A chat UI only needs readable guidance.

If you use a mature Markdown library later, still decide separately whether HTML is allowed, how URLs are restricted, and which attributes are added to external links.

Summary

Markdown link rendering in AI chat looks like a small UI fix, but it is really a boundary decision about how much AI output is trusted.

The important points are:

  • Treat AI answers as text, not HTML
  • Convert only the required Markdown subset into DOM nodes
  • Accept whitespace around Markdown link URLs
  • Trim href values before safety checks
  • Allow only internal URLs and required external domains
  • Preserve disallowed links as text
  • Test broken Markdown and dangerous URLs

The more AI is used in site navigation, the more link rendering matters. Convenient Markdown support and strict link control should be designed together.

Link Rendering Flow for AI Answers

Text

Treat the model answer as plain text first.

Parse

Detect only the Markdown features the chat actually supports.

Validate

Trim href values and allow only internal URLs or approved domains.

Render

Create safe elements with the DOM API instead of using innerHTML.

Rendering Decisions to Separate

Loose Rendering

  • Putting AI answers directly into innerHTML
  • Trying to implement the whole Markdown specification at once
  • Failing to link URLs with spaces around them
  • Treating external URLs and javascript: URLs the same way

Small and Safe Rendering

  • Receive answers as text and turn only needed features into DOM nodes
  • Support only the Markdown subset used in chat
  • Validate URLs after trimming
  • Leave disallowed URLs as plain text
Implementation Checklist
  • Done: Do not trust AI answers as HTML
  • Done: Accept whitespace around Markdown link URLs
  • Done: Always trim href values before validation
  • Done: Allow only internal paths, the current origin, and required external domains
  • Done: Set target and rel explicitly for external links
  • Done: Preserve disallowed links as text
  • Done: Test dangerous URLs and broken Markdown, not only happy paths
FAQ
Is using markdown-it or marked enough?
Even with a library, you still need to decide how HTML output is handled, which link targets are allowed, whether target and rel are added, and how dangerous URLs are rejected. For chat, a small custom renderer can be enough.
Does allowing whitespace around URLs make it unsafe?
The whitespace itself is not the risk. The important part is validating the trimmed href. That makes the renderer tolerant of model formatting while keeping the allowlist strict.
Should disallowed URLs be removed?
Usually keeping them as text is easier to debug and preserves context for the user. If your policy requires hiding suspicious strings, dropping the whole link is also valid.

Comments

Loading comments...

Links, email addresses, and promotional text cannot be posted.

G

Gui

CEO of Acecore. Leads business systems, web, databases and infrastructure, quality assurance, and AI adoption from business problem framing through design, rollout, and post-launch improvement. Builds on hands-on C#/.NET capability while also covering PHP/JavaScript, SQL Server/PostgreSQL/MySQL, and Linux/Windows Server, designing requirements, technology choices, quality standards, and GitHub-based development operations as one coherent workflow. Uses generative AI across development, verification, and information organization, treating it as practical infrastructure that helps small teams deliver faster and more reliably.

Business problem framingTechnology selectionSystem designC#/.NETDatabase/infrastructure designGitHub development operationsGenerative AIAI workflow designQuality designOn-site integration

Want to learn more about our services?

We provide comprehensive support including system development, web design, graphic design, and IT education.

Related Posts

Search articles