{ "info": { "author": "Alexander Hultn\u00e9r", "author_email": "ahultner@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "![safemd](resources/castle_wide.jpg) \n[![Twitter Follow](https://img.shields.io/twitter/follow/ahultner.svg?style=social&label=Follow)](https://twitter.com/ahultner)\n[![Add Hultn\u00e9r 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&utm_medium=referral&utm_content=Hultner/safemd&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&utm_medium=referral&utm_content=Hultner/safemd&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\n# \u265c 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 & 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\u2026](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\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/Hultner/safemd/", "keywords": "", "license": "", "maintainer": "Alexander Hultn\u00e9r", "maintainer_email": "ahultner@gmail.com", "name": "safemd", "package_url": "https://pypi.org/project/safemd/", "platform": "", "project_url": "https://pypi.org/project/safemd/", "project_urls": { "Homepage": "https://github.com/Hultner/safemd/" }, "release_url": "https://pypi.org/project/safemd/19.10.1/", "requires_dist": [ "cmarkgfm", "bleach", "black ; extra == 'dev'", "pytest ; extra == 'dev'", "twine ; extra == 'dev'", "pytest-cov ; extra == 'dev'", "codacy-coverage ; extra == 'dev'" ], "requires_python": ">=3.6", "summary": "A markdown renderer focusing on security first", "version": "19.10.1" }, "last_serial": 5912187, "releases": { "18.10": [ { "comment_text": "", "digests": { "md5": "2553a7ea1ffc14f0ae2e020d2c86ad24", "sha256": "4c21ef0edc8b96397418a6343a0534a3315988c1653ed083db1cb4bd9325aae7" }, "downloads": -1, "filename": "safemd-18.10-py3-none-any.whl", "has_sig": false, "md5_digest": "2553a7ea1ffc14f0ae2e020d2c86ad24", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 3789, "upload_time": "2018-10-15T16:28:14", "url": "https://files.pythonhosted.org/packages/bc/b4/8c729a281ed9b4a8b67953330105dfb7e6200ca927256beaa1e48840eb42/safemd-18.10-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4d049cc0e3d8f6f14741bedf974d9e27", "sha256": "468463c7c41125a7a4e3109ae43ef20378e89e75c7841cf38da99e1445b32070" }, "downloads": -1, "filename": "safemd-18.10.tar.gz", "has_sig": false, "md5_digest": "4d049cc0e3d8f6f14741bedf974d9e27", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 4232, "upload_time": "2018-10-15T16:28:15", "url": "https://files.pythonhosted.org/packages/c9/4a/97294dbcaefe2cc4233788caee41994fc3617b9d6d43d1e333c7f051a020/safemd-18.10.tar.gz" } ], "18.10.1": [ { "comment_text": "", "digests": { "md5": "36fe353f2250c0f5740fe26f7b3d62d6", "sha256": "069d59288b8f8b4cfd449db4cd7c51ae40a41eb6bbde5c1f45c5af0543b1fdb5" }, "downloads": -1, "filename": "safemd-18.10.1-py3-none-any.whl", "has_sig": false, "md5_digest": "36fe353f2250c0f5740fe26f7b3d62d6", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 4988, "upload_time": "2018-10-15T18:48:05", "url": "https://files.pythonhosted.org/packages/a6/ec/6df8ab5b0956d50170197c2697bd4b7d969f728333110a9fb3e026ab4b73/safemd-18.10.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "01ec6d87a84b2d1a27e7c9d2024da029", "sha256": "baa97ff0da459659f0ad8df50e0fafec6b11db9977f540e782199e1b5e044f05" }, "downloads": -1, "filename": "safemd-18.10.1.tar.gz", "has_sig": false, "md5_digest": "01ec6d87a84b2d1a27e7c9d2024da029", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 4600, "upload_time": "2018-10-15T18:48:06", "url": "https://files.pythonhosted.org/packages/61/c8/6903904e9fa129cae520712a0958cf0c57d55fd54d91a0d5e60a9f6a4195/safemd-18.10.1.tar.gz" } ], "18.10.2": [ { "comment_text": "", "digests": { "md5": "0129bc8b86cffc9ec772bb10b4889113", "sha256": "d462ea123edd47fd29ae35dc085d984ecb741fb7bb3d332538da0b227da97173" }, "downloads": -1, "filename": "safemd-18.10.2-py3-none-any.whl", "has_sig": false, "md5_digest": "0129bc8b86cffc9ec772bb10b4889113", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 8859, "upload_time": "2018-10-16T12:17:32", "url": "https://files.pythonhosted.org/packages/43/6b/aa973176c4ec428d76d9e8449d720bd0282a44c09b2ebeb8cb41af3d0359/safemd-18.10.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "755aeae3e5ee7e25eb9882edecb9b170", "sha256": "fb8f8756309b7f25322686f682b21a38d72d2805bc365158d68639092a7e9a54" }, "downloads": -1, "filename": "safemd-18.10.2.tar.gz", "has_sig": false, "md5_digest": "755aeae3e5ee7e25eb9882edecb9b170", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 6627, "upload_time": "2018-10-16T12:17:33", "url": "https://files.pythonhosted.org/packages/c7/83/874994110ca862672de23da25bcd92ed7a556ef0cc5aaf69530d80a97246/safemd-18.10.2.tar.gz" } ], "18.10.3": [ { "comment_text": "", "digests": { "md5": "56b207545e091ad7af71143431767957", "sha256": "2207a9c07a97e411ccb71adf58680d5b734161f892b4b3a62230c382d750e44f" }, "downloads": -1, "filename": "safemd-18.10.3-py3-none-any.whl", "has_sig": false, "md5_digest": "56b207545e091ad7af71143431767957", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.7", "size": 8858, "upload_time": "2018-10-30T16:22:30", "url": "https://files.pythonhosted.org/packages/21/44/b86c772a35c21f6a61dd74203e286b4568f364dd14b5f9fda403cebc43fb/safemd-18.10.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "ba611da95781f012fe47ca3e2b3bc8fc", "sha256": "a652aa361c6d05dff8d0e986a8cf6c30a9f2e8e7059bfffd2dc809ed173bb528" }, "downloads": -1, "filename": "safemd-18.10.3.tar.gz", "has_sig": false, "md5_digest": "ba611da95781f012fe47ca3e2b3bc8fc", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.7", "size": 6627, "upload_time": "2018-10-30T16:22:32", "url": "https://files.pythonhosted.org/packages/41/5b/14489e4d5aa5006a8d7a103d93814f9ea7cf7eccbb9007323f99c307d0ea/safemd-18.10.3.tar.gz" } ], "18.10.4": [ { "comment_text": "", "digests": { "md5": "a3a0861b8c96786ad5f40fc0277a1c08", "sha256": "ca78ef847bcb7fa4d0fdf753aaca8abc199e89446b3d8677a54a7f30eedde17b" }, "downloads": -1, "filename": "safemd-18.10.4-py3-none-any.whl", "has_sig": false, "md5_digest": "a3a0861b8c96786ad5f40fc0277a1c08", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 8863, "upload_time": "2019-10-01T12:52:18", "url": "https://files.pythonhosted.org/packages/ab/c7/34286eb28b67eb80840f1304dd0fa0bb1e638aa04f7958ca6f581f6f5513/safemd-18.10.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "293acf3a2b37dff974a84a069a8c4425", "sha256": "4aa8acaf9cbeeed5e909731d0c67609e97cc81236ec48f37d1e31377a8407c89" }, "downloads": -1, "filename": "safemd-18.10.4.tar.gz", "has_sig": false, "md5_digest": "293acf3a2b37dff974a84a069a8c4425", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 6592, "upload_time": "2019-10-01T12:52:20", "url": "https://files.pythonhosted.org/packages/23/4f/e7598a28463e78820ebd9bc289debfee7f42f6d4eb21bd1bc69a94e9058b/safemd-18.10.4.tar.gz" } ], "19.10.1": [ { "comment_text": "", "digests": { "md5": "6a575246ee8ab7027396a6d55bec52ef", "sha256": "006ba3f0f9fe8046c3e4a62a06c9d371df4511624195cfd7323cdc0785f8d5c5" }, "downloads": -1, "filename": "safemd-19.10.1-py3-none-any.whl", "has_sig": false, "md5_digest": "6a575246ee8ab7027396a6d55bec52ef", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 8862, "upload_time": "2019-10-01T13:06:20", "url": "https://files.pythonhosted.org/packages/5d/0a/3a3381969b39fba7b50bd13834a1a7207951a0712e6360f9fb2ee29fb51d/safemd-19.10.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2a566817a0144d55e1f26ef6ff89e44e", "sha256": "0393934fb81a0755a988894f0b0c4bd23a7249fb3722ac1e1dfc6c4513af6aaa" }, "downloads": -1, "filename": "safemd-19.10.1.tar.gz", "has_sig": false, "md5_digest": "2a566817a0144d55e1f26ef6ff89e44e", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 6597, "upload_time": "2019-10-01T13:06:22", "url": "https://files.pythonhosted.org/packages/52/9a/cdecb0184101e6e75befa0ad6fd27397a1425c4796d2a296370aa60c0c2a/safemd-19.10.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "6a575246ee8ab7027396a6d55bec52ef", "sha256": "006ba3f0f9fe8046c3e4a62a06c9d371df4511624195cfd7323cdc0785f8d5c5" }, "downloads": -1, "filename": "safemd-19.10.1-py3-none-any.whl", "has_sig": false, "md5_digest": "6a575246ee8ab7027396a6d55bec52ef", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 8862, "upload_time": "2019-10-01T13:06:20", "url": "https://files.pythonhosted.org/packages/5d/0a/3a3381969b39fba7b50bd13834a1a7207951a0712e6360f9fb2ee29fb51d/safemd-19.10.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2a566817a0144d55e1f26ef6ff89e44e", "sha256": "0393934fb81a0755a988894f0b0c4bd23a7249fb3722ac1e1dfc6c4513af6aaa" }, "downloads": -1, "filename": "safemd-19.10.1.tar.gz", "has_sig": false, "md5_digest": "2a566817a0144d55e1f26ef6ff89e44e", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 6597, "upload_time": "2019-10-01T13:06:22", "url": "https://files.pythonhosted.org/packages/52/9a/cdecb0184101e6e75befa0ad6fd27397a1425c4796d2a296370aa60c0c2a/safemd-19.10.1.tar.gz" } ] }