OLD | NEW |
(Empty) | |
| 1 <!DOCTYPE html> |
| 2 <html> |
| 3 |
| 4 <head> |
| 5 <title>Introduction to Writing Content Security Policy Tests</title> |
| 6 <link rel="stylesheet" type="text/css" href="README.css"> |
| 7 <link rel="stylesheet" type="text/css" href="http://cdnjs.cloudflare.com/aja
x/libs/highlight.js/8.1/styles/default.min.css"> |
| 8 <script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.1/highligh
t.min.js"></script> |
| 9 <script> |
| 10 hljs.initHighlightingOnLoad(); |
| 11 </script> |
| 12 </head> |
| 13 |
| 14 <body> |
| 15 <h1>Introduction to Writing Content Security Policy Tests</h1> |
| 16 <p>The CSP test suite uses the standard W3C testharness.js framework, but th
ere are a few additional things you'll need to do because of the unique way CSP
works, even if you're already an expert at writing W3C tests. These tests requir
e the use of the |
| 17 <a href="https://github.com/w3c/wptserve">wptserve</a> server (included in t
he <a href="https://github.com/w3c/web-platform-tests">web-platform-tests reposi
tory</a>) to operate correctly.</p> |
| 18 |
| 19 <h2>What's different about writing CSP tests?</h2> |
| 20 |
| 21 <h3>Headers</h3> |
| 22 <p>Content Security Policy is preferentially set through an HTTP header. Thi
s means we can't do our tests just as a simple set of HTML+CSS+JS files. Luckily
the wptserver framework provides an easy method to add headers to a file.</p> |
| 23 <p>If my file is named <span class=code>example.html</span> then I can creat
e a file |
| 24 <span class=code>example.html.headers</span> to define the headers that
will be served with it. If I need to do template substitutions in the headers, I
can instead create a file named <span class=code>example.html.sub.headers</span
>.</p> |
| 25 |
| 26 <h3>Negative Test Cases and Blocked Script Execution</h3> |
| 27 <p>Another interesting feature of CSP is that it <em>prevents</em> things fr
om happening. It even can and prevent script from running. How do we write tests
that detect something didn't happen?</p> |
| 28 |
| 29 <h3>Checking Reports</h3> |
| 30 <p>CSP also has a feature to send a report. We ideally want to check that wh
enever a policy is enforced, a report is sent. This also helps us with the previ
ous problem - if it is difficult to observe something not happening, we can stil
l check that a report fired.</p> |
| 31 |
| 32 <h2>Putting it Together</h2> |
| 33 <p>Here's an example of a simple test. (ignore the highlights for now...) Th
is file lives in the |
| 34 <span class=code>/content-security-policy/script-src/</span> directory.<
/p> |
| 35 |
| 36 <p class=codeTitle>script-src-1_1.html</p> |
| 37 <pre><code class="html"><!DOCTYPE HTML> |
| 38 <html> |
| 39 <head> |
| 40 <script src='/resources/testharness.js'></script> |
| 41 <script src='/resources/testharnessreport.js'></script> |
| 42 </head> |
| 43 <body> |
| 44 <h1>Inline script should not run without 'unsafe-inline' scrip
t-src directive.</h1> |
| 45 <div id='log'></div> |
| 46 |
| 47 <script> |
| 48 test(function() { |
| 49 asset_unreached('Unsafe inline script ran.')}, |
| 50 'Inline script in a script tag should not run without an unsafe-inli
ne directive' |
| 51 ); |
| 52 </script> |
| 53 |
| 54 <img src='doesnotexist.jpg' onerror='test(function() { assert
_false(true, "Unsafe inline event handler ran.") }, "Inline event
handlers should not run without an unsafe-inline directive");'> |
| 55 |
| 56 <script async defer src='../support/checkReport.sub.js?reportField=vi
olated-directive&reportValue=<span class=highlight1>script-src%20%27self%27<
/span>'></script> |
| 57 |
| 58 </body> |
| 59 </html> |
| 60 </code></pre> |
| 61 |
| 62 |
| 63 <p>This code includes three tests. The first one in the script block will ge
nerate a failure if it runs. The second one, in the onerror handler for the img
which does not exist should also generate a failure if it runs. But for a succes
sful CSP implementation, neither of these tests does run. The final test is run
by the link to <span class=code>../support/checkReport.sub.js</span>. It will lo
ad some script in the page (make sure its not blocked by your policy!) which con
tacts the server asynchronously and sees if the expected report was sent. This s
hould always run an generate a positive or negative result even if the inline te
sts are blocked as we expect.</p> |
| 64 |
| 65 <p>Now, to acutally exercise these tests against a policy, we'll need to set
headers. In the same directory we'll place this file:</p> |
| 66 |
| 67 <p class=codeTitle>script-src-1_1.html.sub.headers</p> |
| 68 <pre><code class="html"> |
| 69 Expires: Mon, 26 Jul 1997 05:00:00 GMT |
| 70 Cache-Control: no-store, no-cache, must-revalidate |
| 71 Cache-Control: post-check=0, pre-check=0, false |
| 72 Pragma: no-cache |
| 73 Set-Cookie: <span class=highlight2>script-src-1_1</span>={{$id:uuid()}}; Path=<s
pan class=highlight2>/content-security-policy/script-src/</span> |
| 74 Content-Security-Policy: <span class=highlight1>script-src 'self'</span>; report
-uri <span class=highlight2>..</span>/support/report.py?op=put&reportID={{$id}} |
| 75 </code></pre> |
| 76 <p>This sets some headers to prevent caching (just so we are more likely to
see our latest changes if we're actively developing this test) sets a cookie (mo
re on that later) and sets the relevant <span class=code>Content-Security-Policy
</span> header for our test case.</p> |
| 77 |
| 78 <h4>What about those highlights?</h4> |
| 79 <p>In production code we don't like to repeat ourselves. For this test suite
, we'll relax that rule a little bit. Why? It's easier to have many people contr
ibuting "safe" files using some template substitutions than require every file t
o be executable content like Python or PHP which would require much more careful
code review. The highlights show where you have to be careful as you repeat you
rself in more limited static files. |
| 80 </p> |
| 81 |
| 82 <p>The <span class=highlight1>YELLOW</span> highlighted text is information
that must be the same between both files for report checking to work correctly.
In the html file, we're telling |
| 83 <span class=code>checkReport.sub.js</span> to check the value of the <sp
an class=code> |
| 84 violated-directive</span> key in the report JSON. So it needs to match (
after URL encoding) the directive we set in the header.</p> |
| 85 |
| 86 <p>The <span class=highlight2>PINK</span> highlighted text is information th
at must be repeated from the path and filename of your test file into the header
s file. The name of the cookie must match the name of the test file without its
extension, the path for the cookie must be correct, and the relative path compon
ent to the report-uri must also be corrected if you nest your tests more than on
e directory deep.</p> |
| 87 |
| 88 <h2>Check Your Effects!</h2> |
| 89 <p>A good test case should also verify the state of the DOM in addition to c
hecking the report - after all, a browser might send a report without actually b
locking the banned content. Note that in a browser without CSP support there wil
l be three failures on the example page as the inline script executes.</p> |
| 90 <p>How exactly you check your effects will depend on the directive, but don'
t hesitate to use script for testing to see if computed styles are as expected,
if layouts changed or if certain elements were added to the DOM. Checking that t
he report also fired is just the final step of verifing correct behavior.</p> |
| 91 |
| 92 <p>Note that avoiding inline script is good style and good habits, but not 1
00% necessary for every test case. Go ahead and specify 'unsafe-inline' if it ma
kes your life easier.</p> |
| 93 |
| 94 <h2>Report Existence Only and Double-Negative Tests</h2> |
| 95 <p>If you want to check that a report exists, or verify that a report <em>wa
sn't</em> sent for a double-negative test case, |
| 96 you can pass <strong>?reportExists=</strong><em>[true|false]</em> to <span c
lass=code>checkReport.sub.js</span> instead of <strong>reportField</strong> and
<strong>reportValue</strong>.</p> |
| 97 |
| 98 <h2>How does the magic happen?</h2> |
| 99 <p>Behind the scenes, a few things are going on in the framework.</p> |
| 100 <ol> |
| 101 <li>The {{$id:uuid}} templating marker in the headers file tells the wpt
serve HTTP server to create a new unique id and assign it to a variable, which w
e can re-use as {{$id}}.</li> |
| 102 <li>We'll use this UUID in two places: |
| 103 <ol> |
| 104 <li>As a GET parameter to our reporting script, to uniquely iden
tify this instance of the test case so our report can be stored and retrieved. |
| 105 </li> |
| 106 <li>As a cookie value associated with the filename, so script in
the page context can learn what UUID the report was sent under.</li> |
| 107 </ol> |
| 108 </li> |
| 109 <li>The report listener is a simple python file that stashes the report
value under its UUID and allows it to be retrieved again, exactly once.</li> |
| 110 <li><span class=code>checkReport.sub.js</span> then grabs the current pa
th information and uses that to find the cookie holding the report UUID. It dele
tes that cookie (otherwise the test suite would overrun the maximum size of a co
okie header allowed) then makes an XMLHttpRequest to the report listener to retr
ieve the report, parse it and verify the contents as per the parameters it was l
oaded with.</li> |
| 111 </ol> |
| 112 |
| 113 <p>Why all these gymnastics? CSP reports are delivered by an <em>anonymous f
etch</em>. This means that the browser does not process the response headers, bo
dy, or allow any state changes as a result. So we can't pull a trick like just e
choing the report contents back in a Set-Cookie header or writing them to local
storage.</p> |
| 114 |
| 115 <p>Luckily, you shouldn't have to worry about this magic much, as long as yo
u get the incantation correct.</p> |
| 116 </body> |
| 117 |
| 118 </html> |
OLD | NEW |