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:
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.
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.
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}
If your plugin uses Velocity templates (.vm files), you can access the CSP nonce through the request attribute cspNonceId
:
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>
<csp>
Module in atlassian-plugin.xmlIf 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>
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 2package 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: