Content Security Policy Compliance Guide for Plugin Authors

This guide explains how to make your Bitbucket plugin compliant with the Content Security Policy (CSP) headers that Bitbucket produces. Bitbucket implements CSP to prevent cross-site scripting (XSS) attacks and other code injection vulnerabilities. For more detail on CSP headers, see the MDN documentation.

There are two main ways to comply with CSP in your plugin:

  1. Adding a nonce attribute to inline script and style tags: This is the recommended approach for most plugins. Nonces are unique values that are generated for each request and included in the CSP header.

  2. Using the <csp> Module: This approach is useful when your plugin adds resources from external domains into frontend code. It allows you to modify the CSP headers to allow these resources from external domains.

Using Nonces in Soy Templates

1
2
{template .myTemplate}
    <script nonce="{cspNonce()}" type="text/javascript">
        // Your JavaScript code here
        require('my-plugin/my-module').initialize();
    </script>

    <style nonce="{cspNonce()}">
        .my-class {
            color: #ff0000;
            font-weight: bold;
        }
    </style>
{/template}

Using Nonces in Velocity Templates

If your plugin uses Velocity templates (.vm files), you can access the CSP nonce through the request attribute cspNonceId:

Script Tags in Velocity

1
2
<script nonce="$!request.getAttribute('cspNonceId')" type="text/javascript">
    // Your JavaScript code here
    require('my-plugin/my-module').initialize();
</script>

<style nonce="$!request.getAttribute('cspNonceId')">
    .my-class {
        color: #ff0000;
        font-weight: bold;
    }
</style>

Using the <csp> Module in atlassian-plugin.xml

If your plugin needs to modify the CSP headers (for example, to allow resources from external domains), you can define a CSP module in your atlassian-plugin.xml:

1
2
<atlassian-plugin>
    <csp key="my-csp-fragment" class="com.example.plugin.MyCspFragment"/>
</atlassian-plugin>

Creating a CSP Fragment Class

Your CSP fragment class must implement com.atlassian.security.csp.api.CspFragment. To do this, you'll first need to add the atlassian-secure-api library to your plugin's dependencies. If using Maven, this will look like:

1
2
<dependency>
    <groupId>com.atlassian.security</groupId>
    <artifactId>atlassian-secure-api</artifactId>
    <scope>provided</scope>
</dependency>

Here is an example CspFragment:

1
2
package com.example.plugin;

import com.atlassian.security.csp.api.CspDirective;
import com.atlassian.security.csp.api.CspFragment;

import java.net.URI;
import java.util.Set;

public class MyCspFragment implements CspFragment {

    @Override
    public Set<CspDirective> getCSPDirectives() {
        return Set.of(CspDirective.IMG_SRC, CspDirective.SCRIPT_SRC);
    }

    @Override
    public Set<URI> getCSPOrigins(CspDirective cspDirective) {
        switch (cspDirective) {
            case CspDirective.IMG_SRC:
                return Set.of(URI.create("https://images.example.com/"));
            case CspDirective.SCRIPT_SRC:
                return Set.of(URI.create("https://scripts.example.com/"));
            default:
                return Set.of();
        }
    }

    /**
     * Return ant matchers for the urls that should include this CSP fragment.
     * These must match including the context path prefix on the URI, which will be '/bitbucket'
     * when this plugin is loaded into Bitbucket.
     * Beginning the pattern with `/*` will allow it to work in any host or context path.
     */
    @Override
    public Set<String> getUrlPatterns() {
        return Set.of("/*/plugins/servlet/my-plugin/**");
    }
}

Rate this page: