Skip to content

Repository files navigation

OWASP Java HTML Sanitizer

Build OpenSSF Best Practices Maven Central

A fast and easy to configure HTML Sanitizer written in Java which lets you include HTML authored by third-parties in your web application while protecting against XSS.

The sanitizer JAR has no runtime dependencies. Its only compile-time dependency is spotbugs-annotations (provided scope, annotations only); the other jars are only needed by the test suite.

This code was written with security best practices in mind, has an extensive test suite, and has undergone adversarial security review.

Table Of Contents

Getting Started

Getting Started includes instructions on how to get started with or without Maven.

Prepackaged Policies

You can use prepackaged policies:

PolicyFactory policy = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
String safeHTML = policy.sanitize(untrustedHTML);

Crafting a policy

The tests show how to configure your own policy:

PolicyFactory policy = new HtmlPolicyBuilder()
    .allowElements("a")
    .allowUrlProtocols("https")
    .allowAttributes("href").onElements("a")
    .requireRelNofollowOnLinks()
    .toFactory();
String safeHTML = policy.sanitize(untrustedHTML);

Custom Policies

You can write custom policies to do things like changing h1s to divs with a certain class:

PolicyFactory policy = new HtmlPolicyBuilder()
    .allowElements("p")
    .allowElements(
        (String elementName, List<String> attrs) -> {
          // Add a class attribute.
          attrs.add("class");
          attrs.add("header-" + elementName);
          // Return elementName to include, null to drop.
          return "div";
        }, "h1", "h2", "h3", "h4", "h5", "h6")
    .toFactory();
String safeHTML = policy.sanitize(untrustedHTML);

Please note that the elements "a", "font", "img", "input" and "span" need to be explicitly whitelisted using the allowWithoutAttributes() method if you want them to be allowed through the filter when these elements do not include any attributes.

Attribute policies allow running custom code too. Adding an attribute policy will not water down any default policy like style or URL attribute checks.

PolicyFactory myPolicy = new HtmlPolicyBuilder()
    .allowElements("div", "span")
    .allowAttributes("data-foo")
        .matching(
            (String elementName, String attributeName, String value) -> {
              // Return value for the attribute or null to drop.
              return value;
            })
        .onElements("div", "span")
    .toFactory();

Preprocessors

Preprocessors allow inserting text and large scale structural changes.

PolicyFactory myPolicy = new HtmlPolicyBuilder()
    .withPreprocessor(
        (HtmlStreamEventReceiver r) -> {
          // Provide user with info about links before they click.
          // Before:                       <a href="https://example.com/...">
          // After:  (https://example.com) <a href="https://example.com/...">
          return new HtmlStreamEventReceiverWrapper(r) {
            @Override public void openTag(String elementName, List<String> attrs) {
              if ("a".equals(elementName)) {
                for (int i = 0, n = attrs.size(); i < n; i += 2) {
                  if ("href".equals(attrs.get(i))) {
                    String url = attrs.get(i + 1);
                    String origin;
                    try {
                      URI uri = new URI(url);
                      String scheme = uri.getScheme();
                      String authority = uri.getRawAuthority();
                      if (scheme == null && authority == null) {
                        origin = null;
                      } else {
                        origin = (scheme != null ? scheme + ":" : "")
                               + (authority != null ? "//" + authority : "");
                      }
                    } catch (URISyntaxException ex) {
                      origin = "about:invalid";
                    }
                    if (origin != null) {
                      text(" (" + origin + ") ");
                    }
                  }
                }
              }
              super.openTag(elementName, attrs);
            }
          };
        })
     .allowElements("a")
     .allowAttributes("href").onElements("a")
     .allowStandardUrlProtocols()
    ...
    .toFactory();

Preprocessing happens before a policy is applied, so cannot affect the security of the output.

Telemetry

When a policy rejects an element or attribute it notifies an HtmlChangeListener.

You can use this to keep track of policy violation trends and find out when someone is making an effort to breach your security.

PolicyFactory myPolicyFactory = ...;
// If you need to associate reports with some context, you can do so.
MyContextClass myContext = ...;

String sanitizedHtml = myPolicyFactory.sanitize(
    unsanitizedHtml,
    new HtmlChangeListener<MyContextClass>() {
      @Override
      public void discardedTag(MyContextClass context, String elementName) {
        // ...
      }
      @Override
      public void discardedAttributes(
          MyContextClass context, String elementName, String... attributeNames) {
        // ...
      }
    },
    myContext);

discardedAttributes also fires for attributes rejected from an element that was then dropped for having none left, such as a link whose only href was rejected. Two default methods carry more detail for listeners that override them: discardedAttribute receives each rejected attribute's value, and discardedText receives tag-like content the policy removed from a kept literal-content element, and content the renderer could not emit from one.

Note: If a string sanitizes with no change notifications, it is not the case that the input string is necessarily safe to use. Only use the output of the sanitizer.

The sanitizer ensures that the output is in a sub-set of HTML that commonly used HTML parsers will agree on the meaning of, but the absence of notifications does not mean that the input is in such a sub-set, only that it does not contain structural content that was removed.

See "Why sanitize when you can validate" for more on this topic.

Questions?

If you wish to report a vulnerability, please see the security policy and the attack review ground rules.

Subscribe to the mailing list or watch this repository's releases and security advisories to be notified of known Vulnerabilities and important updates.

Contributing

The project is led by Jim Manico. Release management and maintenance are shared with Abhishek, Andres Almiray, Ben Evans, Erik Costlow and Brian Fox. Mike Samuel founded the project and wrote the original sanitizer; he is no longer involved in day-to-day maintenance.

If you would like to contribute, open an issue -- that is the best way to reach the maintainers.

We welcome issue reports and PRs. PRs that change behavior or that add functionality should include both positive and negative tests.

Please be aware that contributions fall under the project's dual license: Apache-2.0 OR BSD-2-Clause, at the recipient's option. See COPYING.

License

Dual licensed: Apache-2.0 OR BSD-2-Clause. You may use this software under either the Apache License, Version 2.0 or the BSD 2-Clause License, at your option -- you do not need to comply with both.

COPYING is the authoritative statement of the grant and contains the full text of both licenses. LICENSE holds only the Apache-2.0 arm, so that automated tooling which understands a single license file detects one; it does not narrow the choice offered by COPYING.

Every source file carries an SPDX identifier. Two AntiSamy-derived test files are third-party code under BSD-3-Clause and are listed under THIRD-PARTY CODE in COPYING; they are not compiled into the published artifact.

Credits

Thanks to everyone who has helped with criticism and code

About

Takes third-party HTML and produces HTML that is safe to embed in your web application. Fast and easy to configure.

Resources

Contributing

Security policy

Stars

950 stars

Watchers

34 watching

Forks

Releases

Used by

Contributors

Languages