{"id":13936080,"url":"https://github.com/Hultner/safemd","last_synced_at":"2025-07-19T21:31:38.566Z","repository":{"id":50200895,"uuid":"153146657","full_name":"Hultner/safemd","owner":"Hultner","description":"Safety first markdown rendering","archived":false,"fork":false,"pushed_at":"2022-12-08T06:35:17.000Z","size":648,"stargazers_count":77,"open_issues_count":5,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-10T07:37:14.941Z","etag":null,"topics":["markdown","md","python","python3","render","rendering","security"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hultner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-15T16:30:23.000Z","updated_at":"2024-01-04T16:27:05.000Z","dependencies_parsed_at":"2023-01-24T18:45:48.310Z","dependency_job_id":null,"html_url":"https://github.com/Hultner/safemd","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/Hultner/safemd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hultner%2Fsafemd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hultner%2Fsafemd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hultner%2Fsafemd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hultner%2Fsafemd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hultner","download_url":"https://codeload.github.com/Hultner/safemd/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hultner%2Fsafemd/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266019657,"owners_count":23864916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["markdown","md","python","python3","render","rendering","security"],"created_at":"2024-08-07T23:02:21.879Z","updated_at":"2025-07-19T21:31:33.556Z","avatar_url":"https://github.com/Hultner.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"![safemd](resources/castle_wide.jpg) \n[![Twitter Follow](https://img.shields.io/twitter/follow/ahultner.svg?style=social\u0026label=Follow)](https://twitter.com/ahultner)\n[![Add Hultnér on LinkedIn](https://img.shields.io/badge/linkedin-hultner-blue.svg)](https://www.linkedin.com/in/hultner/)\n[![Build Status](https://travis-ci.org/Hultner/safemd.svg?branch=master)](https://travis-ci.org/Hultner/safemd)\n![PyPI - Status](https://img.shields.io/pypi/status/safemd.svg)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/21872a9d5f154750b457e6207a83298d)](https://www.codacy.com/app/Hultner/safemd?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=Hultner/safemd\u0026amp;utm_campaign=Badge_Grade)\n[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/21872a9d5f154750b457e6207a83298d)](https://www.codacy.com/app/Hultner/safemd?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=Hultner/safemd\u0026amp;utm_campaign=Badge_Coverage)\n![PyPI](https://img.shields.io/pypi/v/safemd.svg)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)\n\u003c!--\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/safemd.svg)\n[![GitHub license](https://img.shields.io/github/license/Hultner/safemd.svg)](https://github.com/Hultner/safemd/blob/master/LICENSE)--\u003e\n# ♜ safemd\n**A markdown renderer focusing on security first**  \nBuilding upon the strong foundation of GitHub's fork of [cmark][] while adding\n[additional security precautions](#precautions-taken) to be safe out of the box. \n\nWhen auditing applications rendering markdown from user input I noticed that\nmany popular markdown implementations are unsafe and vulnerable to XSS with\nstandard configuration. \n\nI am a strong believer in safe by default instead of opt-in security. Therefor \nI decided to build a library focusing on using the best tools for the job while\nconfiguring them to safely render unsanitized user input. \n\n## Installation \u0026 usage\n**Install through pip:**  \nAny other tool using PyPI works fine as well, I always recommend using virtual\nenvironments.\n```shell\n$ pip install safemd\n```\n\nRender standard markdown:\n```python\nimport safemd\n\nsafemd.render(content)\n```\n\nRender GitHub Flavoured Markdown:\n```python\nimport safemd\n\nsafemd.render(content, flavour=\"github\")\n```\n\n## Precautions taken\nThe same [library][cmarkgfm] used for rendering markdown in the official PyPI \nWarehouse application is used by safemd. This is based on GitHub's \nbattle-tested [fork][cmark] of CommonMark. We use this with the safety feature \n`CMARK_OPT_SAFE` enabled per default, so no one in your team accidentally let\ninsecure code slip through. As an additional safety layer safemd also pass the\noutput from cmark through a whitelist with [Bleach][bleach], Mozilla's HTML sanitizing\nlibrary. \n\nAutomatic safety testing through Travis is also utilized, running daily even if\nthere are no new changes.\n\n### Opt-out from safety features\nThere's a way to opt-out of these safety precautions for those cases where you \nhave a genuine need, this way it's obvious for you and your team that these\nplaces are to be considered with extra care.\n```python\nimport safemd\n\n# Disable additional whitelist sanitizing through bleach\nsafemd.render(content, UNSAFE_NO_BLEACH=True)\n\n# Disable cmark safety functions\nsafemd.render(content, UNSAFE_CMARK_XSS=True)\n\n```\n\n## Is my application vulnerable? \nIt's not uncommon for various markdown-renderers in production environments to\nbe open for XSS-exploits, some more widespread than others. A list of common\nexploits have been assembled for your convenience, so you can test your current\nand future code.\n```md\n[Just a link](javascript:alert(\"hi\"))\n\n[Normal link](data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGkiKTwvc2NyaXB0Pgo=) \n\n[Nothing fishy here](data:text/html;base64,PHNjcmlwdCBzcmM9Imh0dHBzOi8vZ2lzdGNkbi5naXRoYWNrLmNvbS9IdWx0bmVyL2JjMDIzOGJkOWIxZDI4M2JhMWM5NDczZjU0M2ZmZjc4L3Jhdy9kM2U5YWFkYTdlMGRlNzFkNmNlYTY1MDVmMTljZGE2NjE1MmE0MDFlL2hpLmpzIiBpbnRlZ3JpdHk9InNoYTM4NC0yaGZ6aFlkelB1SGd0S1E2Vk96UGlNbEN2Nzl3WDM1NzdxTDR3eWpmNWhMYkEvcW1BZHhCbXdxNGl6YXRwRy93IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj48L3NjcmlwdD4=)\n```\n\n## Markdown XSS exploits found in the wild\nOf course, this document wouldn't be complete without a list of markdown-based\nXSS-exploits found in the wild. Most of these are from 2018 and 2017.\n  - [Valve, store.steampowered.com markdown XSS](https://hackerone.com/reports/313250)\n  - [GitLab, Markdown XSS](https://hackerone.com/reports/270999), [internal](https://about.gitlab.com/2017/10/17/gitlab-10-dot-0-dot-4-security-release/)\n  - [PasteBin, markdown XSS (twice)](https://medium.com/@Nhoya/xss-in-pastebin-com-via-unsanitized-output-e216190b7949)\n    - [CVE-2017-1000459](https://www.cvedetails.com/cve/CVE-2017-1000459/)\n  - [Google Colaboratory, XSS + CSP Bypass](https://blog.bentkowski.info/2018/06/xss-in-google-colaboratory-csp-bypass.html)\n  - [Zendesk, Markdown based Stored XSS](https://blog.0daylabs.com/2016/02/15/stored-xss-on-zendesk/)\n  - [Streamlabs, account comromise XSS](https://blog.rockhouse.ga/2017/12/31/streamlabs-stored-xss-in-donation-page-leading-to-account-compromise-and-my-first-reward/)\n  - [Commento](https://github.com/adtac/commento-ce/issues/154)\n  - [Leanote](https://github.com/leanote/leanote/issues/719)\n  - [Markdown's XSS Vulnerability (and how to mitigate it), showdownjs](https://github.com/showdownjs/showdown/wiki/Markdown%27s-XSS-Vulnerability-%28and-how-to-mitigate-it%29)\n  - [And the list goes on…](https://www.google.com/search?q=markdown+xss)\n\n## Found something\nI am grateful for all suggestions, improvements and bugfixes. Feel free to send\na PR or create a GitHub Issue for anything that isn't sensitive and urgent.\nAdditional tests trying to break the security is especially appriciated.\n\nI'm on [keybase](https://keybase.io/encrypt#hultner) for encrypted communication. \nSend an email to security on my own domain hultner.se. Be aware, I discard any\nSPF, DMARC or DKIM-failing message, including SPF-Soft fail.\n\n---\n```\n .\n..:\n```\n\n[cmark]: https://github.com/github/cmark-gfm\n[cmarkgfm]: https://github.com/theacodes/cmarkgfm\n[bleach]: https://github.com/mozilla/bleach\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHultner%2Fsafemd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHultner%2Fsafemd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHultner%2Fsafemd/lists"}