{"id":"HSEC-2024-0004","summary":"Hackage package and doc upload stored XSS vulnerability","details":"# Hackage package and doc upload stored XSS vulnerability\n\n*Author: Fraser Tweedale (Haskell SRT)*\n\n## Executive summary\n\nA **critical XSS vulnerability** affected *hackage-server* and\n`hackage.haskell.org`.  HTML and JavaScript files provided in source\npackages or via the documentation upload facility were served\n*as-is* on the main `hackage.haskell.org` domain.  As a consequence,\nwhen a user with latent HTTP credentials browses to the package\npages or documentation uploaded by a malicious package maintainer,\ntheir **session can be hijacked** to upload packages or\ndocumentation, amend maintainers or other package metadata, or\nperform any other action the user is authorised to do.\n\nThis issue was discovered and reported to the Haskell Security\nResponse Team (SRT) in June 2024.  Remediations were progressively\ndeveloped and deployed throughout 2025.  **The known issues have\nbeen fully mitigated on `hackage.haskell.org` as of January 2026.**\n\nThe mitigations involve serving user content from a sister domain.\nIn the case of `hackage.haskell.org`, this domain is\n`hackage-content.haskell.org`.\n\nOperators of other *hackage-server* instances should update to the\n`master` branch from the upstream repository\n(https://github.com/haskell/hackage-server; commit\n`9a1887607d9b8d1a3b8b02c990ee144c0d402b79` or later), and configure\nthe user content domain (new flag `--user-content-uri`).\n\n## Scope of the issue\n\n### Package documentation uploads\n\nAny Hackage user can upload documentation bundles for the packages\nthey own.  Typically those are prepared by Haddock, but they may be\ncustomised or modified.  Malicious code can be delivered in several\nways:\n\n- In JavaScript files loaded by an HTML page;\n- In `\u003cscript\u003e` tags on an HTML page;\n- In action attributes (e.g. `onclick`) on an HTML page.\n\n### `quick-jump` JavaScript\n\nIn addition to the supplied HTML pages, parts of the uploaded\ndocumentation bundle are referenced from the main package page (and\ncandidate pages)—in particular, `quick-jump.min.js`, which\nimplements the \"quick jump\" feature, and the corresponding CSS file\n(CSS can also present an XSS risk).  These files are included by the\nfollowing templates:\n\n- `datafiles/templates/Html/package-page.html.st`\n- `datafiles/templates/Html/candidate-page.html.st`\n\nIt is loaded via a `\u003cscript\u003e` tag:\n\n```html\n\u003cscript src=\"$doc.baseUrl$/quick-jump.min.js\" type=\"text/javascript\"\u003e\u003c/script\u003e\n```\n\n### Source tarball browsing\n\nHTML content in the source package is served as `text/html` or\n`application/xhtml+xml`, depending on file extension.  The browser\nrenders the page and executes scripts without restriction.  The same\nscope of exploitation arises as for documentation uploads.\n\n\n## Impact\n\nMalicious scripts can use latent HTTP credentials in the browser to\nperform any action the user is authorised to do, **without user\ninteraction**.  This could include uploading new package versions,\nediting packaging metadata, adding new maintainers, and more.\n\nAdditionally, malicious scripts and HTML could present a counterfeit\nlogin form to the user, tricking the user into divulging their\ncredentials.  The script can then abuse the credentials in the same\nway as the latent credential scenario.\n\n- CVSS vector string: **CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L**\n- CVSS score: **9.9 (Critical)**\n\n\n## Mitigations\n\nThis section describes the code and configuration changes to\nmitigate the issues.\n\n\n### User content domain\n\n*hackage-server* was updated to serve user-uploaded content from a\nseparate DNS domain.  This includes package documentation (excluding\nthe main package description) and source tarball browsing, for both\npublished and candidate packages.\n\nFor the relevant resources, requests on the main domain are\nredirected to the user content domain via an HTTP 301 (\"Moved\nPermanently\") response status.\n\nDoc and source tarballs uploaded prior to the deployment of these\nmitigations are exempted from the redirection, and remain accessible\nvia the main domain (this may be changed in the future).\n\nAlternative approaches considered:\n\n- Serving HTML from the source tarball with `Content-Type:\n  text/plain` would prevent the browser from interpreting them as\n  HTML and executing inline or referenced scripts.  But several\n  packages have custom doc bundles uploaded, offering a rich\n  documentation experience to their users.  Therefore, changing this\n  behaviour would surprise and probably disappoint many users.\n\n\n### quick-jump: reject unrecognised JavaScript and CSS\n\nA small number of versions of `quick-jump.min.js` and\n`quick-jump.css` have appeared in official releases of of the\nHaddock documentation builder program.  *hackage-server* matches the\nSHA-256 digests of the files in the doc bundle against the list of\n\"known good\" files.  If there is no match, *hackage-server* refuses\nto serve the file (403 Forbidden).\n\nThe digest lists are currently hard-coded; the initial values are\nmentioned at the end of this document.  The list needs to be updated\nwhenever the official Haddock quick-jump implementation changes.\n\nAlternative approaches considered:\n\n- Using [*Subresource Integrity (SRI)*][spec-SRI] to limit to\n  known-good hashes.  This would require the same amount of\n  development and maintenance overhead whilst depending on browser\n  implementation for enforcement.\n\n- Ignoring the quick-jump implementation from the doc bundle and\n  serving a single \"golden\" implementation for all packages.\n  Because quick-jump reads the generated `doc-index.json` this\n  approach ran the risk of breaking the feature on older (or newer)\n  uploads if the format of the file changes.  Formally specifying\n  the `doc-index.json` would allow the quick-jump feature to be\n  evolved and improved to bring benefits to all packages and reduce\n  security risks.\n\nThere was considerable debate between the digest allow-list and\n\"golden\" approaches.  The *hackage-server* maintainers had the final\nsay, and the SRT is satisfied that the chosen approach resolves the\nsecurity issues.\n\n[spec-SRI]: https://www.w3.org/TR/SRI/\n\n\n## Acknowledgements\n\n- **Zubin Duggal** reported the issue in documentation uploads.\n- **Duncan Coutts** noticed that the issue also affects source\n  tarball browsing.\n- **Gershom Bazerman** and **Janus Troelsen** implemented the\n  mitigations, and Gershom handled redeploys of the\n  `hackage.haskell.org` service.\n- **Fraser Tweedale** verified and analysed the issue, and\n  coordinated the security response.\n\n## Appendix A.1.  Known-good variants of `quick-jump.min.js`\n\nThis appendix records known variants of `quick-jump.min.js` from the\nGHC and Haddock repositories.  The commit ID, Git object (short) ID,\nand SHA-256 digests are noted.  The SHA-256 digests can be used with\nSubresource Integrity (SRI) or server-side checks to prevent\ndelivery or execution of unrecognised script content.\n\n### From `https://gitlab.haskell.org/ghc/haddock.git`—`master` branch\n\n```\ncommit: e99aefb50ca63e2dbcc95841efbb53cea90151d8 (Sep 23 2017)\nobject: c9f2b445b9\nsha256: e1da96b0d7ab3d72cfe3786def923c5af91ba331858852f1f43a1acfc5ee6966\n\ncommit: 8e88615a23a9f1980a55bd1b3ef9dcc938d95237 (Oct 10 2017)\nobject: cb24f8bdea\nsha256: a273a3ef19c21032afc5f65d1e09933146f183da906ca9d0b4c285095539e0e7\n\ncommit: b4982d87f41d9a4d3f6237bacfd819145723e35b (Oct 30 2017)\nobject: f22f8f2881\nsha256: 8aed621ac2b746751585cbe271631394cacc0e01cca4ef589e11b077b0acd291\n\ncommit: 93c1e6eb9e829a66ff213ec076d529ab008880b3 (Dec 16 2017)\nobject: bfdf04a372\nsha256: 4b10c18a7ad35f032e8cdc0d263716a93878bf06d998b1b66dccff06ceeee89d\n\ncommit: 59812a09eb69cbf12407206381f4c214987b1efd (Apr 3 2018)\nobject: c03e083607\nsha256: ce86bba43edb0534c0faa2d6d0f504877576c5271321e3fbd9638fd4667384a2\n\ncommit: a69311708493efe8524aed0e9d19365f79f2fab3 (Oct 24 2018)\nobject: 06c35c7454\nsha256: 548d676b3e5a52cbfef06d7424ec065c1f34c230407f9f5dc002c27a9666bec4\n```\n\n### From `https://gitlab.haskell.org/ghc/ghc.git`—`master` branch\n\nNote: all of the *objects* listed above also occur in the GHC repo,\nbut the commit history is different (as expected).\n\n```\ncommit: 7776566531e72c415f66dd3b13da9041c52076aa (Nov 13 2019)\nobject: 0b0eeb27d1\nsha256: 7ca43fc2058574846e032bc5493a0ad4568e4fa14fb58558fbf48d3bd6693e59\n\ncommit: 14e554cf682ae975ba356b07672c0f9d465e2a78 (May 24 2024)\nobject: 06c35c7454\nsha256: (seen already)\n```\n\n## Appendix A.2.  Known-good variants of `quick-jump.css`\n\nThis appendix records known variants of `quick-jump.css` from the\nGHC and Haddock repositories.  The commit ID, Git object (short) ID,\nand SHA-256 digests are noted.  The SHA-256 digests can be used with\nSubresource Integrity (SRI) or server-side checks to prevent\ndelivery or use of unrecognised CSS content.\n\n### From `https://gitlab.haskell.org/ghc/haddock.git`—`master` branch\n\n*No changes since integration of haddock into the ghc repo*.\n\n### From `https://gitlab.haskell.org/ghc/ghc.git`—`master` branch\n\n```\ncommit: 9511e587701349093cbe3ac7c00f13583820774f (Feb 7 2021)\nobject: cf10eee4\nsha256: 6bd159f6d7b1cfef1bd190f1f5eadcd15d35c6c567330d7465c3c35d5195bc6f\n\ncommit: 05ccce6e07731f9788a434d6e06f4cadeff3d6ba (Dec 8 2020)\nobject: d656f51c\nsha256: 6997c223e09b340f5f1bb970c930b458f768a0bbbe787cb87f181820a3d122b3\n\ncommit: fa5ec121e2a700137bab8bd48cc30b1e80f58fd4 (Feb 27 2019)\nobject: 8772809c\nsha256: 29fe483bd37ad3feba12f646e9661731127526f246c246b0011b384e11649dff\n\ncommit: fc069bf200f930c21f96ddbbec1d7c5c69f8ba72 (Jan 15 2018)\nobject: 468d8036\nsha256: 1d51573b72bc8a7b9b0dda3beffb7882db78d22a37840203f761e3969d915027\n\ncommit: 0997eb61803a37803ddb6cf7116eb9db1046b2ce (Oct 10 2017)\nobject: ede05042\nsha256: 59693ef3f0d793031b3af58b214af7884c0f63ce6db659ffd7432cf0aa852b51\n\ncommit: d41abb0f606bf5fdbdc0a7bd3758e0c30601b121 (Sep 23 2017)\nobject: b69903c3\nsha256: f95b8b12a8a13dd31add93527e1239fdff6997c7f2396e975e2e415db04b75fb\n```\n","aliases":["CVE-2026-40470"],"modified":"2026-04-14T18:00:10.355627Z","published":"2026-01-16T11:18:20Z","database_specific":{"repository":"https://github.com/haskell/security-advisories","osvs":"https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export","home":"https://github.com/haskell/security-advisories"},"references":[{"type":"FIX","url":"https://github.com/haskell/hackage-server/commit/5a0dc3357b042502fcf684ed7e2464fde1407809"},{"type":"FIX","url":"https://github.com/haskell/hackage-server/commit/33d9ea3de9a298b21b53ba23386af2742384e627"},{"type":"FIX","url":"https://github.com/haskell/hackage-server/commit/354a7de3b7f21ad1346677df515d8e8ec75016e6"}],"affected":[{"package":{"name":"hackage-server","ecosystem":"Hackage","purl":"pkg:hackage/hackage-server"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.1"},{"fixed":"0.6"}]}],"versions":["0.4","0.5.0"],"database_specific":{"human_link":"https://github.com/haskell/security-advisories/tree/main/advisories/published/2024/HSEC-2024-0004.md","osv":"https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export/2024/HSEC-2024-0004.json","source":"https://github.com/haskell/security-advisories/blob/generated/osv-export/2024/HSEC-2024-0004.json"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L"}]}],"schema_version":"1.7.5"}