Skip to content

Fix #24: Sanitize Node Metadata Inputs - #29

Merged
diusazzad merged 1 commit into
mainfrom
fix/issue-24-sanitize-inputs
Aug 5, 2026
Merged

diusazzad merged 1 commit into
mainfrom
fix/issue-24-sanitize-inputs

Conversation

@diusazzad

@diusazzad diusazzad commented Jul 29, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes
    • Improved route map diagram rendering by safely escaping dynamic route, controller, middleware, model, and view names.
    • Prevented special characters in application metadata from affecting generated Mermaid diagrams.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

RouteMapCollector now HTML-escapes dynamic controller, route, middleware, action, model, and view values before embedding them in generated Mermaid markup.

Changes

Mermaid Output Escaping

Layer / File(s) Summary
Escape dynamic Mermaid labels
packages/diusazzad/laranexus/src/Support/RouteMapCollector.php
Controller route metadata and reflection-derived model/view names use htmlspecialchars(..., ENT_QUOTES, 'UTF-8') before insertion into Mermaid output.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: sanitizing dynamic node metadata before generating Mermaid output.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-24-sanitize-inputs

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PHPStan (2.2.6)

Composer install failed: the lock file is not up to date with the latest changes in composer.json. Run composer update and commit the updated composer.lock.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/diusazzad/laranexus/src/Support/RouteMapCollector.php`:
- Line 72: Update the htmlspecialchars() calls in RouteMapCollector, including
the escaping performed for names, routes, middleware, actions, models, and
views, to combine ENT_QUOTES with ENT_SUBSTITUTE. Preserve UTF-8 encoding and
ensure invalid byte sequences are substituted rather than removed from Mermaid
labels.
- Around line 72-89: Update the Mermaid label generation in RouteMapCollector’s
route and controller label paths, including the corresponding lines around the
method labels, so user-controlled names, URIs, actions, and middleware cannot
become executable or unintended HTML after Mermaid decodes entities. Either
apply an allowlisted sanitizer that preserves only explicitly supported
formatting or disable HTML labels and use a stricter Mermaid security mode;
ensure the dashboard output remains safe and add an end-to-end test covering
HTML-bearing route metadata.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ee8b59f-c3ad-471b-a2b2-fcc5e142bc49

📥 Commits

Reviewing files that changed from the base of the PR and between 753d0d0 and 3b67045.

📒 Files selected for processing (1)
  • packages/diusazzad/laranexus/src/Support/RouteMapCollector.php

foreach ($controllers as $name => $data) {
$ctrlId = "C_" . md5($name);
$mermaidLines[] = " subgraph {$ctrlId} [\"$name\"]";
$safeName = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'RouteMapCollector.php' . || true

echo "== file outline =="
ast-grep outline packages/diusazzad/laranexus/src/Support/RouteMapCollector.php --view expanded || true

echo "== relevant lines =="
cat -n packages/diusazzad/laranexus/src/Support/RouteMapCollector.php | sed -n '60,120p'

echo "== all htmlspecialchars usages in file =="
rg -n "htmlspecialchars" packages/diusazzad/laranexus/src/Support/RouteMapCollector.php

echo "== PHP availability/version =="
php -v 2>/dev/null || true
php -r 'echo implode("|", [
  defined("ENT_QUOTES"),
  defined("ENT_SUBSTITUTE"),
  ENT_QUOTES,
  ENT_SUBSTITUTE,
]);' 2>/dev/null || true

echo "== behavioral probe =="
tmpfile="$(mktemp)"
cat > "$tmpfile" <<'PHP'
<?php
if (!function_exists('htmlspecialchars')) {
    echo "htmlspecialchars unavailable\n";
    exit(0);
}
$inputs = [
    "ok",
    "-\x80",
    "-\xFF",
    "-\xC3",
];
$flags = [
    "ENT_QUOTES only" => ENT_QUOTES,
    "ENT_QUOTES | ENT_SUBSTITUTE" => ENT_QUOTES | ENT_SUBSTITUTE,
];
if (defined('ENT_SUBSTITUTE')) {
    foreach ($inputs as $input) {
        echo "input bytes: " . bin2hex($input) . PHP_EOL;
        foreach ($flags as $label => $flag) {
            if ($label === "ENT_QUOTES | ENT_SUBSTITUTE" && !defined('ENT_SUBSTITUTE')) {
                continue;
            }
            $out = htmlspecialchars($input, $flag, 'UTF-8');
            echo "$label: bytes=" . bin2hex($out) . " len=" . strlen($out) . PHP_EOL;
        }
    }
} else {
    foreach ($inputs as $input) {
        echo "input bytes: " . bin2hex($input) . PHP_EOL;
        $out = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
        echo "ENT_QUOTES only: bytes=" . bin2hex($out) . " len=" . strlen($out) . PHP_EOL;
    }
}
PHP
php "$tmpfile"
rm -f "$tmpfile"

Repository: diusazzad/LaraNexus

Length of output: 5340


Preserve ENT_SUBSTITUTE when escaping Mermaid labels.

The default flags are not preserved when only ENT_QUOTES is passed, so invalid UTF-8 in names, routes, middleware, actions, models, or views turns into an empty string and can silently erase labels. Add ENT_SUBSTITUTE to these htmlspecialchars() calls.

Suggested change
- htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
+ htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$safeName = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
$safeName = htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/diusazzad/laranexus/src/Support/RouteMapCollector.php` at line 72,
Update the htmlspecialchars() calls in RouteMapCollector, including the escaping
performed for names, routes, middleware, actions, models, and views, to combine
ENT_QUOTES with ENT_SUBSTITUTE. Preserve UTF-8 encoding and ensure invalid byte
sequences are substituted rather than removed from Mermaid labels.

Source: MCP tools

Comment on lines +72 to +89
$safeName = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
$mermaidLines[] = " subgraph {$ctrlId} [\"$safeName\"]";

foreach ($data['routes'] as $r) {
$routeId = "R_" . md5($r['uri']);
$methodId = "M_" . md5($r['uri'] . $r['action']);

// Route Node with Middleware
$label = "/{$r['uri']}";
$safeUri = htmlspecialchars($r['uri'], ENT_QUOTES, 'UTF-8');
$label = "/{$safeUri}";
if ($r['middleware']) {
$label .= " <br/><small>🔐 {$r['middleware']}</small>";
$safeMiddleware = htmlspecialchars($r['middleware'], ENT_QUOTES, 'UTF-8');
$label .= " <br/><small>🔐 {$safeMiddleware}</small>";
}

$safeAction = htmlspecialchars($r['action'], ENT_QUOTES, 'UTF-8');
$mermaidLines[] = " {$routeId}([\"$label\"]):::route";
$mermaidLines[] = " {$methodId}(\"{$r['action']}\"):::method";
$mermaidLines[] = " {$methodId}(\"{$safeAction}\"):::method";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

version="${MERMAID_VERSION:-10.9.6}"
base="https://raw.githubusercontent.com/mermaid-js/mermaid/v${version}/packages/mermaid/src"

integration="$(curl -fsSL "$base/mermaid.ts")"
api="$(curl -fsSL "$base/mermaidAPI.ts")"

grep -Fq 'txt = element.innerHTML' <<<"$integration"
grep -Fq 'utils.entityDecode(txt)' <<<"$integration"
grep -Fq 'else if (!isLooseSecurityLevel)' <<<"$api"

echo "Verified Mermaid ${version} parsing and loose-mode sanitization behavior."

Repository: diusazzad/LaraNexus

Length of output: 227


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== Locate relevant files =="
fd -a 'RouteMapCollector.php|*.blade.php|composer.lock|package.json|pnpm-lock.yaml|yarn.lock|package-lock.json' . \
  | sed 's#^\./##' | head -200

echo
echo "== RouteMapCollector excerpt =="
file="$(fd 'RouteMapCollector.php' . | head -1)"
if [ "${file:-}" != "" ]; then
  wc -l "$file"
  sed -n '1,160p' "$file" | nl -ba
fi

echo
echo "== Mermaid config/usages through raw blade or securityLevel =="
rg -n --hidden --glob '!vendor/**' --glob '!node_modules/**' "securityLevel|DOMPurify|\.mermaid|innerHTML|RouteMapCollector|mermaid" . | head -300

echo
echo "== Composer/package Mermaid references =="
rg -n --hidden --glob '!vendor/**' --glob '!node_modules/**' "mermaid|viz" composer.json composer.lock package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

Repository: diusazzad/LaraNexus

Length of output: 590


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== Locate relevant files =="
files=()
for p in packages/diusazzad/laranexus/src/Support/RouteMapCollector.php; do
  [ -f "$p" ] && files+=("$p")
done
fd 'RouteMapCollector.php$' . packages || true
fd '\.blade\.php$' . packages || true
for f in composer.lock package.json pnpm-lock.yaml yarn.lock package-lock.json; do
  [ -f "$f" ] && files+=("$f")
done
printf '%s\n' "${files[@]}"

file="$(fd 'RouteMapCollector.php$' . packages | head -1 || true)"
if [ -n "${file:-}" ]; then
  echo
  echo "== RouteMapCollector excerpt =="
  wc -l "$file"
  sed -n '1,170p' "$file" | nl -ba
fi

echo
echo "== Mermaid config/usages through raw blade or securityLevel =="
rg -n --hidden --glob '!vendor/**' --glob '!node_modules/**' "securityLevel|DOMPurify|\.mermaid|innerHTML|RouteMapCollector|mermaid" . packages || true

echo
echo "== Composer/package Mermaid references =="
for f in composer.json composer.lock package.json pnpm-lock.yaml yarn.lock package-lock.json; do
  [ -f "$f" ] && { echo "--- $f"; rg -n 'mermaid|viz' "$f" || true; }
done

Repository: diusazzad/LaraNexus

Length of output: 864


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== RouteMapCollector excerpt =="
sed -n '1,170p' packages/diusazzad/laranexus/src/Support/RouteMapCollector.php

echo
echo "== Dashboard and relevant Blade excerpts =="
sed -n '1,240p' packages/diusazzad/laranexus/resources/views/dashboard.blade.php
rg -n --hidden --glob '!vendor/**' --glob '!node_modules/**' "RouteMapCollector|mermaid|securityLevel|innerHTML|`@raw`|dangerouslySetInnerHTML" packages/diusazzad/laranexus resources . | head -200

echo
echo "== Mermaid package references =="
for f in composer.json composer.lock package.json pnpm-lock.yaml yarn.lock package-lock.json; do
  [ -f "$f" ] && { echo "--- $f"; rg -n 'mermaid|viz' "$f" || true; }
done

Repository: diusazzad/LaraNexus

Length of output: 25764


🌐 Web query:

Mermaid v10 parses innerHTML reads element.innerHTML entityDecode htmlLabels labels

💡 Result:

In Mermaid, the interaction between htmlLabels, innerHTML, and entity decoding has been a critical security consideration, particularly in versions 10 and 11. The htmlLabels configuration option determines whether Mermaid uses HTML elements (like <span> or <div>) to render node and edge labels instead of standard SVG text elements [1][2]. When htmlLabels is enabled, the library may process input that includes HTML tags, which can introduce security risks if the input is not strictly sanitized [1][3][4]. A notable security vulnerability (CVE-2025-54881) was identified in Mermaid versions 10.9.0-rc.1 through 11.9.0 [5][6][7]. In these versions, user-supplied input for sequence diagram labels containing KaTeX delimiters was passed to a function called calculateMathMLDimensions [8][7]. This function incorrectly assigned the raw, unsanitized input to an element's innerHTML property to measure its dimensions [8][6][7]. This behavior created an XSS (Cross-Site Scripting) sink, allowing attackers to inject arbitrary malicious HTML [8][5][7]. Key points regarding Mermaid's handling of these mechanisms include: - Security Levels: Mermaid provides a securityLevel configuration (strict, loose, antiscript, sandbox) to manage trust [1]. The strict (default) setting encodes HTML tags, while loose or antiscript may permit them, increasing the risk of XSS if the input source is untrusted [1][3]. - Sanitization: Even when htmlLabels is enabled, Mermaid employs internal sanitization (often using DOMPurify) to strip dangerous elements [4]. However, vulnerabilities arise when components—such as the aforementioned calculateMathMLDimensions—bypass these protections by directly setting innerHTML [8][4]. - Entity Decoding: Mermaid performs entity decoding (e.g., converting &lt; back to <) to ensure labels render correctly [9]. Developers have worked to ensure that such decoding is handled safely (e.g., using a local function in createText.ts) to avoid affecting global rendering processes or creating security regressions [9]. To mitigate these risks, it is recommended to upgrade Mermaid to version 11.10.0 or higher [6][7]. Additionally, users handling untrusted diagram input should enforce a securityLevel of strict or sandbox and consider applying secondary sanitization on the rendered output if custom HTML rendering is required [1][4].

Citations:


🌐 Web query:

GHSA-7rqq-prvp-x9jh Mermaid v10.9.6 flowchart htmlLabels XSS innerHTML entityDecode

💡 Result:

The vulnerability identified as GHSA-7rqq-prvp-x9jh (also tracked as CVE-2025-54881) is a Cross-Site Scripting (XSS) flaw in the Mermaid.js library [1][2][3]. The vulnerability stems from improper sanitization of user-supplied input in sequence diagram labels [1][2]. Specifically, the library's calculateMathMLDimensions function—which is used when KaTeX support is enabled (the default configuration)—takes the raw label text and passes it directly to innerHTML when calculating element dimensions [1][4]. This allows attackers to inject malicious HTML and execute arbitrary scripts in the victim's browser [2][5]. This issue affects Mermaid versions 10.9.0-rc.1 through 11.9.0 [3][6]. It was patched in version 11.10.0 [1][2][5]. Regarding your query about flowchart htmlLabels and entityDecode: while GHSA-7rqq-prvp-x9jh specifically targets sequence diagram labels via the calculateMathMLDimensions function [1][2], Mermaid's handling of htmlLabels (often used in flowcharts) has historically presented security challenges, especially when combined with a loose securityLevel configuration [7][8]. In such setups, if Mermaid's output is injected into the DOM via innerHTML without secondary sanitization, it can lead to vulnerabilities like SSRF or NTLM hash theft, as the library's internal sanitizer may permit certain tags (such as <img>) that can trigger network requests [7][8]. Developers are advised to use securityLevel: "strict" and ensure any HTML rendered from Mermaid diagrams is passed through a robust sanitizer like DOMPurify before being inserted into the page [7][8].

Citations:


🌐 Web query:

site:github.com/mermaid-js/mermaid v10.9.6 packages/mermaid/src/mermaid.ts txt = element.innerHTML utils.entityDecode

💡 Result:

In the context of the Mermaid.js library (specifically versions like v10.9.6), the code snippet involving txt = element.innerHTML and utils.entityDecode(txt) is found in the main rendering logic, typically located within packages/mermaid/src/mermaid.ts [1][2]. This code handles the retrieval and processing of the diagram definition from the DOM [1]. Specifically: 1. txt = element.innerHTML: The library retrieves the raw HTML content of the target element (the div containing the Mermaid diagram definition) [1][2]. 2. utils.entityDecode(txt): Since HTML content often includes encoded entities (e.g., > instead of >), this utility function decodes those entities back into their original character representations [1][2]. 3. Dedent and Cleaning: The decoded text is passed through a dedent function to remove unnecessary indentation, which is crucial for correct YAML and diagram parsing, and then trimmed and cleaned (e.g., replacing
tags with a standardized format) [1][2]. This sequence ensures that the diagram definition provided by the user is correctly interpreted as valid text, regardless of how it was encoded within the HTML document [1][2].

Citations:


Don’t rely on htmlspecialchars() for Mermaid inputs.

The dashboard renders $mermaidString through raw Blade output and runs Mermaid with securityLevel: 'loose' plus htmlLabels; encoded values are decoded by the Mermaid pipeline before parsing, so tags encoded via htmlspecialchars() can be interpreted as markup again. Either sanitize/deny HTML carefully for the allowed formatting or switch to a non-HTML label mode/stricter Mermaid mode, and cover this with an end-to-end test.

Also applies to: 103-111

🧰 Tools
🪛 ast-grep (0.45.0)

[error] 75-75: Do not use a weak hash algorithm
Context: md5($r['uri'])
Note: [CWE-328] Use of Weak Hash.

(weak-hash-algorithm)


[error] 76-76: Do not use a weak hash algorithm
Context: md5($r['uri'] . $r['action'])
Note: [CWE-328] Use of Weak Hash.

(weak-hash-algorithm)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/diusazzad/laranexus/src/Support/RouteMapCollector.php` around lines
72 - 89, Update the Mermaid label generation in RouteMapCollector’s route and
controller label paths, including the corresponding lines around the method
labels, so user-controlled names, URIs, actions, and middleware cannot become
executable or unintended HTML after Mermaid decodes entities. Either apply an
allowlisted sanitizer that preserves only explicitly supported formatting or
disable HTML labels and use a stricter Mermaid security mode; ensure the
dashboard output remains safe and add an end-to-end test covering HTML-bearing
route metadata.

Source: MCP tools

@diusazzad
diusazzad merged commit 672037f into main Aug 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant