nyk commited on
Commit
394621e
·
unverified ·
1 Parent(s): 8a143d4

fix: remove CSP nonce from style-src to unblock reagraph canvas (#425)

Browse files

Replace style-src nonce directive with unsafe-inline to support
reagraph's runtime <style> injection. Add style-src-elem and
style-src-attr directives for CSP Level 3 compliance. Extend
fitNodesInView retries from 2 to 4 for more reliable canvas sizing.

Closes #414
Supersedes #415

src/components/panels/memory-graph.tsx CHANGED
@@ -267,9 +267,11 @@ export function MemoryGraph() {
267
  useEffect(() => {
268
  if (!graphNodes.length) return
269
  // reagraph force layout needs time to settle before fitNodesInView works
270
- const t1 = setTimeout(() => graphRef.current?.fitNodesInView(), 800)
271
- const t2 = setTimeout(() => graphRef.current?.fitNodesInView(), 2000)
272
- return () => { clearTimeout(t1); clearTimeout(t2) }
 
 
273
  }, [graphNodes.length, selectedAgent])
274
 
275
  // Navigation helpers
 
267
  useEffect(() => {
268
  if (!graphNodes.length) return
269
  // reagraph force layout needs time to settle before fitNodesInView works
270
+ const t1 = setTimeout(() => graphRef.current?.fitNodesInView(undefined, { animated: false }), 800)
271
+ const t2 = setTimeout(() => graphRef.current?.fitNodesInView(undefined, { animated: false }), 2500)
272
+ const t3 = setTimeout(() => graphRef.current?.fitNodesInView(undefined, { animated: false }), 5000)
273
+ const t4 = setTimeout(() => graphRef.current?.fitNodesInView(undefined, { animated: false }), 8000)
274
+ return () => { clearTimeout(t1); clearTimeout(t2); clearTimeout(t3); clearTimeout(t4) }
275
  }, [graphNodes.length, selectedAgent])
276
 
277
  // Navigation helpers
src/lib/__tests__/csp.test.ts CHANGED
@@ -6,7 +6,9 @@ describe('buildMissionControlCsp', () => {
6
  const csp = buildMissionControlCsp({ nonce: 'nonce-123', googleEnabled: false })
7
 
8
  expect(csp).toContain(`script-src 'self' 'nonce-nonce-123' 'strict-dynamic'`)
9
- expect(csp).toContain(`style-src 'self' 'nonce-nonce-123'`)
 
 
10
  })
11
  })
12
 
@@ -19,6 +21,6 @@ describe('buildNonceRequestHeaders', () => {
19
  })
20
 
21
  expect(headers.get('x-nonce')).toBe('nonce-123')
22
- expect(headers.get('Content-Security-Policy')).toContain(`'nonce-nonce-123'`)
23
  })
24
  })
 
6
  const csp = buildMissionControlCsp({ nonce: 'nonce-123', googleEnabled: false })
7
 
8
  expect(csp).toContain(`script-src 'self' 'nonce-nonce-123' 'strict-dynamic'`)
9
+ expect(csp).toContain("style-src 'self' 'unsafe-inline'")
10
+ expect(csp).toContain("style-src-elem 'self' 'unsafe-inline'")
11
+ expect(csp).toContain("style-src-attr 'unsafe-inline'")
12
  })
13
  })
14
 
 
21
  })
22
 
23
  expect(headers.get('x-nonce')).toBe('nonce-123')
24
+ expect(headers.get('Content-Security-Policy')).toContain("style-src 'self' 'unsafe-inline'")
25
  })
26
  })
src/lib/csp.ts CHANGED
@@ -7,7 +7,9 @@ export function buildMissionControlCsp(input: { nonce: string; googleEnabled: bo
7
  `object-src 'none'`,
8
  `frame-ancestors 'none'`,
9
  `script-src 'self' 'nonce-${nonce}' 'strict-dynamic' blob:${googleEnabled ? ' https://accounts.google.com' : ''}`,
10
- `style-src 'self' 'nonce-${nonce}'`,
 
 
11
  `connect-src 'self' ws: wss: http://127.0.0.1:* http://localhost:* https://cdn.jsdelivr.net`,
12
  `img-src 'self' data: blob:${googleEnabled ? ' https://*.googleusercontent.com https://lh3.googleusercontent.com' : ''}`,
13
  `font-src 'self' data:`,
 
7
  `object-src 'none'`,
8
  `frame-ancestors 'none'`,
9
  `script-src 'self' 'nonce-${nonce}' 'strict-dynamic' blob:${googleEnabled ? ' https://accounts.google.com' : ''}`,
10
+ `style-src 'self' 'unsafe-inline'`,
11
+ `style-src-elem 'self' 'unsafe-inline'`,
12
+ `style-src-attr 'unsafe-inline'`,
13
  `connect-src 'self' ws: wss: http://127.0.0.1:* http://localhost:* https://cdn.jsdelivr.net`,
14
  `img-src 'self' data: blob:${googleEnabled ? ' https://*.googleusercontent.com https://lh3.googleusercontent.com' : ''}`,
15
  `font-src 'self' data:`,