Skip to content

[@sentry/astro] injectMetaTagsInResponse uses broken Response clone pattern; drops security headers in Cloudflare Workers runtime #21008

@gmcluckie1

Description

@gmcluckie1

Description

@sentry/astro auto-registers an Astro middleware that wraps every dynamic SSR response via injectMetaTagsInResponse(originalResponse, traceMetaTags) to inject <meta name="sentry-trace"> tags. The wrap uses the well-known broken new Response(body, originalResponse) clone pattern, which silently drops X-Frame-Options, Permissions-Policy, Cross-Origin-Opener-Policy, and (some observers report) Content-Security-Policy in the Cloudflare Workers runtime when copying from a Worker-originating response.

Source reference

@sentry/astro v10.49 (and current 10.53):

build/esm/server/middleware.jsinjectMetaTagsInResponse:

function injectMetaTagsInResponse(originalResponse, metaTagsStr) {
  try {
    // ...stream transform setup...
    return new Response(newResponseStream, originalResponse);  // <-- broken pattern
  } catch (e) {
    sendErrorToSentry(e);
    throw e;
  }
}

This function is called from handleStaticRoute, enhanceHttpServerSpan, and instrumentRequestStartHttpServerSpan — every Astro SSR route in environments where @sentry/astro registered itself via addMiddleware() in astro:config:setup.

Reproduction conditions

  • Astro output: "server" with @astrojs/cloudflare v13 adapter
  • @sentry/astro v10.40+ in integrations: [sentry()]
  • PUBLIC_SENTRY_DSN env var set at build time so the integration is active
  • A downstream layer (e.g. a Pages Functions middleware, or in our case apps/<app>/functions/_middleware.ts) that stamps response headers expecting them to survive

Empirical evidence

In our setup (mcluckie-marketing-astro / Riney Electric) the applySecurityHeaders and runtime-CSP middleware in apps/<app>/functions/_middleware.ts stamp the BASE security floor onto every response after context.next() returns. The four headers above are correctly set when Sentry is NOT enabled (build without PUBLIC_SENTRY_DSN) but are silently dropped when Sentry IS enabled.

Suggested fix

Replace the broken clone pattern with the explicit init form:

function injectMetaTagsInResponse(originalResponse, metaTagsStr) {
  try {
    // ...stream transform setup...
    return new Response(newResponseStream, {
      status: originalResponse.status,
      statusText: originalResponse.statusText,
      headers: new Headers(originalResponse.headers),
    });
  } catch (e) {
    sendErrorToSentry(e);
    throw e;
  }
}

This is the same pattern Sentry has already adopted elsewhere — e.g., @sentry/cloudflare's wrapRequestHandler (build/esm/request.js) uses { status, statusText, headers: res.headers }. Applying it here aligns @sentry/astro with that prior art and avoids the silent-drop in Workers runtimes.

Workaround currently deployed

We've added a defense-in-depth final-pass that materializes upstream headers via .entries() iteration (bypassing any guarded-Headers semantics) and re-stamps the security floor after Sentry's wrap:

export function assertSecurityFloor(response: Response, headers = NON_CSP_SECURITY_FLOOR): Response {
  const entries: [string, string][] = [];
  for (const [name, value] of response.headers.entries()) {
    entries.push([name, value]);
  }
  const next = new Headers(entries);
  for (const [name, value] of Object.entries(headers)) {
    next.set(name, value);
  }
  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: next,
  });
}

This is paranoid and works, but should not be necessary if injectMetaTagsInResponse is fixed upstream.

Severity

Medium. Affects security headers (defense-in-depth) but not core functionality. Easy to miss in CI because Node's undici doesn't drop headers across new Response(body, response) — only the workerd runtime does.

Metadata

Metadata

Assignees

No fields configured for issues without a type.

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions