max / makenotwork
1 file changed,
+11 insertions,
-6 deletions
| @@ -275,8 +275,9 @@ async fn discount_code_project_scoped() { | |||
| 275 | 275 | // UX-1: a promo code must never reach a JS-string context unescaped. Two layers | |
| 276 | 276 | // now enforce this: (1) custom codes are charset-restricted at creation to | |
| 277 | 277 | // [A-Z0-9_-], so a quote or HTML metacharacter can't enter in the first place; | |
| 278 | - | // (2) the code still renders into an escaped data-* attribute read by the copy | |
| 279 | - | // handler, never interpolated raw into a writeText('...') literal. | |
| 278 | + | // (2) the code renders only into escaped data-* attributes read by the delegated | |
| 279 | + | // copy/redemptions handlers, never into an inline on* handler or a writeText('...') | |
| 280 | + | // literal (the CSP now forbids inline script entirely). | |
| 280 | 281 | #[tokio::test] | |
| 281 | 282 | async fn promo_code_with_quote_is_rejected_and_valid_code_is_not_in_js_string_context() { | |
| 282 | 283 | let mut h = TestHarness::new().await; | |
| @@ -297,8 +298,8 @@ async fn promo_code_with_quote_is_rejected_and_valid_code_is_not_in_js_string_co | |||
| 297 | 298 | rejected.status, rejected.text | |
| 298 | 299 | ); | |
| 299 | 300 | ||
| 300 | - | // Layer 2: a valid code still lands in an escaped data-* attribute, and the | |
| 301 | - | // copy handler reads dataset rather than interpolating the code. | |
| 301 | + | // Layer 2: a valid code lands in escaped data-* attributes read by the | |
| 302 | + | // delegated handlers; no inline script carries the code. | |
| 302 | 303 | let resp = h | |
| 303 | 304 | .client | |
| 304 | 305 | .htmx_post_form( | |
| @@ -309,7 +310,11 @@ async fn promo_code_with_quote_is_rejected_and_valid_code_is_not_in_js_string_co | |||
| 309 | 310 | assert!(resp.status.is_success(), "create failed: {} {}", resp.status, resp.text); | |
| 310 | 311 | ||
| 311 | 312 | let html = &resp.text; | |
| 312 | - | assert!(html.contains("writeText(this.dataset.code)"), "copy must read dataset: {html}"); | |
| 313 | + | // The code is carried in an escaped data attribute for the delegated copy | |
| 314 | + | // handler (data-copy) and the redemptions handler (data-arg2), not inline JS. | |
| 315 | + | assert!(html.contains("data-copy=\"AB-CD\""), "code must live in a data-copy attr: {html}"); | |
| 316 | + | assert!(html.contains("data-action=\"copyText\""), "copy must be a delegated data-action: {html}"); | |
| 317 | + | // No inline script may carry the code: no writeText literal, no onclick. | |
| 313 | 318 | assert!(!html.contains("writeText('"), "code must not be interpolated into a JS string literal"); | |
| 314 | - | assert!(html.contains("data-code="), "code must live in a data-code attr: {html}"); | |
| 319 | + | assert!(!html.contains("onclick"), "no inline onclick handler (CSP forbids inline script): {html}"); | |
| 315 | 320 | } |