package.json: Switch to markdown-to-jsx

The PackageKit UpdateDetail signal provides markdown in the
`update_text` and uses remarkable to parse the markdown contents.

Remarkable however has not been updated since 6 years, additionally it
requires us to use `dangerouslySetInnerHTML` which can lead to XSS
issues.

The npm ecosystem has two other markdown alternatives `react-markdown`
and a `markdown-to-jsx`. The latter has zero dependencies while
`react-markdown` has eleven and requires additional plugins like
`rehype-external-links` to add a rel/target to external links.

https://www.freedesktop.org/software/PackageKit/gtk-doc/Transaction.html
https://github.com/jonschlinkert/remarkable/issues/438
This commit is contained in:
Jelle van der Waa 2026-06-03 20:14:52 +02:00 committed by Jelle van der Waa
parent 6cab90c58b
commit 618473f951
5 changed files with 21 additions and 30 deletions

View file

@ -25,6 +25,7 @@
"prefer-promise-reject-errors": ["error", { "allowEmptyReject": true }], "prefer-promise-reject-errors": ["error", { "allowEmptyReject": true }],
"no-var": "error", "no-var": "error",
"react/jsx-indent": ["error", 4], "react/jsx-indent": ["error", 4],
"react/no-danger": "error",
"semi": ["error", "always", { "omitLastInOneLineBlock": true }], "semi": ["error", "always", { "omitLastInOneLineBlock": true }],
"react-hooks/rules-of-hooks": "error", "react-hooks/rules-of-hooks": "error",

@ -1 +1 @@
Subproject commit 271b14ba1169a06e6d11e4455411c22a10ae97f9 Subproject commit c0741b01b2dd77fc2ce598bbbcb1cfbe44cf4072

View file

@ -20,7 +20,7 @@
"prop-types": "15.8.1", "prop-types": "15.8.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"remarkable": "2.0.1", "markdown-to-jsx": "9.10.3",
"throttle-debounce": "5.0.2", "throttle-debounce": "5.0.2",
"uuid": "14.0.2", "uuid": "14.0.2",
"@xterm/xterm": "6.0.0", "@xterm/xterm": "6.0.0",

View file

@ -41,7 +41,7 @@ import {
SecurityIcon, SecurityIcon,
} from "@patternfly/react-icons"; } from "@patternfly/react-icons";
import { TableText } from "@patternfly/react-table"; import { TableText } from "@patternfly/react-table";
import { Remarkable } from "remarkable"; import Markdown from "markdown-to-jsx";
import { AutoUpdates, getBackend } from "./autoupdates.jsx"; import { AutoUpdates, getBackend } from "./autoupdates.jsx";
import { KpatchSettings, KpatchStatus } from "./kpatch"; import { KpatchSettings, KpatchStatus } from "./kpatch";
@ -225,29 +225,19 @@ function isSafeUrl(href) {
} }
} }
// Overrides the link_open function to apply our required HTML attributes const markdownOptions = {
function customRemarkable() { overrides: {
const remarkable = new Remarkable(); a: {
props: {
rel: "noopener noreferrer",
target: "_blank",
},
},
},
forceInline: true,
};
remarkable.renderer.rules.link_open = function(tokens, idx) { function updateItem(info, pkgNames, key) {
try {
const url = tokens[idx].href;
if (!isSafeUrl(url))
return "<a>";
const a = document.createElement("a");
a.setAttribute("href", url);
a.rel = "noopener noreferrer";
a.target = "_blank";
return a.outerHTML.replace("</a>", "");
} catch {
return "<a>";
}
};
return remarkable;
}
function updateItem(remarkable, info, pkgNames, key) {
const bug_urls = (info.bug_urls || []).filter(isSafeUrl); const bug_urls = (info.bug_urls || []).filter(isSafeUrl);
const cve_urls = (info.cve_urls || []).filter(isSafeUrl); const cve_urls = (info.cve_urls || []).filter(isSafeUrl);
const vendor_urls = (info.vendor_urls || []).filter(isSafeUrl); const vendor_urls = (info.vendor_urls || []).filter(isSafeUrl);
@ -333,8 +323,8 @@ function updateItem(remarkable, info, pkgNames, key) {
descriptionFirstLine = cleanupChangelogLine(descriptionFirstLine); descriptionFirstLine = cleanupChangelogLine(descriptionFirstLine);
let description; let description;
if (info.markdown) { if (info.markdown) {
descriptionFirstLine = <span dangerouslySetInnerHTML={{ __html: remarkable.render(descriptionFirstLine) }} />; descriptionFirstLine = <Markdown options={markdownOptions}>{descriptionFirstLine}</Markdown>;
description = <div dangerouslySetInnerHTML={{ __html: remarkable.render(info.description) }} />; description = <Markdown options={{ ...markdownOptions, forceInline: false }}>{info.description}</Markdown>;
} else { } else {
description = <div className="changelog">{info.description}</div>; description = <div className="changelog">{info.description}</div>;
} }
@ -392,7 +382,6 @@ function updateItem(remarkable, info, pkgNames, key) {
} }
const UpdatesList = ({ updates }) => { const UpdatesList = ({ updates }) => {
const remarkable = customRemarkable();
const combined_updates = []; const combined_updates = [];
// PackageKit doesn"t expose source package names, so group packages with the same version and changelog // PackageKit doesn"t expose source package names, so group packages with the same version and changelog
@ -431,7 +420,7 @@ const UpdatesList = ({ updates }) => {
{ title: _("Severity"), props: { width: 15 } }, { title: _("Severity"), props: { width: 15 } },
{ title: _("Details"), props: { width: 30 } }, { title: _("Details"), props: { width: 30 } },
]} ]}
rows={combined_updates.map(update => updateItem(remarkable, update, packageNames[update.id].sort((a, b) => a.name > b.name), update.id))} /> rows={combined_updates.map(update => updateItem(update, packageNames[update.id].sort((a, b) => a.name > b.name), update.id))} />
); );
}; };

View file

@ -155,8 +155,9 @@ cmd_runtime_tar() {
# special-case node_modules/iconv-lite/encodings/sbcs-data-generated.js, it's JS with binary strings in it # special-case node_modules/iconv-lite/encodings/sbcs-data-generated.js, it's JS with binary strings in it
# and resolve/test/resolver/cup.coffee which is a single-byte test file # and resolve/test/resolver/cup.coffee which is a single-byte test file
# Ubuntu 24.04's file (or at least not that in the GitHub actions runner) does not detect *.woff2 file type properly # Ubuntu 24.04's file (or at least not that in the GitHub actions runner) does not detect *.woff2 file type properly
# markdown-to-jsx's js/cjs is so minified it gets recognized as "data", not JavaScript.
for pkg in $runtime_pkgs; do for pkg in $runtime_pkgs; do
if find "$pkg" -type f -print0 | if find "$pkg" -type f -not -path '*/markdown-to-jsx/dist/*.js' -not -path '*/markdown-to-jsx/dist/*.cjs' -print0 |
xargs -0 file --mime | xargs -0 file --mime |
grep -Ev \ grep -Ev \
-e 'text/' \ -e 'text/' \