Skip to content

Commit cd36d3c

Browse files
authored
Fix CSP, allow related_origins to embed one another. (dfinity#3658)
Fix CSP, allow related_origins to embed one another. # Changes - Pass `related_origins` into `get_content_security_policy` to set `frame-src` and `frame-ancestors`. # Tests To be tested on beta.
1 parent 6f0ee38 commit cd36d3c

1 file changed

Lines changed: 48 additions & 10 deletions

File tree

  • src/internet_identity_frontend/src

src/internet_identity_frontend/src/main.rs

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ fn post_upgrade(args: InternetIdentityFrontendArgs) {
3737

3838
fn certify_all_assets(args: InternetIdentityFrontendArgs) {
3939
let static_assets = get_static_assets(&args);
40+
let related_origins = args.related_origins.as_ref();
4041

4142
// 2. Extract integrity hashes for inline scripts from HTML files
4243
let integrity_hashes = static_assets
@@ -82,6 +83,7 @@ fn certify_all_assets(args: InternetIdentityFrontendArgs) {
8283
content_type: Some("text/html".to_string()),
8384
headers: get_asset_headers(
8485
integrity_hashes.clone(),
86+
related_origins,
8587
vec![(
8688
"cache-control".to_string(),
8789
NO_CACHE_ASSET_CACHE_CONTROL.to_string(),
@@ -115,7 +117,11 @@ fn certify_all_assets(args: InternetIdentityFrontendArgs) {
115117
path,
116118
content_type: Some(content_type.to_mime_type_string()),
117119
encodings,
118-
headers: get_asset_headers(integrity_hashes.clone(), vec![headers]),
120+
headers: get_asset_headers(
121+
integrity_hashes.clone(),
122+
related_origins,
123+
vec![headers],
124+
),
119125
fallback_for: vec![],
120126
aliased_by: vec![],
121127
}
@@ -137,8 +143,23 @@ fn certify_all_assets(args: InternetIdentityFrontendArgs) {
137143

138144
fn get_asset_headers(
139145
integrity_hashes: Vec<String>,
146+
related_origins: Option<&Vec<String>>,
140147
additional_headers: Vec<HeaderField>,
141148
) -> Vec<HeaderField> {
149+
let credentials_allowlist = if let Some(related_origins) = related_origins {
150+
if related_origins.is_empty() {
151+
"self".to_string()
152+
} else {
153+
let quoted: Vec<String> = related_origins
154+
.iter()
155+
.map(|origin| format!("\"{origin}\""))
156+
.collect();
157+
format!("self {}", quoted.join(" "))
158+
}
159+
} else {
160+
"self".to_string()
161+
};
162+
142163
// List of recommended security headers as per https://owasp.org/www-project-secure-headers/
143164
// These headers enable browser security features (like limit access to platform apis and set
144165
// iFrame policies, etc.)
@@ -155,7 +176,7 @@ fn get_asset_headers(
155176
// Comprehensive policy to prevent XSS attacks and data injection
156177
(
157178
"Content-Security-Policy".to_string(),
158-
get_content_security_policy(integrity_hashes),
179+
get_content_security_policy(integrity_hashes, related_origins),
159180
),
160181
// Strict-Transport-Security (HSTS)
161182
// Forces browsers to use HTTPS for all future requests to this domain
@@ -178,7 +199,8 @@ fn get_asset_headers(
178199
// - sync-xhr=(self): Allow synchronous XMLHttpRequest from same origin
179200
(
180201
"Permissions-Policy".to_string(),
181-
"accelerometer=(),\
202+
format!(
203+
"accelerometer=(),\
182204
autoplay=(),\
183205
camera=(),\
184206
clipboard-read=(),\
@@ -197,14 +219,14 @@ fn get_asset_headers(
197219
midi=(),\
198220
payment=(),\
199221
picture-in-picture=(),\
200-
publickey-credentials-get=(self),\
222+
publickey-credentials-get=({credentials_allowlist}),\
201223
screen-wake-lock=(),\
202224
serial=(),\
203225
sync-xhr=(self),\
204226
usb=(),\
205227
web-share=(),\
206228
xr-spatial-tracking=()"
207-
.to_string(),
229+
),
208230
),
209231
];
210232
headers.extend(additional_headers);
@@ -246,12 +268,18 @@ fn get_asset_headers(
246268
/// font-src 'self':
247269
/// Allow fonts only from same origin
248270
///
249-
/// frame-ancestors 'self':
250-
/// Control embedding - only allow same origin
271+
/// frame-ancestors 'self' <related_origins...>:
272+
/// Control embedding - allow same origin and configured related origins
273+
///
274+
/// frame-src 'self' <related_origins...>:
275+
/// Allow framing only from same origin and configured related origins
251276
///
252277
/// upgrade-insecure-requests (production only):
253278
/// Automatically upgrade HTTP requests to HTTPS (omitted in dev for localhost)
254-
fn get_content_security_policy(integrity_hashes: Vec<String>) -> String {
279+
fn get_content_security_policy(
280+
integrity_hashes: Vec<String>,
281+
related_origins: Option<&Vec<String>>,
282+
) -> String {
255283
let connect_src = "'self' https:";
256284

257285
// Allow connecting via http for development purposes
@@ -272,6 +300,16 @@ fn get_content_security_policy(integrity_hashes: Vec<String>) -> String {
272300
)
273301
};
274302

303+
let embedding_allowlist = if let Some(related_origins) = related_origins {
304+
if related_origins.is_empty() {
305+
"'self'".to_string()
306+
} else {
307+
format!("'self' {}", related_origins.join(" "))
308+
}
309+
} else {
310+
"'self'".to_string()
311+
};
312+
275313
let csp = format!(
276314
"default-src 'none';\
277315
connect-src {connect_src};\
@@ -282,8 +320,8 @@ fn get_content_security_policy(integrity_hashes: Vec<String>) -> String {
282320
style-src 'self' 'unsafe-inline';\
283321
style-src-elem 'self' 'unsafe-inline';\
284322
font-src 'self';\
285-
frame-ancestors 'self';\
286-
frame-src 'self';"
323+
frame-ancestors {embedding_allowlist};\
324+
frame-src {embedding_allowlist};"
287325
);
288326

289327
// For production builds, upgrade all HTTP connections to HTTPS

0 commit comments

Comments
 (0)