security: remove style-src 'unsafe-inline' from all CSP policies (#193) #197

Merged
forgejo_admin merged 2 commits from fix/193-csp-unsafe-inline into master 2026-05-22 20:06:28 +00:00

Summary

Closes #193

  • Replace the three inline-style React props that required unsafe-inline with CSS classes and a CSSOM-based approach
  • Remove 'unsafe-inline' from style-src in all three enforcement layers: vite.config.web.ts, nginx.conf, public/_headers

What changed

ErrorExpansion.tsx — two static inline styles moved to CSS:

  • style={{ cursor: "copy" }} → new BEM modifier .file-table__error-text--copyable { cursor: copy }
  • style={{ color: "var(--ec-color-text-secondary)" }} on .file-table__copy-hint → declared in CSS directly

SegmentedControl.tsx — dynamic transform migrated to a CSS custom property:

  • Was: style={{ transform: \translateX(${activeIndex * 100}%)` }}`
  • Now: useLayoutEffect + ref.current.style.setProperty("--ec-segment-offset", ...) — CSSOM property access, not an inline style attribute, so it does not require unsafe-inline
  • CSS: .segmented-control__indicator gets transform: translateX(var(--ec-segment-offset, 0%)) — the default 0% means zero flash on first paint (the useLayoutEffect fires before the browser paints)

CSP updates:

  • vite.config.web.tsstyleSrc now mirrors the scriptSrc pattern: dev retains 'unsafe-inline' (required for Vite HMR), prod emits style-src 'self' only
  • nginx.conf — both CSP directives (root + /assets/) updated
  • public/_headers — Cloudflare Pages header updated

Test plan

  • yarn typecheck — pre-existing ffmpeg errors only (unrelated to this PR)
  • yarn lint — passes
  • yarn test — 568/568 pass
  • yarn build — production build emits style-src 'self' (no unsafe-inline) in the CSP meta tag
  • Open the settings panel and switch theme segments — indicator slides correctly with no flash on load
  • Drop an error-producing file — error text shows cursor: copy, copy-hint text is the correct secondary colour

🤖 Generated with Claude Code

## Summary Closes #193 - Replace the three inline-style React props that required `unsafe-inline` with CSS classes and a CSSOM-based approach - Remove `'unsafe-inline'` from `style-src` in all three enforcement layers: `vite.config.web.ts`, `nginx.conf`, `public/_headers` ## What changed **`ErrorExpansion.tsx`** — two static inline styles moved to CSS: - `style={{ cursor: "copy" }}` → new BEM modifier `.file-table__error-text--copyable { cursor: copy }` - `style={{ color: "var(--ec-color-text-secondary)" }}` on `.file-table__copy-hint` → declared in CSS directly **`SegmentedControl.tsx`** — dynamic transform migrated to a CSS custom property: - Was: `style={{ transform: \`translateX(${activeIndex * 100}%)\` }}` - Now: `useLayoutEffect` + `ref.current.style.setProperty("--ec-segment-offset", ...)` — CSSOM property access, not an inline style attribute, so it does not require `unsafe-inline` - CSS: `.segmented-control__indicator` gets `transform: translateX(var(--ec-segment-offset, 0%))` — the default `0%` means zero flash on first paint (the `useLayoutEffect` fires before the browser paints) **CSP updates:** - `vite.config.web.ts` — `styleSrc` now mirrors the `scriptSrc` pattern: dev retains `'unsafe-inline'` (required for Vite HMR), prod emits `style-src 'self'` only - `nginx.conf` — both CSP directives (root + `/assets/`) updated - `public/_headers` — Cloudflare Pages header updated ## Test plan - [ ] `yarn typecheck` — pre-existing ffmpeg errors only (unrelated to this PR) - [ ] `yarn lint` — passes - [ ] `yarn test` — 568/568 pass - [ ] `yarn build` — production build emits `style-src 'self'` (no `unsafe-inline`) in the CSP meta tag - [ ] Open the settings panel and switch theme segments — indicator slides correctly with no flash on load - [ ] Drop an error-producing file — error text shows `cursor: copy`, copy-hint text is the correct secondary colour 🤖 Generated with [Claude Code](https://claude.com/claude-code)
forgejo_admin added 1 commit 2026-05-22 19:18:53 +00:00
security: remove style-src 'unsafe-inline' from all CSP policies (#193)
Some checks failed
CI / Lint, Typecheck & Unit Tests (pull_request) Failing after 26s
CI / Smoke build (VITE_ENABLE_FFMPEG_FALLBACK=false) (pull_request) Has been skipped
CI / E2E (Web) (pull_request) Has been skipped
CI / E2E (Standalone single-file) (pull_request) Has been skipped
35e177b02b
Replace the three inline-style uses that required unsafe-inline:
- ErrorExpansion: move cursor:copy and color:var(--ec-color-text-secondary)
  into new BEM classes (file-table__error-text--copyable, copy-hint)
- SegmentedControl: replace style={{transform}} with a CSS custom property
  (--ec-segment-offset) set via useLayoutEffect + ref.style.setProperty,
  which is a CSSOM call and does not require unsafe-inline

Remove 'unsafe-inline' from style-src in all three enforcement layers:
- vite.config.web.ts: split styleSrc so dev retains it (Vite HMR) but
  prod builds emit style-src 'self' only
- nginx.conf: both CSP directives updated
- public/_headers: Cloudflare Pages header updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
forgejo_admin added 1 commit 2026-05-22 19:55:56 +00:00
style: fix prettier formatting in ErrorExpansion
All checks were successful
CI / Lint, Typecheck & Unit Tests (pull_request) Successful in 1m12s
CI / Smoke build (VITE_ENABLE_FFMPEG_FALLBACK=false) (pull_request) Successful in 1m27s
CI / E2E (Standalone single-file) (pull_request) Successful in 2m30s
CI / E2E (Web) (pull_request) Successful in 4m32s
edf4e6b3e4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
forgejo_admin merged commit 1c642f5b37 into master 2026-05-22 20:06:28 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: forgejo_admin/exifcleaner-web#197
No description provided.