Skip to content

Commit d65915b

Browse files
authored
fix(astro): Avoid injecting meta tags into <head> inside attribute values (#21089)
`addMetaTagToHead` previously used string replacement matching on `<head>` strings. This however also matches when `<head>` is used in data attribute values, e.g. `data-code="<head>..."`, corrupting the markup. We now use a regex that skips over quoted strings, so only a real `<head>` tag gets meta tags injected. Closes: #21068
1 parent a9086ed commit d65915b

2 files changed

Lines changed: 58 additions & 2 deletions

File tree

packages/astro/src/server/middleware.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,15 @@ function addMetaTagToHead(htmlChunk: string, metaTagsStr: string): string {
279279
return htmlChunk;
280280
}
281281

282-
const content = `<head>${metaTagsStr}`;
283-
return htmlChunk.replace('<head>', content);
282+
// Skip quoted attribute values so we don't match <head> inside e.g. data-code="...<head>..."
283+
let replaced = false;
284+
return htmlChunk.replace(/"[^"]*"|'[^']*'|(<head>)/g, (match, headTag) => {
285+
if (headTag && !replaced) {
286+
replaced = true;
287+
return `<head>${metaTagsStr}`;
288+
}
289+
return match;
290+
});
284291
}
285292

286293
function getMetaTagsStr({

packages/astro/test/server/middleware.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,55 @@ describe('sentryMiddleware', () => {
428428
expect(resultFromNext).toBe(originalResponse);
429429
});
430430

431+
it('does not inject meta tags into <head> inside attribute values', async () => {
432+
const middleware = handleRequest();
433+
434+
const ctx = {
435+
...DYNAMIC_REQUEST_CONTEXT,
436+
};
437+
438+
const bodyWithHeadInAttr =
439+
'<head><meta name="something" content=""/></head><body><button data-code="&lt;html&gt;&lt;head&gt;&lt;/head&gt;"></button></body>';
440+
const next = vi.fn(() =>
441+
Promise.resolve(
442+
new Response(bodyWithHeadInAttr, {
443+
headers: new Headers({ 'content-type': 'text/html' }),
444+
}),
445+
),
446+
);
447+
448+
// @ts-expect-error, a partial ctx object is fine here
449+
const resultFromNext = await middleware(ctx, next);
450+
const html = await resultFromNext?.text();
451+
452+
expect(html).toContain('<meta name="sentry-route-name" content="%2Fusers"/>');
453+
expect(html).not.toContain('data-code="&lt;html&gt;&lt;head&gt;<meta name="sentry-route-name"');
454+
});
455+
456+
it('does not inject meta tags into <head> inside quoted attribute values in the same chunk', async () => {
457+
const middleware = handleRequest();
458+
459+
const ctx = {
460+
...DYNAMIC_REQUEST_CONTEXT,
461+
};
462+
463+
const htmlWithHeadInDataAttr = '<head></head><body><div data-content="<head>should not be modified"></div></body>';
464+
const next = vi.fn(() =>
465+
Promise.resolve(
466+
new Response(htmlWithHeadInDataAttr, {
467+
headers: new Headers({ 'content-type': 'text/html' }),
468+
}),
469+
),
470+
);
471+
472+
// @ts-expect-error, a partial ctx object is fine here
473+
const resultFromNext = await middleware(ctx, next);
474+
const html = await resultFromNext?.text();
475+
476+
expect(html).toContain('<meta name="sentry-route-name" content="%2Fusers"/>');
477+
expect(html).toContain('data-content="<head>should not be modified"');
478+
});
479+
431480
it("no-ops if there's no <head> tag in the response", async () => {
432481
const middleware = handleRequest();
433482

0 commit comments

Comments
 (0)