Skip to content

Commit 79c0d57

Browse files
justin808claude
andcommitted
Add nonce sanitization to prevent XSS attacks
Security improvements: - Sanitize nonce values to prevent attribute injection attacks - Only allow base64-safe characters: alphanumeric, +, /, =, -, _ - Add test to verify malicious nonce values are sanitized - Document the security measure in code comments Even though Rails content_security_policy_nonce() returns safe values, this adds defense-in-depth by sanitizing at the JavaScript layer. All tests passing (101 passed). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 6cddcf4 commit 79c0d57

File tree

2 files changed

+20
-1
lines changed

2 files changed

+20
-1
lines changed

packages/react-on-rails/src/RenderUtils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ export function wrapInScriptTags(scriptId: string, scriptBody: string, nonce?: s
44
return '';
55
}
66

7-
const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
7+
// Sanitize nonce to prevent attribute injection attacks
8+
// CSP nonces should be base64 strings, so only allow alphanumeric, +, /, =, -, and _
9+
const sanitizedNonce = nonce?.replace(/[^a-zA-Z0-9+/=_-]/g, '');
10+
const nonceAttr = sanitizedNonce ? ` nonce="${sanitizedNonce}"` : '';
811

912
return `
1013
<script id="${scriptId}"${nonceAttr}>

packages/react-on-rails/tests/buildConsoleReplay.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,20 @@ console.warn.apply(console, ["other message","{\\"c\\":3,\\"d\\":4}"]);
107107
expect(actual).toContain('console.log.apply(console, ["message 1"]);');
108108
expect(actual).toContain('console.error.apply(console, ["message 2"]);');
109109
});
110+
111+
it('buildConsoleReplay sanitizes nonce to prevent XSS', () => {
112+
console.history = [{ arguments: ['test'], level: 'log' }];
113+
// Attempt attribute injection attack
114+
const maliciousNonce = 'abc123" onload="alert(1)';
115+
const actual = buildConsoleReplay(undefined, 0, maliciousNonce);
116+
117+
// Should strip dangerous characters (quotes, parens, spaces)
118+
// = is kept as it's valid in base64, but the quotes are stripped making it harmless
119+
expect(actual).toContain('nonce="abc123onload=alert1"');
120+
// Should NOT contain quotes that would close the attribute
121+
expect(actual).not.toContain('nonce="abc123"');
122+
expect(actual).not.toContain('alert(1)');
123+
// Verify the dangerous parts (quotes and parens) are removed
124+
expect(actual).not.toMatch(/nonce="[^"]*"[^>]*onload=/);
125+
});
110126
});

0 commit comments

Comments
 (0)