Lesson #1461
← Back to Knowledge Board
Strict-CSP nonce-only blocks inline event-handler attributes
- ID
- 1461
- Author
- Agent
- agent-claude
- Reviewed
- ✓ Yes
- Source authority
- 75 / 100
- Source
- yoga's CSP `script-src nonce-only` (no `unsafe-inline`/`unsafe-hashes`) silently breaks `onclick=`/`onload=`/`onsubmit=`/etc β buttons appear dead, deferred-CSS swaps don't fire, fonts stay invisible
- Source issue
- β
- Created at
- 2026-05-12T10:00:23.451910+00:00
- Valid until
- β
- Deprecated at
- β
- Supersedes
- β
- Obsidian path
- /root/.claude/projects/-nvmetank1-projects/memory/feedback_strict_csp_inline_handlers.md
- Obsidian hash
- 0f52ecf119e9ad7a15704466ecd02546
- Tags
- claude-memory,feedback
Content
**Rule:** Before emitting OR copy-pasting any web-perf / interactivity pattern that uses inline event-handler attributes (`onclick="..."`, `onload="..."`, `onsubmit="..."`, `onchange="..."`, `oninput="..."` etc.), verify the target site's CSP. If `script-src` is nonce-only without `unsafe-inline` or `unsafe-hashes`, the inline handler is silently blocked β the feature looks "deployed" but doesn't work.
**Why:** Recurring source of "why is this broken on prod, fine in dev" bugs in yoga. Documented incidents:
- yoga#103-#107 β VerfΓΌgbarkeit-tab, qeMode buttons, customer Select-All, theme-toggle, all CSP-blocked
- 2026-05-05 deferred-CSS regression β `media="print" onload="this.media='all'"` standard pattern from web tutorials BROKE Material Symbols + body fonts β icons rendered as literal-text "calendar_month" labels for ~30 min before user reported
**How to apply:**
1. Default to `addEventListener` from a `<script>` block (auto-nonced by webserver.py:_inject_csp_nonce).
2. Mark targets with `data-*` attributes; bind delegated handler:
```html
<button data-toggle-foo>...</button>
<script>
document.querySelectorAll('[data-toggle-foo]').forEach(el =>
el.addEventListener('click', () => toggleFoo()));
</script>
```
3. For deferred-CSS without inline `onload`: use a nonced `<script>` that calls `link.media = 'all'` on `addEventListener('load')`.
4. Run `python3 bin/lint-csp-handlers.py` before pushing to surface accidental inline handlers (262-baseline, must NOT increase).
**Automation:**
- `.gitea/workflows/security-scan.yml` runs the linter on every push + PR + daily cron (currently warn-only; flip to `--strict` once baseline is cleared).
- Same pattern applies to glug (mirror).
**Memory cross-refs:**
- `feedback_design_in_template.md` (different rule: design changes go in template not framework)
- `feedback_or_loop_tooling.md` (or-loop max_tokens fix β unrelated but in same recent work)