Search code examples
javascriptcloudflarecloudflare-workers

Cloudflare worker strips headers


I'm trying to migrate my CORS checker app from heroku to cloudflare workers as it seems to be a perfect fit for my use case.

The heroku app uses axios to asks a website for an options call to see if there are any headers with x-frame-options. If so, I check if it's denied, and return this as a boolean value in the REST response.

This sounds really simple, but I'm having trouble making it work on cloudflare workers. Am I correct in assuming that cloudflare is stripping these headers and there is no way to get this working with cloudflare workers?

Here is the full worker code

// node_modules/itty-router/dist/itty-router.mjs
var e = ({ base: e2 = "", routes: r = [] } = {}) => ({ __proto__: new Proxy({}, { get: (a, o, t) => (a2, ...p) => r.push([o.toUpperCase(), RegExp(`^${(e2 + a2).replace(/(\/?)\*/g, "($1.*)?").replace(/(\/$)|((?<=\/)\/)/, "").replace(/(:(\w+)\+)/, "(?<$2>.*)").replace(/:(\w+)(\?)?(\.)?/g, "$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/, "\\.").replace(/\)\.\?\(([^\[]+)\[\^/g, "?)\\.?($1(?<=\\.)[^\\.")}/*$`), p]) && t }), routes: r, async handle(e3, ...a) {
  let o, t, p = new URL(e3.url), l = e3.query = {};
  for (let [e4, r2] of p.searchParams)
    l[e4] = void 0 === l[e4] ? r2 : [l[e4], r2].flat();
  for (let [l2, s, c] of r)
    if ((l2 === e3.method || "ALL" === l2) && (t = p.pathname.match(s))) {
      e3.params = t.groups || {};
      for (let r2 of c)
        if (void 0 !== (o = await r2(e3.proxy || e3, ...a)))
          return o;
    }
} });

// src/index.ts
var router = e();
var corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
  "Access-Control-Max-Age": "86400"
};
function jsonResponse(body) {
  return new Response(JSON.stringify(body), {
    status: 200,
    headers: {
      "Content-Type": "application/json",
      "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload"
    }
  });
}
function handleOptions(request) {
  let headers = request.headers;
  if (headers.get("Origin") !== null && headers.get("Access-Control-Request-Method") !== null && headers.get("Access-Control-Request-Headers") !== null) {
    let respHeaders = {
      ...corsHeaders,
      "Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers")
    };
    return new Response(null, {
      headers: respHeaders
    });
  } else {
    return new Response(null, {
      headers: {
        Allow: "GET, HEAD, POST, OPTIONS"
      }
    });
  }
}
router.options("/test-cors", async function(request) {
  return handleOptions(request);
});
router.post("/test-cors", async function(request) {
  const url = request.body.url;
  const externalRequest = new Request(url, { method: "head" });
  try {
    let response = await fetch(externalRequest);
    if (response.headers.get("x-frame-options") !== null) {
      const frameOptionsValue = response.headers.get("x-frame-options");
      if (frameOptionsValue === "SAMEORIGIN") {
        return jsonResponse({ canAccess: false });
      } else {
        return jsonResponse({ canAccess: true });
      }
    } else {
      return jsonResponse({ canAccess: true });
    }
  } catch (error) {
    console.error(error);
    if (error.code === "ENOTFOUND" || error.response.status === 404) {
      return jsonResponse({ canAccess: false, reason: "NOT_FOUND" });
    } else {
      return jsonResponse({ canAccess: false });
    }
  }
});
router.all("*", (request, args) => {
  return new Response("Not Found", {
    status: 404,
    headers: {
      "Content-Type": "text/html; charset=utf-8",
      "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload"
    }
  });
});
var src_default = {
  async fetch(request, env, ctx) {
    return router.handle(request);
  }
};
export {
  src_default as default
};
//# sourceMappingURL=index.js.map

Solution

  • So I've talked with the developers behind the feature and it turns out the website I was trying to test for the header had a different behavior set for the calling IP.

    Since the editor runs fetch from your browser the IP was different and was not triggering that effect.