I must be doing something wrong because it seems there is no one place documenting how to accept content security policy violation reports in rails. Basic question:
How do we make rails accept a POST with type “application/csp-report” and still have strong params screen it?
The csp_report gem is old and depends on a package with an unpatched CVE, so let’s set that aside. Also I know people use Sentry. I want to accept my own reports.
It seems there are a few undocumented or underdocumented hurdles:
I guess Rails needs to be told to accept “application/csp-report” somehow. I add this line:
# config/initializers/mime_types.rb
Mime::Type.register "application/csp-report", :csp_report
I make this model:
class ContentSecurityPolicyReport < ApplicationRecord
end
By migration, to keep things simple, it has one column:
class CreateContentSecurityPolicyReport < ActiveRecord::Migration[7.2]
def change
create_table :content_security_policy_reports do |t|
t.string :raw_json
t.timestamps
end
end
end
I make this controller:
class ContentSecurityPolicyReportController < ApplicationController
rate_limit to: 10, within: 1.minute
# The browser submitting the report will not have any CSRF token
skip_forgery_protection
def create
# WORKS IN TEST
param = request.request_parameters()
# WORKS IN TEST IF WE CHANGE param BELOW TO params
params.permit(:csp_report, :type)
if param.has_key?('csp-report') || (param.has_key?('type') && param['type'] == 'csp-violation')
ContentSecurityPolicyReport.create!(raw_json: param)
head :ok
else
head :bad_request
end
end
end
I have this line in my CSP:
#config/initializers/content_security_policy.rb
policy.report_uri "/content-security-policy-report-violations"
I have this route:
post '/content-security-policy-report-violations', to: 'content_security_policy_report#create'
I make this spec:
it 'works with old format' do
expect(ContentSecurityPolicyReport.count).to eq 0
post '/content-security-policy-report-violations', params: {"csp-report": {...} }, as: :csp_report
expect(response.status).to eq 200
expect(ContentSecurityPolicyReport.count).to eq 1
end
I actually have three more specs to test the new “type”: ‘csp-violation’, and to test malicious input, and to test real world input.
Everything passes in test using either variant of my params.
I put it out into the wild, and my parameters are no longer coming through.
Has someone written somewhere about how ActionController::Parameters screens stuff out? Shouldn’t accepting CSP violation reports be a pretty basic thing that’s well documented? Any nudge in the right direction would be appreciated. Could we make this thread be the documentation?