getsentry / sentry-docs

Sentry's documentation (and tools to build it)
https://docs.sentry.io
Other
323 stars 1.38k forks source link

For Sentry Tunnel (/tunnel endpoint) implementation example, binary data should not be parsed as string(=UTF8), for like Sentry Replay #10348

Open LumaKernel opened 3 weeks ago

LumaKernel commented 3 weeks ago

Core or SDK?

Core Sentry product

Which part? Which one?

Sentry Tunnel

Description

In the doc, the following example can be found. page: https://docs.sentry.io/platforms/javascript/troubleshooting/ Section Using the tunnel Option

const SENTRY_HOST = "oXXXXXX.ingest.sentry.io";
const SENTRY_PROJECT_IDS = ["123456"];

export const postAction = async ({ request }) => {
  try {
    const envelope = await request.text();
    const piece = envelope.split("\n")[0];
    const header = JSON.parse(piece);
    const dsn = new URL(header["dsn"]);
    const project_id = dsn.pathname?.replace("/", "");

    if (dsn.hostname !== SENTRY_HOST) {
      throw new Error(`Invalid sentry hostname: ${dsn.hostname}`);
    }

    if (!project_id || !SENTRY_PROJECT_IDS.includes(project_id)) {
      throw new Error(`Invalid sentry project id: ${project_id}`);
    }

    const upstream_sentry_url = `https://${SENTRY_HOST}/api/${project_id}/envelope/`;
    await fetch(upstream_sentry_url, { method: "POST", body: envelope });

    return json({}, { status: 200 });
  } catch (e) {
    console.error("error tunneling to sentry", e);
    return json({ error: "error tunneling to sentry" }, { status: 500 });
  }
};

See these points:

const envelope = await request.text();
// ...
await fetch(upstream_sentry_url, { method: "POST", body: envelope });

The body is parsed as UTF8, and we can understand the envelope has JSON metadata on first bytes till byte 0A. But if the envelope has binary that is not compatible with UTF8, the data would be broken, and Sentry envelope endpoint fails. Specifically, if the vendor has the Sentry Replay endpoint, it always goes to 500 error.

Suggested Solution

I can guess the example is written for express users, so it seeems we can use .raw() instead of .text(). API doc: https://expressjs.com/en/4x/api.html#express.raw NOTE: Not sure. We don't test as it is. We're using another web framework.

const SENTRY_HOST = "oXXXXXX.ingest.sentry.io";
const SENTRY_PROJECT_IDS = ["123456"];

export const postAction = async ({ request }) => {
  try {
    const envelope = await request.raw();
    const piece = envelope.toString().split("\n")[0];
    const header = JSON.parse(piece);
    const dsn = new URL(header["dsn"]);
    const project_id = dsn.pathname?.replace("/", "");

    if (dsn.hostname !== SENTRY_HOST) {
      throw new Error(`Invalid sentry hostname: ${dsn.hostname}`);
    }

    if (!project_id || !SENTRY_PROJECT_IDS.includes(project_id)) {
      throw new Error(`Invalid sentry project id: ${project_id}`);
    }

    const upstream_sentry_url = `https://${SENTRY_HOST}/api/${project_id}/envelope/`;
    await fetch(upstream_sentry_url, { method: "POST", body: envelope });

    return json({}, { status: 200 });
  } catch (e) {
    console.error("error tunneling to sentry", e);
    return json({ error: "error tunneling to sentry" }, { status: 500 });
  }
};

Off-Topic: I prefer following to get JSON segment. (But this doesn't affect the behaviour. No worry)

-    const piece = envelope.toString().split("\n")[0];
+    const piece = envelope.slice(0, envelop.indexOf(/* CR */ 0x0a)).toString();
getsantry[bot] commented 3 weeks ago

Assigning to @getsentry/support for routing ⏲️

LumaKernel commented 3 weeks ago

Credits: I found this while working in https://github.com/Optimind-llc and found this problem with the team member @lbk-yuuki-nakamura.