Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
24 changes: 24 additions & 0 deletions crates/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<!--
Content-Security-Policy — see GitHub issue #175.

Directive notes:
* script-src includes 'wasm-unsafe-eval' so the Leptos WASM module can
execute. 'unsafe-eval' is also required today because several call
sites still use js_sys::eval() (theme bootstrap, focus helpers,
relay-URL probe). Removing it is tracked separately by issues #171
and #425; the CSP must not regress those code paths until then.
* style-src needs 'unsafe-inline' because the Leptos views render
inline style="…" attributes. Google Fonts is allow-listed because
foundation.css @imports the stylesheet from fonts.googleapis.com,
and the actual font files are fetched from fonts.gstatic.com
(font-src).
* connect-src permits ws:/wss: for the relay WebSocket transport
(ws://localhost in dev, wss://… in production) and https: for the
relay's /bootstrap-id HTTP endpoint.
* img-src and media-src include data:/blob: so that avatar data
URIs and runtime URL.createObjectURL blobs (file attachments,
inline media) render. frame-ancestors 'none' blocks clickjacking;
base-uri / form-action / object-src lock down the remaining
injection surfaces.
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval' 'unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ws: wss: https:; img-src 'self' data: blob:; media-src 'self' blob:; worker-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
Expand Down
47 changes: 47 additions & 0 deletions crates/web/tests/static_assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,53 @@ fn index_html_only_references_bundled_svg_icons() {
assert_all_in_allow_list("index.html", &refs);
}

/// Required directives for the CSP meta tag baked into `index.html`.
///
/// Each entry is a substring search against the meta tag's `content` value;
/// they collectively encode the policy decisions described in the inline
/// HTML comment beside the tag (see GitHub issue #175). If you intentionally
/// loosen or tighten one of these directives, update both this list and the
/// inline comment so the rationale stays in sync.
const REQUIRED_CSP_DIRECTIVES: &[&str] = &[
"default-src 'self'",
// WASM module + the still-extant js_sys::eval() sites (tracked by
// issues #171 / #425). Drop 'unsafe-eval' once those are gone.
"script-src 'self' 'wasm-unsafe-eval' 'unsafe-eval'",
// Inline style="…" attrs from Leptos views + Google Fonts CSS @import.
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"font-src 'self' https://fonts.gstatic.com",
// ws/wss for relay transport, https for the relay HTTP bootstrap probe.
"connect-src 'self' ws: wss: https:",
// data: for avatar URIs, blob: for runtime createObjectURL attachments.
"img-src 'self' data: blob:",
"media-src 'self' blob:",
"worker-src 'self'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
];

#[test]
fn index_html_declares_content_security_policy() {
let contents = read_asset("index.html");
let needle = "http-equiv=\"Content-Security-Policy\"";
assert!(
contents.contains(needle),
"index.html is missing the Content-Security-Policy meta tag. \
See GitHub issue #175 — the CSP guards against script injection \
and clickjacking and must stay in the document head.",
);
for directive in REQUIRED_CSP_DIRECTIVES {
assert!(
contents.contains(directive),
"index.html CSP is missing required directive `{directive}`. \
Update both the meta tag in index.html and the rationale \
comment beside it if you are intentionally changing this.",
);
}
}

#[test]
fn manifest_json_only_references_bundled_svg_icons() {
let contents = read_asset("manifest.json");
Expand Down