Skip to content

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

Closed

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.js — injectMetaTagsInResponse:

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.

Activity

  1. linear-code commented on May 19, 2026

    @linear-code
  2. moved this to Waiting for: Product Owner in GitHub Issues with 👀 3on May 19, 2026
  3. andreiborza commented on May 20, 2026

    @andreiborza
    Member

    Hi @gmcluckie1, thanks for writing in with a detailed investigation.

    We'll fix this!

  4. self-assigned this
    on May 20, 2026
  5. moved this from Waiting for: Product Owner to No status in GitHub Issues with 👀 3on May 20, 2026
  6. added a commit that references this issue on May 20, 2026
    00fcc87
  7. github-actions commented on May 26, 2026

    @github-actions
    Contributor

    A PR closing this issue has just been released 🚀

    This issue was referenced by PR #21021, which was included in the 10.54.0 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions