diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index 3e2daf462..56b013800 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -38,15 +38,53 @@ pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]); // Embedding header: - if let Some(embeddable_on) = embeddable_on { - headers.set_raw( - "X-Frame-Options", - vec![format!("ALLOW-FROM http://{}", address(&embeddable_on)).into_bytes()] - ); + if let Some(ref embeddable_on) = embeddable_on { + headers.set_raw("X-Frame-Options", vec![ + format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes() + ]); } else { // TODO [ToDr] Should we be more strict here (DENY?)? headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]); } + + // Content Security Policy headers + headers.set_raw("Content-Security-Policy", vec![ + // Allow connecting to WS servers and HTTP(S) servers. + // We could be more restrictive and allow only RPC server URL. + b"connect-src http: https: ws: wss:;".to_vec(), + // Allow framing any content from HTTP(S). + // Again we could only allow embedding from RPC server URL. + // (deprecated) + b"frame-src 'self' http: https:;".to_vec(), + // Allow framing and web workers from HTTP(S). + b"child-src 'self' http: https:;".to_vec(), + // We allow data: blob: and HTTP(s) images. + // We could get rid of wildcarding HTTP and only allow RPC server URL. + // (http require for local dapps icons) + b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(), + // Allow style from data: blob: and HTTPS. + b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(), + // Allow fonts from data: and HTTPS. + b"font-src 'self' data: https:;".to_vec(), + // Allow inline scripts and scripts eval (webpack/jsconsole) + b"script-src 'self' 'unsafe-inline' 'unsafe-eval';".to_vec(), + // Restrict everything else to the same origin. + b"default-src 'self';".to_vec(), + // Run in sandbox mode (although it's not fully safe since we allow same-origin and script) + b"sandbox allow-same-origin allow-forms allow-modals allow-popups allow-presentation allow-scripts;".to_vec(), + // Disallow subitting forms from any dapps + b"form-action 'none';".to_vec(), + // Never allow mixed content + b"block-all-mixed-content;".to_vec(), + // Specify if the site can be embedded. + match embeddable_on { + Some((ref host, ref port)) if host == "127.0.0.1" => { + format!("frame-ancestors {} {};", address(&(host.to_owned(), *port)), address(&("localhost".to_owned(), *port))) + }, + Some(ref embed) => format!("frame-ancestors {};", address(embed)), + None => format!("frame-ancestors 'self';"), + }.into_bytes(), + ]); } diff --git a/devtools/src/http_client.rs b/devtools/src/http_client.rs index de59a7a71..29a6d9c7c 100644 --- a/devtools/src/http_client.rs +++ b/devtools/src/http_client.rs @@ -121,4 +121,8 @@ pub fn assert_security_headers_present(headers: &[String], port: Option) { headers.iter().find(|header| header.as_str() == "X-Content-Type-Options: nosniff").is_some(), "X-Content-Type-Options missing: {:?}", headers ); + assert!( + headers.iter().find(|header| header.starts_with("Content-Security-Policy: ")).is_some(), + "Content-Security-Policy missing: {:?}", headers + ) } diff --git a/js/src/ui/DappIcon/dappIcon.js b/js/src/ui/DappIcon/dappIcon.js index 891f45405..dac703129 100644 --- a/js/src/ui/DappIcon/dappIcon.js +++ b/js/src/ui/DappIcon/dappIcon.js @@ -41,7 +41,7 @@ export default class DappIcon extends Component { src={ app.type === 'local' ? `${dappsUrl}/${app.id}/${app.iconUrl}` - : `${dappsUrl}${app.image}` + : `${app.image}` } /> ); diff --git a/js/src/ui/DappIcon/dappIcon.spec.js b/js/src/ui/DappIcon/dappIcon.spec.js index b7bc81653..fce104685 100644 --- a/js/src/ui/DappIcon/dappIcon.spec.js +++ b/js/src/ui/DappIcon/dappIcon.spec.js @@ -64,7 +64,7 @@ describe('ui/DappIcon', () => { it('renders other apps with correct URL', () => { expect(render({ app: { id: 'test', image: '/test.img' } }).props().src).to.equal( - `${DAPPS_URL}/test.img` + `/test.img` ); }); }); diff --git a/rpc/src/v1/extractors.rs b/rpc/src/v1/extractors.rs index 261d3bfbf..67f121526 100644 --- a/rpc/src/v1/extractors.rs +++ b/rpc/src/v1/extractors.rs @@ -140,6 +140,9 @@ fn add_security_headers(res: &mut ws::ws::Response) { headers.push(("X-Frame-Options".into(), b"SAMEORIGIN".to_vec())); headers.push(("X-XSS-Protection".into(), b"1; mode=block".to_vec())); headers.push(("X-Content-Type-Options".into(), b"nosniff".to_vec())); + headers.push(("Content-Security-Policy".into(), + b"default-src 'self';form-action 'none';block-all-mixed-content;sandbox allow-scripts;".to_vec() + )); } fn auth_token_hash(codes_path: &Path, protocol: &str, save_file: bool) -> Option {