Skip to content

Commit b003f65

Browse files
Aurelioloclaude
andcommitted
fix: address review findings -- caching bug, tests, comment clarity
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>
1 parent 13a9d5a commit b003f65

4 files changed

Lines changed: 36 additions & 6 deletions

File tree

web/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default tseslint.config(
2929
selector: 'JSXAttribute[name.name="dangerouslySetInnerHTML"]',
3030
message:
3131
'dangerouslySetInnerHTML is banned -- use text content or a sanitization library. ' +
32-
'If absolutely necessary, add // eslint-disable-next-line with a justification comment.',
32+
'If absolutely necessary, add // eslint-disable-next-line no-restricted-syntax with a justification comment.',
3333
},
3434
],
3535
// Rule flags every obj[var] with no data-flow analysis -- too many false

web/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<meta name="description" content="SynthOrg -- monitoring and management dashboard for synthetic organizations" />
77
<!-- CSP nonce: nginx sub_filter replaces __CSP_NONCE__ on each request.
8-
Uncomment when CSP is tightened to use nonce-based style-src. -->
8+
Uncomment when style-src 'unsafe-inline' in security-headers.conf is
9+
replaced with nonce-based policy. See App.tsx MotionConfig + lib/csp.ts. -->
910
<!-- <meta name="csp-nonce" content="__CSP_NONCE__" /> -->
1011
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
1112
<title>SynthOrg Dashboard</title>

web/src/__tests__/lib/csp.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,31 @@ describe('getCspNonce', () => {
4242
const { getCspNonce } = await import('@/lib/csp')
4343
expect(getCspNonce()).toBeUndefined()
4444
})
45+
46+
it('returns undefined for whitespace-only content', async () => {
47+
const meta = document.createElement('meta')
48+
meta.name = 'csp-nonce'
49+
meta.content = ' '
50+
document.head.appendChild(meta)
51+
52+
const { getCspNonce } = await import('@/lib/csp')
53+
expect(getCspNonce()).toBeUndefined()
54+
})
55+
56+
it('caches absent result and does not re-query DOM', async () => {
57+
const spy = vi.spyOn(document, 'querySelector')
58+
59+
const { getCspNonce } = await import('@/lib/csp')
60+
expect(getCspNonce()).toBeUndefined()
61+
expect(getCspNonce()).toBeUndefined()
62+
63+
// querySelector called once during import-time init + once for first call
64+
// Second call should hit cache, not query DOM again
65+
const cspCalls = spy.mock.calls.filter(
66+
([sel]) => sel === 'meta[name="csp-nonce"]',
67+
)
68+
expect(cspCalls).toHaveLength(1)
69+
70+
spy.mockRestore()
71+
})
4572
})

web/src/lib/csp.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55
* infrastructure (e.g. nginx `sub_filter`). If no meta tag is present
66
* (local dev, environments without CSP nonce injection), returns `undefined`.
77
*
8-
* The value is read once and cached for the lifetime of the page.
8+
* The value is read once on first call and cached for the lifetime of the
9+
* page -- both present and absent results are cached.
910
*/
1011

11-
let cached: string | undefined
12+
const UNREAD: unique symbol = Symbol('unread')
13+
let cached: string | undefined | typeof UNREAD = UNREAD
1214

1315
export function getCspNonce(): string | undefined {
14-
if (cached !== undefined) return cached
16+
if (cached !== UNREAD) return cached as string | undefined
1517

1618
const meta = document.querySelector<HTMLMetaElement>(
1719
'meta[name="csp-nonce"]',
1820
)
19-
cached = meta?.content || undefined
21+
cached = meta?.content?.trim() || undefined
2022
return cached
2123
}

0 commit comments

Comments
 (0)