mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 04:07:05 +00:00
🔣 fix: Escape SPA Language Attribute (#15248)
This commit is contained in:
parent
6d8b1cb013
commit
f34a49007d
6 changed files with 34 additions and 4 deletions
|
|
@ -195,6 +195,14 @@ describe('Content Security Policy', () => {
|
|||
expect(response.headers['content-security-policy']).toContain("script-src 'nonce-");
|
||||
});
|
||||
|
||||
it('keeps replacement patterns in the language cookie as literal attribute text', async () => {
|
||||
const response = await request(app).get('/').set('Cookie', 'lang=$&');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.text).toContain('<html lang="$&">');
|
||||
expect(response.text).not.toContain('<html lang="lang="en-US"">');
|
||||
});
|
||||
|
||||
it('carries deployment-specific sources and the clickjacking default', async () => {
|
||||
const csp = (await request(app).get('/')).headers['content-security-policy'];
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const {
|
|||
applyCspNonce,
|
||||
createCspPolicy,
|
||||
shellCacheHeaders,
|
||||
escapeHtmlAttribute,
|
||||
ErrorController,
|
||||
QUERY_DEVTOOLS_HEADER,
|
||||
createSecurityHeaders,
|
||||
|
|
@ -434,8 +435,8 @@ if (cluster.isMaster) {
|
|||
res.vary(QUERY_DEVTOOLS_HEADER);
|
||||
|
||||
const lang = req.cookies.lang || req.headers['accept-language']?.split(',')[0] || 'en-US';
|
||||
const saneLang = lang.replace(/"/g, '"');
|
||||
let updatedIndexHtml = indexHTML.replace(/lang="en-US"/g, `lang="${saneLang}"`);
|
||||
const saneLang = escapeHtmlAttribute(lang);
|
||||
let updatedIndexHtml = indexHTML.replace(/lang="en-US"/g, () => `lang="${saneLang}"`);
|
||||
updatedIndexHtml = maybeInjectQueryDevtoolsBootstrap(updatedIndexHtml, req);
|
||||
|
||||
/* Nonce last: every injected script above must be stamped too. */
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const {
|
|||
applyCspNonce,
|
||||
createCspPolicy,
|
||||
shellCacheHeaders,
|
||||
escapeHtmlAttribute,
|
||||
ErrorController,
|
||||
memoryDiagnostics,
|
||||
createSecurityHeaders,
|
||||
|
|
@ -252,8 +253,8 @@ const startServer = async () => {
|
|||
res.vary(QUERY_DEVTOOLS_HEADER);
|
||||
|
||||
const lang = req.cookies.lang || req.headers['accept-language']?.split(',')[0] || 'en-US';
|
||||
const saneLang = lang.replace(/"/g, '"');
|
||||
let updatedIndexHtml = indexHTML.replace(/lang="en-US"/g, `lang="${saneLang}"`);
|
||||
const saneLang = escapeHtmlAttribute(lang);
|
||||
let updatedIndexHtml = indexHTML.replace(/lang="en-US"/g, () => `lang="${saneLang}"`);
|
||||
updatedIndexHtml = maybeInjectQueryDevtoolsBootstrap(updatedIndexHtml, req);
|
||||
|
||||
/* Nonce last: every injected script above must be stamped too. */
|
||||
|
|
|
|||
7
packages/api/src/security/html.spec.ts
Normal file
7
packages/api/src/security/html.spec.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { escapeHtmlAttribute } from './html';
|
||||
|
||||
describe('escapeHtmlAttribute', () => {
|
||||
it('escapes all characters that can alter a quoted attribute', () => {
|
||||
expect(escapeHtmlAttribute(`$&<>'"`)).toBe('$&<>'"');
|
||||
});
|
||||
});
|
||||
12
packages/api/src/security/html.ts
Normal file
12
packages/api/src/security/html.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const HTML_ATTRIBUTE_ENTITIES: Readonly<Record<string, string>> = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
};
|
||||
|
||||
/** Escapes text before interpolating it into a quoted HTML attribute. */
|
||||
export function escapeHtmlAttribute(value: string): string {
|
||||
return value.replace(/[&<>"']/g, (character) => HTML_ATTRIBUTE_ENTITIES[character]);
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './env';
|
||||
export * from './headers';
|
||||
export * from './csp';
|
||||
export * from './html';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue