skip to content
ainoya.dev

LLM Application Security Practice: Markdown Image Tag

/ 4 min read

Rendering Markdown Images in LLM Apps? Proceed with Caution

It’s a common practice to render markdown-formatted text into HTML for user responses in LLM applications. Markdown’s image embedding syntax, ![image alt text](image url), seamlessly integrates images into the HTML output. But here’s the catch – this convenience can be a potential security pitfall.

When an image is rendered in HTML, the browser accesses its URL. This opens up a subtle attack vector:

  • Malicious actors can craft URLs with embedded parameters containing sensitive user context or personal information.
  • When the browser fetches the image, this data leaks to the external server via the URL parameters, potentially ending up in server logs or worse.

How to Mitigate the Risk

Fortunately, there are several ways to prevent this type of data leakage:

  1. Disable Markdown Image Embedding: The most straightforward approach is to entirely disable the image embedding functionality within your markdown parser.

  2. Implement a Whitelist: Allow images to be embedded only from trusted domains by creating a whitelist of allowed hosts.

Example Code: Protecting Your LLM Application

Let’s look at some practical TypeScript examples using the remark library for rendering markdown to HTML:

Disabling Image Embedding

import { remark } from "remark";
import html from "remark-html";
import { visit } from "unist-util-visit";

interface ImageNode {
  type: "image";
  url: string;
}

type MarkdownNode = ImageNode | any;

// Function to remove all image tags
const removeImage = (): ((tree: MarkdownNode) => void) => {
  return (tree: MarkdownNode) => {
    visit(
      tree,
      "image",
      (node: ImageNode, index: number | undefined, parent: MarkdownNode) => {
        parent.children.splice(index!, 1); // Remove the image tag
      }
    );
  };
};

const markdown = `# Example Markdown
This is a paragraph.
![An image](https://example.com/image.png)
Another paragraph.
`;

remark()
  .use(removeImage)
  .use(html)
  .process(markdown, function (err, file) {
    if (err) throw err;
    console.log(String(file));
  });

Whitelisting Trusted Hosts

import { remark } from "remark";
import html from "remark-html";
import { visit } from "unist-util-visit";
const trustedHosts = ["https://example.com"]; // Manage your whitelist here

interface ImageNode {
  type: "image";
  url: string;
}

type MarkdownNode = ImageNode | any;

const safeImageEmbedding = (): ((tree: MarkdownNode) => void) => {
  return (tree: MarkdownNode) => {
    visit(
      tree,
      "image",
      (node: ImageNode, index: number | undefined, parent: MarkdownNode) => {
        if (!trustedHosts.some((host) => node.url.startsWith(host))) {
          parent.children.splice(index!, 1); // Remove untrusted image tags
        }
      }
    );
  };
};

const markdown = `# Example Markdown
This is a paragraph.
![An image](https://example.com/image.png)
Another paragraph.

This is a dangerous image, but it will be removed by the safeImageEmbedding function
![An image](http://nottrusted.com/image.png)
`;

remark()
  .use(safeImageEmbedding)
  .use(html)
  .process(markdown, function (err, file) {
    if (err) throw err;
    console.log(String(file));
  });

Important: These methods provide a layer of protection but don’t guarantee complete security. Thoroughly test your implementation to ensure it meets your specific security needs. While you could attempt to use regular expressions to replace image tags, this approach is prone to errors. Escape characters or cleverly crafted input could bypass your regex patterns. Directly manipulating the Abstract Syntax Tree (AST) parsed by your markdown library is a much more robust and reliable solution.

Why This Matters, Especially for UGC

Services that encourage user-generated content (UGC), such as platforms for sharing LLM prompts, are particularly vulnerable:

  1. A malicious user shares a seemingly helpful prompt.
  2. An unsuspecting user inputs this prompt into their LLM application.
  3. The LLM, potentially employing in-context learning, generates a response that incorporates the user’s sensitive data within a crafted image URL.
  4. The malicious user receives the leaked data when the image is rendered.

Remember: This vulnerability extends beyond LLM applications. It’s crucial to stay informed about security best practices for handling user-generated content and data privacy in any online service.

References