Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CSP header to treat served files as belonging to a separate origin #3341

Merged
merged 2 commits into from
Mar 9, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions notebook/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,10 @@ def set_headers(self):
# disable browser caching, rely on 304 replies for savings
if "v" not in self.request.arguments:
self.add_header("Cache-Control", "no-cache")

# In case we're serving HTML/SVG, confine any Javascript to a unique
# origin so it can't interact with the notebook server.
self.set_header('Content-Security-Policy', 'sandbox allow-scripts')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also overrides any currently set Content-Security-Policy -- should we merge with the operator's settings?

Note: I refer to operator to mean whoever administers this deployment, whether a local server user or a jupyterhub administrator.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point. Do you think it's sufficient to use add_header() to do this, or should we get into checking any existing Content-Security-Policy headers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can multiple CSP headers be set and do what we want? If not, we might want to override the inherited content_security_policy property instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From MDN:

You can use the Content-Security-Policy header more than once... Adding additional policies can only further restrict the capabilities of the protected resource

I think that means this should work, but maybe the c_s_p property is a neater way to achieve the same thing?

Copy link
Member

@rgbkrk rgbkrk Feb 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I didn't realize we could add multiple headers with the same key. How does set_header work when there are multiple headers with the same key from add_header?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think set_header blows away anything that was set before.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My question comes down to "what's the set of headers after running the following"

self.add_header("X", "y")
self.add_header("X", "z")
self.set_header("X", "x")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AIUI, only X: x would remain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking using only one header is good. For now I think the value you've set here is a very sane default and further enhancements on merging content security policies can be separate work.


def compute_etag(self):
return None
Expand Down
4 changes: 4 additions & 0 deletions notebook/files/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ def get(self, path, include_body=True):
else:
self.set_header('Content-Type', 'text/plain; charset=UTF-8')

# In case we're serving HTML/SVG, confine any Javascript to a unique
# origin so it can't interact with the notebook server.
self.set_header('Content-Security-Policy', 'sandbox allow-scripts')

if include_body:
if model['format'] == 'base64':
b64_bytes = model['content'].encode('ascii')
Expand Down