Skip to content

new crate feature: allow_all_protocols_in_img #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 28, 2025
13 changes: 12 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,21 @@ The following bash scripts are useful when working on this project:
The typical security aspect discussed for markdown is [cross-site scripting
(XSS)][xss] attacks.
Markdown itself is safe if it does not include embedded HTML or dangerous
protocols in links/images (such as `javascript:` or `data:`).
protocols in links/images (such as `javascript:`).
`markdown-rs` makes any markdown safe by default, even if HTML is embedded or
dangerous protocols are used, as it encodes or drops them.

Turning on the `allow_dangerous_html` or `allow_dangerous_protocol` options for
user-provided markdown opens you up to XSS attacks.

Additionnally, you should be able to set `allow_any_img_src` safely.
The default is to allow only `http:`, `https:`, and relative images,
which is what GitHub does. But it should be safe to allow any value on `src`.

The [HTML specification][whatwg-html-image] prohibits dangerous scripts in
images and all modern browsers respect this and are thus safe.
Opera 12 (from 2012) is a notable browser that did not respect this.

An aspect related to XSS for security is syntax errors: markdown itself has no
syntax errors.
Some syntax extensions (specifically, only MDX) do include syntax errors.
Expand Down Expand Up @@ -413,3 +422,5 @@ Special thanks go out to:
[support]: .github/support.md

[coc]: .github/code-of-conduct.md

[whatwg-html-image]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model
59 changes: 59 additions & 0 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,16 @@ pub struct CompileOptions {
/// `ircs`, `mailto`, `xmpp`), are safe.
/// All other URLs are dangerous and dropped.
///
/// When the option `allow_all_protocols_in_img` is enabled,
/// `allow_dangerous_protocol` only applies to links.
///
/// This is safe because the
/// [HTML specification][whatwg-html-image-processing]
/// does not allow executable code in images.
/// All modern browsers respect this.
///
/// [whatwg-html-image-processing]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model
///
/// ## Examples
///
/// ```
Expand Down Expand Up @@ -553,6 +563,55 @@ pub struct CompileOptions {
/// ```
pub allow_dangerous_protocol: bool,

/// Whether to allow all values in images.
///
/// The default is `false`,
/// which lets `allow_dangerous_protocol` control protocol safety for
/// both links and images.
///
/// Pass `true` to allow all values as `src` on images,
/// regardless of `allow_dangerous_protocol`.
/// This is safe because the
/// [HTML specification][whatwg-html-image-processing]
/// does not allow executable code in images.
///
/// [whatwg-html-image-processing]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model
///
/// ## Examples
///
/// ```
/// use markdown::{to_html_with_options, CompileOptions, Options};
/// # fn main() -> Result<(), markdown::message::Message> {
///
/// // By default, some protocols in image sources are dropped:
/// assert_eq!(
/// to_html_with_options(
/// "![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)",
/// &Options::default()
/// )?,
/// "<p><img src=\"\" alt=\"\" /></p>"
/// );
///
/// // Turn `allow_any_img_src` on to allow all values as `src` on images.
/// // This is safe because browsers do not execute code in images.
/// assert_eq!(
/// to_html_with_options(
/// "![](javascript:alert(1))",
/// &Options {
/// compile: CompileOptions {
/// allow_any_img_src: true,
/// ..CompileOptions::default()
/// },
/// ..Options::default()
/// }
/// )?,
/// "<p><img src=\"javascript:alert(1)\" alt=\"\" /></p>"
/// );
/// # Ok(())
/// # }
/// ```
pub allow_any_img_src: bool,

// To do: `doc_markdown` is broken.
#[allow(clippy::doc_markdown)]
/// Default line ending to use when compiling to HTML, for line endings not
Expand Down
5 changes: 4 additions & 1 deletion src/to_html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1457,7 +1457,10 @@ fn on_exit_media(context: &mut CompileContext) {
};

if let Some(destination) = destination {
let url = if context.options.allow_dangerous_protocol {
let allow_dangerous_protocol = context.options.allow_dangerous_protocol
|| (context.options.allow_any_img_src && media.image);

let url = if allow_dangerous_protocol {
sanitize(destination)
} else {
sanitize_with_protocols(
Expand Down
16 changes: 16 additions & 0 deletions tests/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,22 @@ fn image() -> Result<(), message::Message> {
"should allow non-http protocols w/ `allowDangerousProtocol`"
);

assert_eq!(
to_html_with_options(
"![](javascript:alert(1))",
&Options {
compile: CompileOptions {
allow_dangerous_protocol: false,
allow_any_img_src: true,
..Default::default()
},
..Default::default()
}
)?,
"<p><img src=\"javascript:alert(1)\" alt=\"\" /></p>",
"should allow non-http protocols with the `allow_any_img_src` option"
);

assert_eq!(
to_mdast(
"a ![alpha]() b ![bravo](charlie 'delta') c.",
Expand Down
31 changes: 31 additions & 0 deletions tests/misc_dangerous_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,34 @@ fn dangerous_protocol_link() {
"should allow a colon in a path"
);
}

#[test]
fn dangerous_protocol_image_with_option() {
use markdown::{to_html_with_options, CompileOptions, Options};

let options = Options {
compile: CompileOptions {
allow_any_img_src: true,
..Default::default()
},
..Default::default()
};

let result = to_html_with_options("![](javascript:alert(1))", &options).unwrap();
assert_eq!(
result, "<p><img src=\"javascript:alert(1)\" alt=\"\" /></p>",
"should allow javascript protocol with allow_any_img_src option"
);

let result = to_html_with_options("![](irc:///help)", &options).unwrap();
assert_eq!(
result, "<p><img src=\"irc:///help\" alt=\"\" /></p>",
"should allow irc protocol with allow_any_img_src option"
);

let result = to_html_with_options("![](mailto:a)", &options).unwrap();
assert_eq!(
result, "<p><img src=\"mailto:a\" alt=\"\" /></p>",
"should allow mailto protocol with allow_any_img_src option"
);
}