# harden-react-markdown-urls 🔗🔒<img src="https://raw.githubusercontent.com/mayank1513/mayank1513/main/popper.png" style="height: 40px"/>

[![test](https://github.com/tiny-md/harden-urls/actions/workflows/test.yml/badge.svg)](https://github.com/tiny-md/harden-urls/actions/workflows/test.yml)
[![codecov](https://codecov.io/gh/tiny-md/harden-urls/graph/badge.svg?flag=harden-react-markdown-urls)](https://codecov.io/gh/tiny-md/harden-urls)
[![Version](https://img.shields.io/npm/v/harden-react-markdown-urls.svg?colorB=green)](https://www.npmjs.com/package/harden-react-markdown-urls)
[![Downloads](https://img.jsdelivr.com/img.shields.io/npm/d18m/harden-react-markdown-urls.svg)](https://www.npmjs.com/package/harden-react-markdown-urls)
![npm bundle size](https://img.shields.io/bundlephobia/minzip/harden-react-markdown-urls)
[![NPM License](https://img.shields.io/npm/l/harden-react-markdown-urls)](../../LICENSE)

<p align="center">
  <img src="./harden-react-markdown.jpg" alt="harden-react-markdown-urls banner: Secure your Markdown links and images" />
</p>

🛡️ A **drop-in security wrapper (Higher-Order Component)** for [`react-markdown`](https://github.com/remarkjs/react-markdown) that automatically sanitizes URLs in your rendered content using the deep cleaning capabilities of [`rehype-harden-urls`](https://github.com/tiny-md/harden-urls/tree/main/libs/harden-urls).

> “Do complex things, the simple way.”

---

## ✨ Features

- **Full URL Hardening**: Protects against malicious links and tracking parameters (like `utm_`, `fbclid`) using robust normalization and strict protocol checking.
- **Drop-in Integration**: Wraps any `react-markdown` instance with zero configuration required.
- **Seamless Compatibility**: Works perfectly alongside existing `rehypePlugins`.
- **Flexible Policies**: Fully leverages `rehype-harden-urls` presets (`strict`, `balanced`, `relaxed`) and granular custom options.
- **Lightweight**: Pure functional HOC with minimal overhead.

---

## 📦 Installation

We recommend using utilities such as `toRegexps`, `domainsToRegexps`, etc. from `harden-urls` package for creating your custom configurations.

```bash
pnpm add harden-react-markdown-urls harden-urls
```

**_or_**

```bash
npm install harden-react-markdown-urls harden-urls
```

---

## 🧠 Usage

### Basic Setup

Wrap your base `ReactMarkdown` component with `hardenReactMarkdown` and provide your default security preset.

```tsx
import ReactMarkdown from "react-markdown";
import { hardenReactMarkdown } from "harden-react-markdown-urls";
import { presets } from "harden-urls/utils";

// 1. Define your default policy (e.g., balanced is a great default)
const HardenedMarkdown = hardenReactMarkdown(ReactMarkdown, presets.balanced);

// 2. Use the wrapped component
function MyComponent({ markdownText }) {
  return <HardenedMarkdown>{markdownText}</HardenedMarkdown>;
}
```

👉 **Gotcha:** Any unsafe link (e.g., `[Click me](javascript:alert('xss'))`) will have its `href` attribute removed or be pruned, based on the policy.

### Fine-grained Control & Overrides

Use the `hardenedOptions` prop to override the default policy for a specific instance. This is useful for content sources with different security requirements.

```tsx
<HardenedMarkdown
  // Override the default 'balanced' policy for this instance
  hardenedOptions={{
    link: { allowedProtocols: new Set(["https:", "mailto:"]) },
    image: { allowedProtocols: new Set(["https:"]) },
    // Use the callback to log any blocked URLs
    onUnsafeUrl: (url, node, type) => console.warn("Blocked:", type, url),
  }}>
  {markdownText}
</HardenedMarkdown>
```

---

## ⚙️ Props

| Prop                       | Type                      | Description                                                                           |
| -------------------------- | ------------------------- | ------------------------------------------------------------------------------------- |
| `hardenedOptions`          | `RehypeHardenUrlsOptions` | **Instance-level override** for the hardening policy defined by `rehype-harden-urls`. |
| All `react-markdown` props | –                         | Fully supported and forwarded to the wrapped component.                               |

---

## ⚠️ Best Practice: The Security Stack

For maximum safety when dealing with **untrusted Markdown that contains embedded HTML** (i.e., when you use `rehype-raw`), you must pair this package with `rehype-sanitize`.

The recommended secure chain is:

1.  **`rehype-raw`**: Parses raw HTML into the tree.
2.  **`rehype-harden-urls`** (via this package): Deeply cleans and normalizes all URL **values**.
3.  **`rehype-sanitize`**: Provides the final structural guardrail, removing any forbidden tags or attributes.

**Always ensure** your `react-markdown` component allows for both `rehype-harden-urls` and a general sanitizer:

```tsx
// Example of a minimal safe component
const SafeMarkdown = hardenReactMarkdown(ReactMarkdown, presets.balanced, {
  rehypePlugins: [rehypeRaw, rehypeSanitize], // Ensure sanitization is always on top
});
```

---

## ⚖️ Why Use This Over Basic Sanitizers?

This package closes critical security gaps often missed by simpler solutions, including Vercel Labs’ older efforts.

| Capability                                             | harden-react-markdown-urls      | Vercel Labs' Simple Sanitizers |
| :----------------------------------------------------- | :------------------------------ | :----------------------------- |
| **Deep URL Cleaning** (Normalization, Case-folding)    | ✅                              | ❌                             |
| **Strip Tracking Parameters** (`utm_`, `fbclid`, etc.) | ✅                              | ❌                             |
| **Hardens Embedded HTML URLs** (via `rehype-raw`)      | ✅ (Requires `rehype-sanitize`) | ⚠️ Incomplete                  |
| **Protocol Validation**                                | ✅ Comprehensive                | ⚠️ Basic Prefix Check          |

---

## 📚 Security Presets

This package utilizes the following presets from `rehype-harden-urls` for quick configuration:

| Preset     | Description                                       | Key Protocols Allowed                 |
| :--------- | :------------------------------------------------ | :------------------------------------ |
| `strict`   | HTTPS-only, strips all known trackers             | `https:`                              |
| `balanced` | Safe default: allows mailto and secure protocols  | `https:`, `mailto:`                   |
| `relaxed`  | Allows insecure HTTP, minimal parameter stripping | `http:`, `https:`, `mailto:`, `data:` |

---

## 🤝 Contribution & Support

We enthusiastically welcome contributions from the community!

Whether you are reporting a bug, suggesting a new feature, or submitting a pull request, your help makes this a safer tool for everyone. Please check the [GitHub Issues](https://github.com/tiny-md/harden-urls/issues) for open tasks.

**💖 Adopt and Support:** If this package helps secure your application, consider giving us a star on GitHub! You can also [sponsor our work](https://github.com/sponsors/mayank1513) to help fund continued development and maintenance.

---

## 🪷 License

This project is licensed under the **MIT License**.

MIT © [Mayank Chaudhari](https://github.com/mayank1513)

> We express gratitude to **react-markdown**, **rehype**, and **Vercel Labs** for the inspiration.

---

<p align="center" style="text-align:center">with 💖 by <a href="https://mayank-chaudhari.vercel.app" target="_blank">Mayank Kumar Chaudhari</a></p>
