How to work-around firefox lack of respect for the CSP specification for CSP reports to Sentry

Summary

As stated in this issue Firefox doesn't respect the specification and doesn't include the fields effective-directive or status-code.

Sentry expect them, and then refuse the reports because of that.

To workaround the issue, I used Nginx LUA module to manipulate the JSON body before it is send to the uwsgi backend of Sentry.

Note: use it at your own risks.

CSP Headers config

The CSP should contains:

Content-Security-Policy: whatever-you; want;... report-uri https://sentry.sigpipe.me/api/the_project_id/csp-report/?sentry_key=your_key&sentry_version=5

Nginx thing

Makes sures the module is enabled, on Debian it's something like:

# cat /etc/nginx/modules-enabled/00-mod-http-ndk.conf

load_module modules/ndk_http_module.so;
# cat /etc/nginx/modules-enabled/50-mod-http-lua.conf

load_module modules/ngx_http_lua_module.so;

# Makes sure there is:

include /etc/nginx/modules-enabled/*.conf;
# before the http {} directive in /etc/nginx/nginx.conf

I also needed to do in /etc/nginx/nginx.conf because the paths seems wrong by default:

http {
...
    lua_package_path "/usr/share/lua/5.1/?.lua;;";
    lua_package_cpath '/usr/lib/x86_64-linux-gnu/lua/5.1/?.so;;';
...

Packages needed are: nginx-extras libnginx-mod-http-lua libnginx-mod-http-ndk lua-cjson

Add LUA call in the virtual host of your Sentry:

...
    location ~ ^/api/(?<projet>[0-9]+)/csp-report/ {
        access_by_lua_file /etc/nginx/proxy_csp.lua;
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:9000;
    }

    location / {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:9000;
    }
...

And the most useful file, /etc/nginx/proxy_csp.lua:

if ngx.req.get_method() == "POST" then
    local cjson = require "cjson"

    -- read body and set local variables, also dump into logs for debugging if needed
    ngx.req.read_body()
    local body = ngx.req.get_body_data()
    --ngx.log(ngx.STDERR, body)
    -- read json body
    local json = cjson.new().decode(body)

    -- We need to manipulate the JSON body to add if missing:
    -- effective-directive: the violated directive name
    -- status-code: HTTP status code of the violated directive

    if (not json['csp-report']['effective-directive']) then
    -- ugly split thing to get the directive name
        words = {}
        local vd = json['csp-report']['violated-directive']
        for word in vd:gmatch("[a-zA-Z0-9-]+") do table.insert(words, word) end

        if (words[1]) then
            json['csp-report']['effective-directive'] = words[1]
        else
            json['csp-report']['effective-directive'] = 'Unknown violation wrong format string'
        end
    end

    if (not json['csp-report']['status-code']) then
        json['csp-report']['status-code'] = 200
    end

    -- reencode new body
    new_json = cjson.encode(json)
    -- set new body
    ngx.req.set_body_data(new_json)

    -- we are done
    return
end