diff --git a/src/Markdig.Fuzzing/.gitignore b/src/Markdig.Fuzzing/.gitignore
new file mode 100644
index 00000000..60aca1b0
--- /dev/null
+++ b/src/Markdig.Fuzzing/.gitignore
@@ -0,0 +1,4 @@
+corpus
+libfuzzer-dotnet-windows.exe
+crash-*
+timeout-*
\ No newline at end of file
diff --git a/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj b/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj
new file mode 100644
index 00000000..b9774246
--- /dev/null
+++ b/src/Markdig.Fuzzing/Markdig.Fuzzing.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Markdig.Fuzzing/Program.cs b/src/Markdig.Fuzzing/Program.cs
new file mode 100644
index 00000000..9f63b611
--- /dev/null
+++ b/src/Markdig.Fuzzing/Program.cs
@@ -0,0 +1,71 @@
+using Markdig;
+using Markdig.Renderers.Roundtrip;
+using Markdig.Syntax;
+using SharpFuzz;
+using System.Diagnostics;
+using System.Text;
+
+ReadOnlySpanAction fuzzTarget = ParseRenderFuzzer.FuzzTarget;
+
+if (args.Length > 0)
+{
+ // Run the target on existing inputs
+ string[] files = Directory.Exists(args[0])
+ ? Directory.GetFiles(args[0])
+ : [args[0]];
+
+ Debugger.Launch();
+
+ foreach (string inputFile in files)
+ {
+ fuzzTarget(File.ReadAllBytes(inputFile));
+ }
+}
+else
+{
+ Fuzzer.LibFuzzer.Run(fuzzTarget);
+}
+
+sealed class ParseRenderFuzzer
+{
+ private static readonly MarkdownPipeline s_advancedPipeline = new MarkdownPipelineBuilder()
+ .UseAdvancedExtensions()
+ .Build();
+
+ private static readonly ResettableRoundtripRenderer _roundtripRenderer = new();
+
+ public static void FuzzTarget(ReadOnlySpan bytes)
+ {
+ string text = Encoding.UTF8.GetString(bytes);
+
+ try
+ {
+ MarkdownDocument document = Markdown.Parse(text);
+ _ = document.ToHtml();
+
+ document = Markdown.Parse(text, s_advancedPipeline);
+ _ = document.ToHtml(s_advancedPipeline);
+
+ document = Markdown.Parse(text, trackTrivia: true);
+ _ = document.ToHtml();
+ _roundtripRenderer.Reset();
+ _roundtripRenderer.Render(document);
+
+ _ = Markdown.Normalize(text);
+ _ = Markdown.ToPlainText(text);
+ }
+ catch (Exception ex) when (IsIgnorableException(ex)) { }
+ }
+
+ private static bool IsIgnorableException(Exception exception)
+ {
+ return exception.Message.Contains("Markdown elements in the input are too deeply nested", StringComparison.Ordinal);
+ }
+
+ private sealed class ResettableRoundtripRenderer : RoundtripRenderer
+ {
+ public ResettableRoundtripRenderer() : base(new StringWriter(new StringBuilder(1024 * 1024))) { }
+
+ public new void Reset() => base.Reset();
+ }
+}
\ No newline at end of file
diff --git a/src/Markdig.Fuzzing/run-fuzzer.ps1 b/src/Markdig.Fuzzing/run-fuzzer.ps1
new file mode 100644
index 00000000..133acdf8
--- /dev/null
+++ b/src/Markdig.Fuzzing/run-fuzzer.ps1
@@ -0,0 +1,86 @@
+param (
+ [string]$configuration = $null
+)
+
+Set-StrictMode -Version Latest
+
+$libFuzzer = "libfuzzer-dotnet-windows.exe"
+$outputDir = "bin"
+
+function Get-LibFuzzer {
+ param (
+ [string]$Path
+ )
+
+ $libFuzzerUrl = "https://github.com/Metalnem/libfuzzer-dotnet/releases/download/v2025.05.02.0904/libfuzzer-dotnet-windows.exe"
+ $expectedHash = "17af5b3f6ff4d2c57b44b9a35c13051b570eb66f0557d00015df3832709050bf"
+
+ Write-Output "Downloading libFuzzer from $libFuzzerUrl..."
+
+ try {
+ $tempFile = "$Path.tmp"
+ Invoke-WebRequest -Uri $libFuzzerUrl -OutFile $tempFile -UseBasicParsing
+
+ $downloadedHash = (Get-FileHash -Path $tempFile -Algorithm SHA256).Hash
+
+ if ($downloadedHash -eq $ExpectedHash) {
+ Move-Item -Path $tempFile -Destination $Path -Force
+ Write-Output "libFuzzer downloaded successfully to $Path"
+ }
+ else {
+ Write-Error "Hash validation failed."
+ Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue
+ exit 1
+ }
+ }
+ catch {
+ Write-Error "Failed to download libFuzzer: $($_.Exception.Message)"
+ Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue
+ exit 1
+ }
+}
+
+# Check if libFuzzer exists, download if not
+if (-not (Test-Path $libFuzzer)) {
+ Get-LibFuzzer -Path $libFuzzer
+}
+
+$toolListOutput = dotnet tool list --global sharpFuzz.CommandLine 2>$null
+if (-not ($toolListOutput -match "sharpfuzz")) {
+ Write-Output "Installing sharpfuzz CLI"
+ dotnet tool install --global sharpFuzz.CommandLine
+}
+
+if (Test-Path $outputDir) {
+ Remove-Item -Recurse -Force $outputDir
+}
+
+if ($configuration -eq $null) {
+ $configuration = "Debug"
+}
+
+dotnet publish -c $configuration -o $outputDir
+
+$project = Join-Path $outputDir "Markdig.Fuzzing.dll"
+
+$fuzzingTarget = Join-Path $outputDir "Markdig.dll"
+
+Write-Output "Instrumenting $fuzzingTarget"
+& sharpfuzz $fuzzingTarget
+
+if ($LastExitCode -ne 0) {
+ Write-Error "An error occurred while instrumenting $fuzzingTarget"
+ exit 1
+}
+
+New-Item -ItemType Directory -Force -Path corpus | Out-Null
+
+$libFuzzerArgs = @("--target_path=dotnet", "--target_arg=$project", "-timeout=10", "corpus")
+
+# Add any additional arguments passed to the script
+if ($args) {
+ $libFuzzerArgs += $args
+}
+
+Write-Output "Starting libFuzzer with arguments: $libFuzzerArgs"
+& ./$libFuzzer @libFuzzerArgs
\ No newline at end of file
diff --git a/src/markdig.slnx b/src/markdig.slnx
index 512d4a2c..a6f41692 100644
--- a/src/markdig.slnx
+++ b/src/markdig.slnx
@@ -12,6 +12,9 @@
+
+
+