Research

Dropbox wrote down how they deployed CSP.

Then they kept it running to this very day.

In September 2015 a Dropbox security engineer published four posts on getting a Content Security Policy into production — including the problem that nearly stopped them. The policy is still in their response headers today, and you can check it in ten seconds.

2015
Deployed and documented
4
Posts on how they did it
3
Named policies today
The Evidence

Still running today

A 2015 engineering series is a story. A live response header is evidence, so here is the evidence first. This is what www.dropbox.com sends, alongside a second policy that starts default-src 'none'. The command underneath it is how you confirm that for yourself rather than taking our word for it:

www.dropbox.com — HTTP response header
content-security-policy: report-to csp-metaserver-dynamic;
  report-uri https://www.dropbox.com/csp_log?policy_name=metaserver-dynamic;
  script-src 'unsafe-eval' 'strict-dynamic'
    'nonce-geJS2gGDCRq2aliITopu54yda80=' 'nonce-SmNs6E/ir+QwOsckvq1zK+Ga9Sg='

Four things in there are worth pulling out. There are two nonces, regenerated per response, so injected script cannot execute whatever domain it claims to come from. There is 'strict-dynamic', which tells modern browsers to ignore the host allowlist entirely and trust the nonce instead. There is a reporting endpoint, because a policy nobody is watching is a policy nobody is maintaining. And the companion policy opens default-src 'none' — deny everything, then permit deliberately, which is the strongest starting point CSP offers.

Check it yourself. One command, no account:

Verify it yourself
curl -sI -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/140.0.0.0" \
  https://www.dropbox.com/ | grep -i content-security-policy

The user agent matters, and it is not a trick to make the numbers look better. Dropbox serves a different policy to clients it cannot identify as a modern browser — covered below, because it turns out to be one of the more interesting things about their setup. Fetch that URL without a user agent and you will get their allowlist policy, no nonce in sight, and conclude this page is wrong.

The Series

Why they wrote it down

In September 2015, Devdatta Akhawe of Dropbox Security published four posts on their engineering blog about deploying CSP. Not an announcement, and not a case study written by a vendor — a working engineer explaining what broke and how they fixed it. He later became Head of Security at Figma.

On Reporting and Filtering

The report noise problem, and the pipeline they built to survive it.

Unsafe-inline and nonce deployment

Getting to nonces without rewriting the application by hand.

The Unexpected Eval

Their fight with eval, including a jQuery patch they open-sourced.

Third Party Integrations and Privilege Separation

What to do about the code you did not write.

Their own definition, from the first post, is still the clearest one-liner on the subject: “CSP is a declarative mechanism to whitelist content sources (such as sources for scripts, objects, images) in a web application.”

The Blocker

The problem they hit first

Not writing the policy. Not deploying it. Reading the reports that came back.

“At scale, one of the first lessons of deploying CSP is the sheer noise in the reports that make the default report mechanism unusable.”

The cause was not attacks on Dropbox. It was the browsers themselves — extensions injecting scripts into pages, and malware doing the same on compromised machines. Every one of those produced a violation report indistinguishable, at first glance, from a real attack.

So before the reports were worth reading, Dropbox built a three-stage filter:

Stage one

Drop impossible schemes

Reports whose blocked URI used chrome://, file: or jar: could only have come from something running on the visitor's own machine.

Stage two

Filter known injectors

Ad injectors and malicious domains — superfish.com, a typosquat of the Mozilla add-ons domain — plus CDNs Dropbox did not use at all.

Stage three

Discard the impossible

Reports naming a violated directive that could never appear in their real policy, such as one containing http: or :443.

That is a meaningful amount of engineering, and it produced no features. It existed so that the reports coming back from a security control could be read by a human being. Every organisation deploying CSP at scale hits the same wall, and in 2015 there was nothing to buy that solved it.

The Rollout

How they rolled it out

The most common objection to CSP is that adopting it means rewriting the application. Dropbox is the counter-example, and the detail is worth having.

Report-only for months before enforcing

The nonce rollout alone ran report-only for nearly a month. Nothing was blocked while they learned what the policy would have broken.

Nonces applied by the templating layer, not by hand

Their server-side HTML generation inserted the nonce attribute into script tags automatically. The change lived in one place rather than across every template.

Inline handlers rewritten mechanically

Deprecated onX= attributes became nonced script blocks that attached the same listener through addEventListener, preserving the original timing behaviour.

A second policy for browsers without nonce support

Injected after DOMContentLoaded, omitting 'unsafe-inline'. Browsers enforce every policy they are given, so this still blocked handlers injected through DOM APIs after load.

They also hit a Firefox bug that reported violations for code which had in fact executed, and recommended turning Firefox reporting off until the fix shipped in Firefox 43. Deploying a security control at scale looks like this: mostly unglamorous, occasionally somebody else's bug.

The result, in their words: “Between Chrome, Firefox, Safari, and Edge, a huge chunk of our users have a strong mitigation in place” — mitigation that holds even when an injection succeeds.

The Detail

Three policies, not one

Something you only notice by fetching the headers twice. Dropbox does not serve one policy to everybody. They run several named ones and choose between them per request:

Policy name Script control Served to
metaserver-dynamicTwo nonces + 'strict-dynamic'Browsers that can use a nonce
metaserver-whitelistHost allowlistClients that cannot be identified as one
coop-dwsCross-origin isolationAlongside the above

Each one reports to its own endpoint, tagged with policy_name, so violations arrive already attributed to the policy that produced them.

That is the same instinct as the 2015 series, still visible today: browsers that can enforce the strong thing get the strong thing, and everything else gets the best available fallback rather than nothing. It is also why the verification command above sends a user agent — ask anonymously and you are served the fallback.

Being Straight

What this page doesn't claim

Four things, before anyone else points them out.

Their policy contains 'unsafe-eval'

It does, and it weakens that part of the policy. The third post in their series is Dropbox documenting their own fight with exactly that, which is more candour than most organisations manage. It does not touch the nonce: injected script still cannot run without the per-response token.

The series is from 2015

Which is why the live header leads this page rather than closing it. The writing is from 2015; the policy is what they sent today.

Dropbox is not a Report URI customer

They built their own pipeline and report to their own endpoint. Nothing here is an endorsement, and we are not going to dress it up as one.

A policy is not a security programme

Dropbox never suggested otherwise — the fourth post in the series adds privilege separation for third-party code on top of the policy. CSP is one control among several.

The Point

What this means for you

Read the four posts together and the shape of the work is clear. Writing the policy was the small part. What took the effort was everything around it: filtering the reports until they meant something, staging the rollout so nothing broke, keeping a second policy for clients that could not use the first, and going back when a dependency turned out to need eval.

Dropbox built all of that themselves because in 2015 there was nothing to buy that did it. That is the part which has changed.

The filtering pipeline Dropbox had to engineer — drop the extension noise, discard the impossible, keep what actually came from your pages — is not a project any more. It is the thing you are pointing a header at.

The policy is still yours. It sits in your response headers, enforced by every browser that visits you, and it keeps working whether or not you have a relationship with us. What we do is the part Dropbox had to build: collect the reports, filter the noise, turn what is left into a policy you can maintain, keep verified copies of the scripts that actually ran, and tell you when any of it changes.

Nothing routes through us. Nothing runs on your pages. See how that maps to PCI DSS 6.4.3 and 11.6.1 →

FAQ

Frequently asked questions

Yes. To this day, www.dropbox.com sends an enforcing policy with default-src 'none', two nonces and 'strict-dynamic', reporting to an endpoint they run themselves. The page carries the command to check that yourself.

Report noise. In their own words, "at scale, one of the first lessons of deploying CSP is the sheer noise in the reports that make the default report mechanism unusable". Almost all of it came from browser extensions and injected malware rather than attacks on their site, and they had to build a three-stage filtering pipeline before the reports were usable.

It did not for Dropbox. Their server-side HTML generation added the nonce to script tags automatically, and deprecated inline event handlers were rewritten mechanically into nonced script blocks that attached the same listener through addEventListener. The work was in the templating layer, not spread across the application.

It weakens one part of the policy and they are open about it - the third post in their series is Dropbox documenting their own fight with eval, including a jQuery patch they open-sourced. It does not undermine the nonce: an injected script still cannot execute without the per-response token, which is the attack strict CSP exists to stop.

No, and we will not imply otherwise. They report to their own endpoint at dropbox.com/csp_log. The point of this page is that in 2015 nobody sold the report-filtering layer they needed, so a team of their calibre had to build it - and that is now a product rather than a project.

Dropbox serves different named policies to different clients. A request with no browser user agent receives their allowlist policy, which has no nonce. The verification command on this page sends a browser user agent, which is what returns the nonce-based policy quoted here.

Skip the part Dropbox had to build.

Set one header and filtered, readable reports arrive from your first visitor. No agent on your pages, nothing in your request path.