Skip to content

Commit

Permalink
Add end2end test for compatibility with dd-trace
Browse files Browse the repository at this point in the history
Fetch in node.js is powered by undici, node requires undici when fetch
is called the first time:

let fetchImpl;
// https://fetch.spec.whatwg.org/#fetch-method
ObjectDefineProperty(globalThis, 'fetch', {
  __proto__: null,
  configurable: true,
  enumerable: true,
  writable: true,
  value: function fetch(input, init = undefined) { // eslint-disable-line func-name-matching
    if (!fetchImpl) { // Implement lazy loading of undici module for fetch function
      const undiciModule = require('internal/deps/undici/undici');
      fetchImpl = undiciModule.fetch;
    }
    return fetchImpl(input, init);
  },
});

Only then will setGlobalDispatcher be available (albeit via a symbol
hack)

We need to wait until setGlobalDispatcher is properly exposed: nodejs/node#43187

We be able to call setGlobalDispatcher immediately, we call `fetch` with
an arguments and catch the error. We also need to wrap the fetch(<NO
ARGS>) call itself, even though there's `.catch(...)`. This is because
dd-trace throws an invalid URL error.
  • Loading branch information
hansott committed Nov 18, 2024
1 parent 6a0920c commit d55a1b7
Show file tree
Hide file tree
Showing 4 changed files with 600 additions and 2 deletions.
73 changes: 73 additions & 0 deletions end2end/tests/express-postgres.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,76 @@ t.test("it does not block in dry mode", (t) => {
server.kill();
});
});

t.test("it blocks in blocking mode (with dd-trace)", (t) => {
const server = spawn(
`node`,
["--preserve-symlinks", "--require", "dd-trace/init", pathToApp, "4002"],
{
env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCKING: "true" },
cwd: resolve(__dirname, "../../sample-apps/express-postgres"),
}
);

server.on("close", () => {
t.end();
});

server.on("error", (err) => {
t.fail(err.message);
});

let stdout = "";
server.stdout.on("data", (data) => {
stdout += data.toString();
});

let stderr = "";
server.stderr.on("data", (data) => {
stderr += data.toString();
});

// Wait for the server to start
timeout(2000)
.then(() => {
return Promise.all([
fetch(
`http://localhost:4002/?petname=${encodeURIComponent("Njuska'); DELETE FROM cats_2;-- H")}`,
{
signal: AbortSignal.timeout(5000),
}
),
fetch(`http://localhost:4002/string-concat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ petname: ["'", "1)", "(0,1)", "(1", "'"] }),
signal: AbortSignal.timeout(5000),
}),
fetch(
`http://localhost:4002/string-concat?petname='&petname=1)&petname=(0,1)&petname=(1&petname='`,
{
signal: AbortSignal.timeout(5000),
}
),
fetch("http://localhost:4002/?petname=Njuska", {
signal: AbortSignal.timeout(5000),
}),
]);
})
.then(
async ([sqlInjection, sqlInjection2, sqlInjection3, normalSearch]) => {
t.equal(sqlInjection.status, 500);
t.equal(sqlInjection2.status, 500);
t.equal(sqlInjection3.status, 500);
t.equal(normalSearch.status, 200);
t.match(stdout, /Starting agent/);
t.match(stderr, /Zen has blocked an SQL injection/);
}
)
.catch((error) => {
t.fail(error);
})
.finally(() => {
server.kill();
});
});
8 changes: 6 additions & 2 deletions library/sinks/Fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,12 @@ export class Fetch implements Wrapper {
if (typeof globalThis.fetch === "function") {
// Fetch is lazy loaded in Node.js
// By calling fetch() we ensure that the global dispatcher is available
// @ts-expect-error Type is not defined
globalThis.fetch().catch(() => {});
try {
// @ts-expect-error Type is not defined
globalThis.fetch().catch(() => {});
} catch (error) {
// Ignore errors
}

Check warning on line 140 in library/sinks/Fetch.ts

View check run for this annotation

Codecov / codecov/patch

library/sinks/Fetch.ts#L139-L140

Added lines #L139 - L140 were not covered by tests
}

hooks.addGlobal("fetch", {
Expand Down
Loading

0 comments on commit d55a1b7

Please sign in to comment.