Skip to content

feat: frontend security hardening -- ESLint XSS ban + MotionConfig CSP nonce#926

Merged
Aureliolo merged 4 commits intomainfrom
feat/security-hardening-actionable
Mar 30, 2026
Merged

feat: frontend security hardening -- ESLint XSS ban + MotionConfig CSP nonce#926
Aureliolo merged 4 commits intomainfrom
feat/security-hardening-actionable

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Ban dangerouslySetInnerHTML via ESLint no-restricted-syntax rule to catch accidental XSS vectors at write time (zero existing usages)
  • Add <MotionConfig nonce> wrapper in App.tsx so Framer Motion's PopChild style injection is CSP-compliant when nonce-based style-src is enabled
  • New getCspNonce() utility reads nonce from <meta name="csp-nonce"> tag (injected by nginx sub_filter); gracefully returns undefined when absent (current state)
  • Commented-out meta tag placeholder in index.html documents the nginx integration contract

Test plan

  • npm --prefix web run lint -- passes, zero violations (confirms no existing dangerouslySetInnerHTML usage)
  • npm --prefix web run type-check -- clean
  • npm --prefix web run test -- 180 files, 2151 tests pass (6 new tests for getCspNonce: absent meta, valid nonce, caching, empty content, whitespace-only, DOM re-query prevention)
  • Manual: npm --prefix web run dev and navigate between pages to verify AnimatePresence transitions still work with MotionConfig wrapper

Review coverage

Pre-reviewed by 5 agents (docs-consistency, security-reviewer, frontend-reviewer, test-quality-reviewer, issue-resolution-verifier). 4 findings addressed: caching bug fix (Symbol sentinel), whitespace trim, test coverage gaps, comment clarity improvements.

Closes #924

🤖 Generated with Claude Code

Aureliolo and others added 2 commits March 30, 2026 11:20
…P nonce

Ban dangerouslySetInnerHTML via ESLint no-restricted-syntax rule to catch
accidental XSS vectors at write time. Add <MotionConfig nonce> wrapper in
App.tsx so Framer Motion's PopChild style injection is CSP-compliant when
nonce-based style-src is enabled. Nonce is read from a <meta> tag injected
by nginx; gracefully no-ops when absent (current state).

Closes #924

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix getCspNonce caching: use Symbol sentinel so absent-nonce results are
also cached (previously re-queried DOM on every call). Trim whitespace
from nonce content. Add tests for whitespace-only content and DOM
re-query prevention. Improve index.html comment with cross-references
and include rule name in ESLint escape hatch message.

Pre-reviewed by 5 agents, 4 findings addressed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 31afdd61-ba43-4c1c-b1a8-025ff6c97ace

📥 Commits

Reviewing files that changed from the base of the PR and between cbfa204 and 8364e1c.

📒 Files selected for processing (1)
  • CLAUDE.md
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Deploy Preview
  • GitHub Check: Dashboard Build
  • GitHub Check: Dashboard Lint
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-30T10:09:41.160Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:09:41.160Z
Learning: Applies to web/src/components/ui/**/*.{ts,tsx} : When creating new shared web components, place in web/src/components/ui/ with kebab-case filename, create .stories.tsx alongside with all states (default, hover, loading, error, empty), export props as TypeScript interface, use design tokens exclusively with no hardcoded colors/fonts/spacing, and import cn from `@/lib/utils` for conditional class merging

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-30T10:09:41.160Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:09:41.160Z
Learning: Applies to web/src/**/*.{ts,tsx} : Always reuse existing components from web/src/components/ui/ (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast/ToastContainer, Skeleton variants, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup/StaggerItem, Drawer, form fields, TaskStatusIndicator, PriorityBadge, ProviderHealthBadge, TokenUsageBar, CodeMirrorEditor, SegmentedControl, ThemeToggle, LiveRegion, MobileUnsupportedOverlay, LazyCodeMirrorEditor) before creating new components

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
🔇 Additional comments (1)
CLAUDE.md (1)

222-222: Documentation update is accurate and aligned with the security hardening changes.

This line now correctly reflects the added Framer Motion preset utilities and CSP nonce reader in web/src/lib/.


Walkthrough

This pull request adds frontend CSP hardening: an ESLint rule that flags uses of dangerouslySetInnerHTML, a new getCspNonce() utility that reads and caches a nonce from a meta[name="csp-nonce"] tag, tests covering nonce behavior and caching, and wraps the app shell with Framer Motion's MotionConfig passing the nonce. It also adds commented guidance and placeholders for enabling nonce injection in HTML and nginx configuration.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: ESLint XSS ban for dangerouslySetInnerHTML and MotionConfig CSP nonce wrapper.
Description check ✅ Passed The description clearly outlines the four main changes: ESLint rule, MotionConfig wrapper, getCspNonce utility, and commented meta tag placeholder.
Linked Issues check ✅ Passed The PR fulfills all acceptance criteria from issue #924: ESLint rule banning dangerouslySetInnerHTML is added [#924], MotionConfig nonce wrapper integrated in App.tsx [#924], and getCspNonce utility implemented with caching [#924].
Out of Scope Changes check ✅ Passed All changes align with issue #924 objectives. Documentation updates in CLAUDE.md, ux-guidelines.md, security.md, and nginx.conf are supporting materials for the core security hardening features.
Docstring Coverage ✅ Passed Docstring coverage is 50.00% which is sufficient. The required threshold is 40.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 30, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 8364e1c.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

Scanned Files

None

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a CSP nonce handling mechanism, including a utility to retrieve the nonce from a meta tag and integration with framer-motion via MotionConfig. It also adds an ESLint rule to ban dangerouslySetInnerHTML. The review feedback suggests improving the robustness of the getCspNonce utility by guarding against non-browser environments (SSR) and moving the nonce retrieval inside the App component to avoid module-level side effects.

Comment on lines +5 to 13
const nonce = getCspNonce()

export default function App() {
return <AppRouter />
return (
<MotionConfig nonce={nonce}>
<AppRouter />
</MotionConfig>
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling getCspNonce() at the module level introduces a side effect that depends on the document object. While this works for client-side rendering, it's generally better to avoid such side effects at the module level. It can make the code harder to test, can behave unexpectedly with Hot Module Replacement (HMR), and prevents the code from being easily adapted for Server-Side Rendering (SSR) in the future.

A better practice is to retrieve the nonce within the component's lifecycle. This can be done lazily and only once using useState or useMemo.

Suggested change
const nonce = getCspNonce()
export default function App() {
return <AppRouter />
return (
<MotionConfig nonce={nonce}>
<AppRouter />
</MotionConfig>
)
}
import { useState } from 'react'
export default function App() {
const [nonce] = useState(getCspNonce)
return (
<MotionConfig nonce={nonce}>
<AppRouter />
</MotionConfig>
)
}

Comment on lines +63 to +64
// querySelector called once during import-time init + once for first call
// Second call should hit cache, not query DOM again
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This comment is slightly inaccurate. querySelector is not called at import time, but rather on the first call to getCspNonce(). The test logic itself is correct, but the comment could be clarified to avoid confusion for future readers.

Suggested change
// querySelector called once during import-time init + once for first call
// Second call should hit cache, not query DOM again
// querySelector is only called on the first invocation of getCspNonce.
// Subsequent calls should hit the cache and not re-query the DOM.

Comment on lines +15 to +23
export function getCspNonce(): string | undefined {
if (cached !== UNREAD) return cached as string | undefined

const meta = document.querySelector<HTMLMetaElement>(
'meta[name="csp-nonce"]',
)
cached = meta?.content?.trim() || undefined
return cached
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of getCspNonce directly accesses the document object, which will cause a crash if this code is ever run in a non-browser environment, such as during Server-Side Rendering (SSR). To make this utility more robust and future-proof, it's good practice to guard against document being undefined.

Suggested change
export function getCspNonce(): string | undefined {
if (cached !== UNREAD) return cached as string | undefined
const meta = document.querySelector<HTMLMetaElement>(
'meta[name="csp-nonce"]',
)
cached = meta?.content?.trim() || undefined
return cached
}
export function getCspNonce(): string | undefined {
if (cached !== UNREAD) return cached as string | undefined
if (typeof document === 'undefined') {
cached = undefined
return cached
}
const meta = document.querySelector<HTMLMetaElement>(
'meta[name="csp-nonce"]',
)
cached = meta?.content?.trim() || undefined
return cached
}

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/index.html`:
- Around line 7-10: Update the inline comment near the commented meta tag "<meta
name="csp-nonce" content="__CSP_NONCE__" />" to explain the required nginx and
header changes: state that enabling the nonce requires (1) uncommenting this
meta tag, (2) adding a sub_filter directive and sub_filter_once off to
nginx.conf to replace __CSP_NONCE__ per-request, and (3) changing
security-headers.conf to use 'nonce-__CSP_NONCE__' in the style-src policy; also
add a pointer to MotionConfig and lib/csp.ts for runtime nonce handling so
future developers know all three pieces must be updated together.

In `@web/src/__tests__/lib/csp.test.ts`:
- Around line 63-64: The comment is misleading about import-time behavior;
update the test comment near getCspNonce and querySelector to state that
getCspNonce() is lazy and only queries the DOM on the first invocation (then
uses a cache on subsequent calls), so the DOM is queried once during the first
call and once more for the initial import-time query mentioned should be removed
— clarify that the assertion toHaveLength(1) verifies the first call hit and
subsequent calls use the cached value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8805ea85-5110-408a-a881-e374897311ce

📥 Commits

Reviewing files that changed from the base of the PR and between 7d815ca and b003f65.

📒 Files selected for processing (5)
  • web/eslint.config.js
  • web/index.html
  • web/src/App.tsx
  • web/src/__tests__/lib/csp.test.ts
  • web/src/lib/csp.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{ts,tsx}: React component reuse: ALWAYS reuse existing components from web/src/components/ui/ before creating new ones. The design system inventory covers all common patterns.
Design tokens: use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger, etc.) or CSS variables (var(--so-accent)). NEVER hardcode hex values in .tsx/.ts files.
Typography in web code: use font-sans or font-mono (maps to Geist tokens). NEVER set fontFamily directly.
Spacing in web code: use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing. NEVER hardcode pixel values for layout spacing.
Shadows/borders in web code: use token variables (var(--so-shadow-card-hover), border-border, border-bright).
Use cn() from @/lib/utils for conditional class merging in React components.
Do NOT recreate status dots inline - use <StatusBadge> component.
Do NOT build card-with-header layouts from scratch - use <SectionCard> component.
Do NOT create metric displays with text-metric font-bold - use <MetricCard> component.
Do NOT render initials circles manually - use <Avatar> component.
Do NOT create complex (>8 line) JSX inside .map() - extract to a shared component.
Do NOT use rgba() with hardcoded values - use design token variables.
React: TypeScript 6.0 has noUncheckedSideEffectImports defaulting to true - CSS side-effect imports need type declarations (Vite's /// <reference types='vite/client' /> covers this).
React/Web: ESLint zero warnings enforced on web/src/**/*.{ts,tsx} files.
React/Web: use React 19, react-router for routing, shadcn/ui + Radix UI for components, Tailwind CSS 4 for styling.
React/Web: use Zustand for state management and @tanstack/react-query for server state.

Files:

  • web/src/App.tsx
  • web/src/lib/csp.ts
  • web/src/__tests__/lib/csp.test.ts
web/src/**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

React/Web: use Vitest for unit testing with coverage scoped to files changed vs origin/main.

Files:

  • web/src/__tests__/lib/csp.test.ts
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T12:00:18.113Z
Learning: Commits: <type>: <description> — types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook). Signed commits: required on main via branch protection — all commits must be GPG/SSH signed.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : React/Web: ESLint zero warnings enforced on `web/src/**/*.{ts,tsx}` files.
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : React/Web: use React 19, react-router for routing, shadcn/ui + Radix UI for components, Tailwind CSS 4 for styling.

Applied to files:

  • web/src/App.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : React: TypeScript 6.0 has `noUncheckedSideEffectImports` defaulting to true - CSS side-effect imports need type declarations (Vite's `/// <reference types='vite/client' />` covers this).

Applied to files:

  • web/src/App.tsx
  • web/eslint.config.js
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use `cn()` from `@/lib/utils` for conditional class merging in React components.

Applied to files:

  • web/src/App.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : React/Web: use Zustand for state management and tanstack/react-query for server state.

Applied to files:

  • web/src/App.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : React/Web: ESLint zero warnings enforced on `web/src/**/*.{ts,tsx}` files.

Applied to files:

  • web/eslint.config.js
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/tsconfig.json : React: TypeScript 6.0 makes `esModuleInterop` always true - cannot be set to false; remove explicit declaration to avoid deprecation warning.

Applied to files:

  • web/eslint.config.js
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/__tests__/**/*.{ts,tsx} : React/Web: use Vitest for unit testing with coverage scoped to files changed vs origin/main.

Applied to files:

  • web/src/__tests__/lib/csp.test.ts
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).

Applied to files:

  • web/src/__tests__/lib/csp.test.ts
🔇 Additional comments (4)
web/src/lib/csp.ts (1)

1-23: Well-designed caching utility.

The implementation correctly addresses several edge cases:

  • Symbol sentinel (UNREAD) properly distinguishes "never queried" from "queried but absent" — avoiding repeated DOM queries when no nonce exists.
  • Trimming whitespace and converting empty strings to undefined prevents invalid nonce values from propagating.
  • Module-level caching ensures single DOM query per page lifecycle.

The type assertion on line 16 is safe given the preceding guard.

web/eslint.config.js (1)

26-34: Effective XSS prevention rule.

The no-restricted-syntax configuration correctly:

  • Uses the appropriate AST selector to catch dangerouslySetInnerHTML in JSX attributes.
  • Sets error severity to enforce the ban at write time.
  • Provides a clear message with alternatives (text content, sanitization library) and documents the escape hatch for justified exceptions.

This aligns with the PR objective and the learning that ESLint zero warnings are enforced.

web/src/__tests__/lib/csp.test.ts (1)

1-71: Comprehensive test coverage.

The test suite thoroughly validates getCspNonce():

  • Covers missing, empty, whitespace-only, and valid nonce scenarios.
  • Verifies caching behavior for both present and absent results.
  • Uses vi.resetModules() for proper isolation between tests.
  • Correctly uses dynamic imports to get fresh module instances.

This satisfies the PR requirement for test coverage of the caching fix.

web/src/App.tsx (1)

1-13: Clean integration of CSP nonce with Framer Motion.

The implementation correctly:

  • Reads the nonce once at module scope (before first render, when the DOM is already available).
  • Wraps the entire app in MotionConfig to provide the nonce to all motion components.
  • Handles undefined gracefully when no nonce meta tag exists (local dev).

This satisfies the PR objective of making Framer Motion's style injection CSP-compliant.

- Add __CSP_NONCE__ placeholder guard in getCspNonce() (security)
- Move JSDoc to getCspNonce(), add threat model note and activation status
- Remove unnecessary type assertion in getCspNonce() (TS narrows correctly)
- Expand index.html comment with full 3-step nonce activation checklist
- Add nonce activation TODO in nginx.conf with cross-references
- Fix spy leak risk: move vi.restoreAllMocks() to afterEach
- Use querySelectorAll in test cleanup for robustness
- Fix misleading "import-time init" comment in querySelector spy test
- Add tests: placeholder rejection, whitespace trimming to valid nonce
- Update CLAUDE.md lib/ description (add motion.ts, csp.ts)
- Add Frontend Security section to docs/security.md (XSS ban, CSP nonce)
- Expand CSP header row with nonce readiness note
- Add csp.ts to ux-guidelines.md Reference Materials table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 30, 2026 09:48 — with GitHub Actions Inactive
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 30, 2026 10:09 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 6592ed0 into main Mar 30, 2026
32 checks passed
@Aureliolo Aureliolo deleted the feat/security-hardening-actionable branch March 30, 2026 10:16
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 30, 2026 10:16 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.5.1](v0.5.0...v0.5.1)
(2026-03-30)


### Features

* add linear variant to ProgressGauge component
([#927](#927))
([89bf8d0](89bf8d0))
* frontend security hardening -- ESLint XSS ban + MotionConfig CSP nonce
([#926](#926))
([6592ed0](6592ed0))
* set up MSW for Storybook API mocking
([#930](#930))
([214078c](214078c))


### Refactoring

* **web:** replace Sidebar tablet overlay with shared Drawer component
([#928](#928))
([ad5451d](ad5451d))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: frontend security hardening -- actionable items

1 participant