ammonia

repository·master·Indexed 20 days ago

https://github.com/rust-ammonia/ammonia

A whitelist-based HTML sanitization library for Rust designed to prevent XSS, layout breaking, and clickjacking. It uses the html5ever engine to parse untrusted HTML according to the HTML5 specification and strip elements or attributes not explicitly allowed. Features include a simple clean() function for default sanitization, clean_text() for escaping untrusted text, and a Builder for fine-grained configuration of allowed tags, attributes, URL schemes, and CSS properties.

Tokens
10.6K
Snippets
41
Records
45
Agent score
21%

What's inside ammonia

  1. Sanitize HTML using clean()

    master

    Ammonia is a whitelist-based HTML sanitization library designed to prevent XSS, layout breaking, and clickjacking. It uses html5ever to parse and serialize document fragments according to the HTML5 specification, making it resilient to syntactic obfuscation.

    Important Note on Parsing: Ammonia parses input strictly according to the HTML5 spec. It will not perform automatic transformations like linkifying URLs, inserting line breaks, or converting entities like (C) to ©. If you require these features, use a markup processor (like pulldown-cmark) to generate HTML before passing the result to Ammonia's clean() function.

    use ammonia::clean;
    
    let unsafe_html = "<script>alert('xss')</script><p>Hello</p>";
    let safe_html = clean(unsafe_html);
    // safe_html will contain only the allowed elements, e.g., "<p>Hello</p>"
  2. How Ammonia sanitizes attributes and elements

    master

    Ammonia's sanitization process involves several internal steps to ensure HTML safety:

    1. Tag and Attribute Whitelisting: It checks if a tag is in the allowed list. For attributes, it verifies if they are generic (like class), prefixed with allowed prefixes, or explicitly whitelisted for that specific tag. It also supports validating specific attribute values.
    2. URL Validation: For attributes identified as URLs (e.g., href, src, action, data), Ammonia validates the scheme against a whitelist (e.g., http, https) and can handle relative URL rewriting.
    3. Namespace Protection: To prevent XSS via namespace switching (e.g., moving from HTML to SVG/MathML in ways that bypass security checks), Ammonia validates that namespace transitions follow the HTML specification (e.g., <svg> is the only valid way to enter the SVG namespace from HTML).
    4. Special Attribute Transformations: Ammonia can automatically:
      • Rewrite relative URLs.
      • Add rel attributes to <a> tags.
      • Prefix id attributes with a user-provided string.
      • Filter style properties against a whitelist.
      • Filter class attributes to only include allowed classes.
    5. SVG Animation Handling: It includes specialized logic for SVG <animate> and <set> elements to ensure that animated attribute values (like values, from, or to) are also sanitized according to the target tag's rules.
  3. Configure relative URL handling with UrlRelative

    master

    You can control how relative URLs in attributes (like href) are handled using the url_relative method on the Builder. This is useful for preventing XSS via javascript: URIs or for normalizing links to a specific base URL.

    Supported strategies via UrlRelative:

    • UrlRelative::Deny: Removes the attribute if it is a relative URL.
    • UrlRelative::PassThrough: Keeps the relative URL as is.
    • UrlRelative::RewriteWithBase(url): Rewrites relative URLs using the provided base URL.
    • UrlRelative::Custom(callback): Allows you to provide a custom function to evaluate and potentially transform or deny URLs.
    // Example: Rewriting relative URLs with a base URL
    let result = Builder::new()
        .url_relative(UrlRelative::RewriteWithBase(
            Url::parse("http://example.com/").unwrap(),
        ))
        .clean("<a href=test>Test</a>")
        .to_string();
    // result: <a href="http://example.com/test">Test</a>
  4. Configure relative URL policies with `UrlRelative`

    master

    You can control how relative URLs (those lacking a full scheme) are handled in attributes like src or href using the UrlRelative enum. This policy is applied to any attribute named src or href, as well as the data attribute of an object tag.

    Available policies:

    • Deny: Strips relative URLs entirely.
    • PassThrough: Leaves relative URLs unchanged.
    • RewriteWithBase(Url): Converts relative URLs into absolute URLs using the provided base URL.
    • RewriteWithRoot { root: Url, path: String }: Forces absolute and relative paths into a specific directory structure. It treats the root as the base and uses path to resolve relative paths.
    • Custom(Box<dyn UrlRelativeEvaluate>): Allows you to provide a custom function to evaluate and potentially rewrite the URL.
    use ammonia::{Builder, UrlRelative};
    use url::Url;
    
    // Example: Custom URL rewriting
    fn evaluate(url: &str) -> Option<std::borrow::Cow<str>> {
        if url.starts_with('/') {
            Some(std::borrow::Cow::Owned(format!("/root{}", url)))
        } else {
            Some(std::borrow::Cow::Borrowed(url))
        }
    }
    
    let a = Builder::new()
        .url_relative(UrlRelative::Custom(Box::new(evaluate)))
        .clean("<a href=/test/path>fixed</a>")
        .to_string();
    // Result: <a href="/root/test/path" rel="noopener noreferrer">fixed</a>
  5. Configure custom sanitization with `Builder`

    master

    The Builder struct allows for fine-grained control over the sanitization process. You can define which tags are allowed, which attributes are permitted on specific tags, and which attribute values are whitelisted.

    Important Panics to Avoid:

    • Do not add rel to generic_attributes or tag_attributes for <a> tags if link_rel is set (it is Some("noopener noreferrer") by default).
    • Do not add class to generic_attributes or tag_attributes if allowed_classes is configured.
    • Do not add a tag to both the whitelist (tags) and the content-stripping blacklist (clean_content_tags).
    use ammonia::Builder;
    use maplit::{hashmap, hashset};
    
    let a = Builder::default()
        .link_rel(None)
        .url_relative(ammonia::UrlRelative::PassThrough)
        .clean("<a href=/>test")
        .to_string();
    assert_eq!(a, "<a href=\"/\">test</a>");
  6. Use Ammonia with pulldown-cmark for Markdown sanitization

    master

    A common pattern for user-facing comment sites is to convert Markdown to HTML using pulldown-cmark and then sanitize the resulting HTML with ammonia to ensure safety.

    use ammonia::clean;
    use pulldown_cmark::{Parser, Options, html::push_html};
    
    let text = "[a link](http://www.notriddle.com/)";
    
    let mut options = Options::empty();
    options.insert(Options::ENABLE_TABLES);
    
    let mut md_parse = Parser::new_ext(text, options);
    let mut unsafe_html = String::new();
    push_html(&mut unsafe_html, md_parse);
    
    let safe_html = clean(&*unsafe_html);
    assert_eq!(safe_html, "<a href=\"http://www.notriddle.com/\">a link</a>");
  7. Configure permitted URL schemes

    master

    Control which URL schemes (e.g., http, https, mailto) are allowed in href and src attributes.

    • url_schemes(HashSet<&str>): Overwrites the allowed schemes.
    • add_url_schemes(IntoIter<T>): Appends new schemes to the whitelist.
    • rm_url_schemes(IntoIter<T>): Removes specific schemes.
    • clone_url_schemes(): Returns the current set of schemes.
    use ammonia::Builder;
    use maplit::hashset;
    
    let url_schemes = hashset![
        "http", "https", "mailto", "magnet"
    ];
    let a = Builder::new()
        .url_schemes(url_schemes)
        .clean("<a href=\"magnet:?xt=...\">link</a>")
        .to_string();
  8. Access the underlying DOM with `to_dom_node` (Unstable)

    master

    If you need to perform manual DOM manipulations on the sanitized tree without re-parsing or re-serializing, you can use to_dom_node().

    Warning: This method is considered unstable and is outside of semver guarantees. It may change or be removed.

    To use this, you must enable the ammonia_unstable feature via RUSTFLAGS during compilation:

    Unix-like:

    RUSTFLAGS='--cfg ammonia_unstable' cargo build

    Windows:

    set RUSTFLAGS=--cfg ammonia_unstable
    #[cfg(ammonia_unstable)]
    // Requires RUSTFLAGS='--cfg ammonia_unstable'
    let node = document.to_dom_node();
  9. Configure whitelisted generic attributes

    master

    Use these methods to define a set of attributes that are permitted on any tag.

    • generic_attributes(HashSet<&str>): Overwrites the current set of allowed attributes.
    • add_generic_attributes(IntoIter<T>): Appends new attributes to the whitelist.
    • rm_generic_attributes(IntoIter<T>): Removes specific attributes from the whitelist.
    • clone_generic_attributes(): Returns the current set of attributes.
    use ammonia::Builder;
    use maplit::hashset;
    
    let attributes = hashset!["data-val"];
    let a = Builder::new()
        .generic_attributes(attributes)
        .clean("<b data-val=1>")
        .to_string();
    assert_eq!(a, "<b data-val=\"1\"></b>");
  10. Configure allowed CSS classes for specific tags

    master

    Restrict which CSS classes are allowed on specific HTML tags using a map of tag names to sets of class names.

    • allowed_classes(HashMap<&str, HashSet<&str>>): Overwrites the current class whitelist.
    • add_allowed_classes(tag, IntoIter<T>): Adds classes to an existing tag's whitelist.
    • rm_allowed_classes(tag, IntoIter<T>): Removes classes from a tag's whitelist.
    • clone_allowed_classes(): Returns the current class configuration.

    Warning: If the class attribute is already whitelisted as a generic attribute, adding entries to this map will cause a panic.

    use ammonia::Builder;
    use maplit::{hashmap, hashset};
    
    let allowed_classes = hashmap![
        "code" => hashset!["rs", "ex", "c", "cxx", "js"]
    ];
    let a = Builder::new()
        .allowed_classes(allowed_classes)
        .clean("<code class=rs>fn main() {}</code>")
        .to_string();
    assert_eq!(a, "<code class=\"rs\">fn main() {}</code>");