Skip to content

Commit

Permalink
Add example/w-content-security-policy
Browse files Browse the repository at this point in the history
Resolves #48.
Resolves #49.
  • Loading branch information
aantron committed Jun 9, 2021
1 parent bbf9822 commit 2eddc7b
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 2 deletions.
2 changes: 1 addition & 1 deletion docs/web/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ <h6>Tidy Web framework for OCaml and ReasonML</h6>
</div>

<ul>
<li><code>1.0.0~alpha2</code></li>
<li><code>1.0.0~alpha3</code></li>
<li><code>opam install dream</code></li>
<li><a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream">GitHub</a></li>
<li><a target="_blank" rel="noreferrer noopener" href="https://github.com/aantron/dream/blob/master/src/dream.mli">Edit these docs</a></li>
Expand Down
2 changes: 2 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ if something is missing!

- [**`w-graphql-subscription`**](w-graphql-subscription#files)
&nbsp;&mdash;&nbsp; GraphQL subscriptions.
- [**`w-content-security-policy`**](w-content-security-policy#files)
&nbsp;&mdash;&nbsp; sandboxes Web pages using `Content-Security-Policy`.
- [**`w-esy`**](w-esy#files) &nbsp;&mdash;&nbsp; gives detail on packaging with
[esy](https://esy.sh/), an npm-like package manager.
- [**`w-one-binary`**](w-one-binary#files) &nbsp;&mdash;&nbsp; bakes static
Expand Down
104 changes: 104 additions & 0 deletions example/w-content-security-policy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# `w-content-security-policy`

<br>

The [`Content-Security-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)
(CSP) header is used to control where your Web pages can be embedded, and what
content they can be made to load. This example uses the CSP
[`frame-ancestors`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors)
directive to prevent a page from being loaded inside a frame, which can help
prevent
[clickjacking](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html).
In addition, it tells the browser to send CSP violation reports back to the
server:

```ocaml
let home =
<html>
<body>
<iframe src="/nested"></iframe>
</body>
</html>
let () =
Dream.run
@@ Dream.logger
@@ Dream.router [
Dream.get "/" (fun _ ->
Dream.html home);
Dream.get "/nested" (fun _ ->
Dream.html
~headers:["Content-Security-Policy",
"frame-ancestors 'none'; " ^
"report-uri /violation"]
"You should not be able to see this inside a frame!");
Dream.post "/violation" (fun request ->
let%lwt report = Dream.body request in
Dream.error (fun log -> log "%s" report);
Dream.empty `OK);
]
@@ Dream.not_found
```

<pre><code><b>$ cd example/w-content-security-policy</b>
<b>$ npm install esy && npx esy</b>
<b>$ npx esy start</b></code></pre>

<br>

Visit [http://localhost:8080](http://localhost:8080)
[[playground](http://dream.as/w-content-security-policy)], and your browser
should refuse to show `/nested` inside the frame on the home page. In addition,
the server log will show something like

```
09.06.21 09:54:35.971 ERROR REQ 3 {
"csp-report": {
"document-uri": "http://localhost:8080/",
"referrer": "",
"violated-directive": "frame-ancestors",
"effective-directive": "frame-ancestors",
"original-policy": "frame-ancestors 'none'; report-uri /violation",
"disposition": "enforce",
"blocked-uri": "http://localhost:8080/",
"status-code": 200,
"script-sample": ""
}
}
```

<br>

You can use CSP to limit which resources can be loaded by the pages you serve,
forbid execution of JavaScript `eval`, and so on. You may want to apply CSP by
writing a wrapper around
[`Dream.html`](https://aantron.github.io/dream/#val-html), or in a middleware.
Note that static file loaders such as
[`Dream.from_filesystem`](https://aantron.github.io/dream/#val-from_filesystem)
can also serve HTML pages, so if you choose not to use a middleware and have
static HTML pages, be sure to write a custom static loader as well.

Dream does not offer a default CSP, because it will inevitably interfere with
development, depending on what each Web app is using. Also, it is possible not
to use CSP at all &mdash; CSP is only a defense-in-depth technique. However, it
is highly recommended to eventually look through the CSP directives as your Web
app develops. When enabling CSP, also consider
[`Strict-Transport-Security`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security).

<br>

**See:**

- [`Content-Security-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) on MDN
- [`Strict-Transport-Security`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) on MDN
- OWASP [*Content Security Policy Cheat Sheet*](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html)
- OWASP [*Clickjacking Defense Cheat Sheet*](https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html)
- OWASP [*HTTP Strict Transport Security Cheat Sheet*](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.html)

<br>

[Up to the example index](../#examples)
29 changes: 29 additions & 0 deletions example/w-content-security-policy/content_security_policy.eml.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
let home =
<html>
<body>
<iframe src="/nested"></iframe>
</body>
</html>

let () =
Dream.run
@@ Dream.logger
@@ Dream.router [

Dream.get "/" (fun _ ->
Dream.html home);

Dream.get "/nested" (fun _ ->
Dream.html
~headers:["Content-Security-Policy",
"frame-ancestors 'none'; " ^
"report-uri /violation"]
"You should not be able to see this inside a frame!");

Dream.post "/violation" (fun request ->
let%lwt report = Dream.body request in
Dream.error (fun log -> log "%s" report);
Dream.empty `OK);

]
@@ Dream.not_found
11 changes: 11 additions & 0 deletions example/w-content-security-policy/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(executable
(name content_security_policy)
(libraries dream)
(preprocess (pps lwt_ppx)))

(rule
(targets content_security_policy.ml)
(deps content_security_policy.eml.ml)
(action (run dream_eml %{deps} --workspace %{workspace_root})))

(data_only_dirs _esy esy.lock)
1 change: 1 addition & 0 deletions example/w-content-security-policy/dune-project
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(lang dune 2.0)
17 changes: 17 additions & 0 deletions example/w-content-security-policy/esy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"dependencies": {
"@opam/dream": "1.0.0~alpha2",
"@opam/dune": "^2.0",
"ocaml": "4.12.x"
},
"devDependencies": {
"@opam/ocaml-lsp-server": "*",
"@opam/ocamlfind-secondary": "*"
},
"resolutions": {
"@opam/conf-libev": "esy-packages/libev:package.json#0b5eb6685b688649045aceac55dc559f6f21b829"
},
"scripts": {
"start": "dune exec --root . ./content_security_policy.exe"
}
}
1 change: 1 addition & 0 deletions example/z-playground/server/sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ example i-graphql
example j-stream
example k-websocket
example w-graphql-subscription
example w-content-security-policy
example w-long-polling
example w-multipart-dump
example w-query
Expand Down
8 changes: 7 additions & 1 deletion src/dream.mli
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,13 @@ val html :
?headers:(string * string) list ->
string -> response promise
(** Same as {!Dream.respond}, but adds [Content-Type: text/html; charset=utf-8].
See {!Dream.text_html}. *)
See {!Dream.text_html}.
As your Web app develops, consider adding [Content-Security-Policy] headers,
as described in example
{{:https://github.com/aantron/dream/tree/master/example/w-content-security-policy#files}
[w-content-security-policy]}. These headers are completely optional, but
they can provide an extra layer of defense for a mature app. *)

val json :
?status:status ->
Expand Down

0 comments on commit 2eddc7b

Please sign in to comment.