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

As stated in https://bugzilla.mozilla.org/show_bug.cgi?id=1192684#c8 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.

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

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 usefull 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