From 60266a8799dbca3db2eb118f55224496ac6354ad Mon Sep 17 00:00:00 2001 From: William Conti Date: Tue, 12 Nov 2024 12:28:58 -0500 Subject: [PATCH 01/31] update express to not wrap router methods if version is v5 --- packages/datadog-instrumentations/src/express.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/datadog-instrumentations/src/express.js b/packages/datadog-instrumentations/src/express.js index 74e159fb042..22332caf32c 100644 --- a/packages/datadog-instrumentations/src/express.js +++ b/packages/datadog-instrumentations/src/express.js @@ -57,7 +57,7 @@ function wrapResponseRender (render) { } } -addHook({ name: 'express', versions: ['>=4'] }, express => { +addHook({ name: 'express', versions: ['>=4 <5.0.0'] }, express => { shimmer.wrap(express.application, 'handle', wrapHandle) shimmer.wrap(express.Router, 'use', wrapRouterMethod) shimmer.wrap(express.Router, 'route', wrapRouterMethod) @@ -69,6 +69,16 @@ addHook({ name: 'express', versions: ['>=4'] }, express => { return express }) +addHook({ name: 'express', versions: ['>=5.0.0'] }, express => { + shimmer.wrap(express.application, 'handle', wrapHandle) + + shimmer.wrap(express.response, 'json', wrapResponseJson) + shimmer.wrap(express.response, 'jsonp', wrapResponseJson) + shimmer.wrap(express.response, 'render', wrapResponseRender) + + return express +}) + const queryParserReadCh = channel('datadog:query:read:finish') function publishQueryParsedAndNext (req, res, next) { @@ -129,7 +139,7 @@ addHook({ name: 'express', versions: ['>=4.0.0 <4.3.0'] }, express => { return express }) -addHook({ name: 'express', versions: ['>=4.3.0'] }, express => { +addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'] }, express => { shimmer.wrap(express.Router, 'process_params', wrapProcessParamsMethod(2)) return express }) From 0bbc7df38f1d24a4d0f08a1811c35e99aeb6d1ed Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Nov 2024 11:53:38 -0500 Subject: [PATCH 02/31] update express v5 plugin, change failing tests to only run for 3 ? 1 : 0] + wrapParams(req) const next = arguments[lastIndex] if (typeof next === 'function') { @@ -167,12 +168,37 @@ function createWrapRouterMethod (name) { return wrapMethod } +const processParamsStartCh = channel('datadog:express:process_params:start') +function wrapParams (req) { + let params + Object.defineProperty(req, 'params', { + get () { return params }, + set (val) { + params = val + if (val && processParamsStartCh.hasSubscribers) { + const abortController = new AbortController() + + processParamsStartCh.publish({ + req, + res: req?.res, + abortController, + params: req?.params + }) + } + } + }) +} + const wrapRouterMethod = createWrapRouterMethod('router') +const wrapExpressRouterMethod = createWrapRouterMethod('express') addHook({ name: 'router', versions: ['>=1'] }, Router => { shimmer.wrap(Router.prototype, 'use', wrapRouterMethod) shimmer.wrap(Router.prototype, 'route', wrapRouterMethod) + shimmer.wrap(Router.prototype, 'use', wrapExpressRouterMethod) + shimmer.wrap(Router.prototype, 'route', wrapExpressRouterMethod) + return Router }) diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 55a608f4adf..92e378df933 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -5,10 +5,11 @@ const axios = require('axios') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') const plugin = require('../src') +const semver = require('semver') const sort = spans => spans.sort((a, b) => a.start.toString() >= b.start.toString() ? 1 : -1) -describe('Plugin', () => { +describe('Plugin', function () { let tracer let express let appListener @@ -197,146 +198,6 @@ describe('Plugin', () => { }) }) - it('should do automatic instrumentation on middleware', done => { - const app = express() - const router = express.Router() - - router.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - app.use(function named (req, res, next) { next() }) - app.use('/app', router) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /app/user/:id') - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[1]).to.have.property('resource', 'query') - expect(spans[1]).to.have.property('name', 'express.middleware') - expect(spans[1].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[1].meta).to.have.property('component', 'express') - expect(spans[2]).to.have.property('resource', 'expressInit') - expect(spans[2]).to.have.property('name', 'express.middleware') - expect(spans[2].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[2].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('resource', 'named') - expect(spans[3]).to.have.property('name', 'express.middleware') - expect(spans[3].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[3].meta).to.have.property('component', 'express') - expect(spans[4]).to.have.property('resource', 'router') - expect(spans[4]).to.have.property('name', 'express.middleware') - expect(spans[4].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[4].meta).to.have.property('component', 'express') - expect(spans[5].resource).to.match(/^bound\s.*$/) - expect(spans[5]).to.have.property('name', 'express.middleware') - expect(spans[5].parent_id.toString()).to.equal(spans[4].span_id.toString()) - expect(spans[5].meta).to.have.property('component', 'express') - expect(spans[6]).to.have.property('resource', '') - expect(spans[6]).to.have.property('name', 'express.middleware') - expect(spans[6].parent_id.toString()).to.equal(spans[5].span_id.toString()) - expect(spans[6].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/app/user/1`) - .catch(done) - }) - }) - - it('should do automatic instrumentation on middleware that break the async context', done => { - let next - - const app = express() - const interval = setInterval(() => { - if (next) { - next() - clearInterval(interval) - } - }) - - app.use(function breaking (req, res, _next) { - next = _next - }) - app.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /user/:id') - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('resource', 'breaking') - expect(spans[3]).to.have.property('name', 'express.middleware') - expect(spans[3].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user/1`) - .catch(done) - }) - }) - - it('should handle errors on middleware that break the async context', done => { - let next - - const error = new Error('boom') - const app = express() - const interval = setInterval(() => { - if (next) { - next() - clearInterval(interval) - } - }) - - app.use(function breaking (req, res, _next) { - next = _next - }) - app.use(() => { throw error }) - // eslint-disable-next-line n/handle-callback-err - app.use((err, req, res, next) => next()) - app.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[4]).to.have.property('name', 'express.middleware') - expect(spans[4].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[4].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user/1`) - .catch(done) - }) - }) - it('should surround matchers based on regular expressions', done => { const app = express() const router = express.Router() @@ -393,40 +254,6 @@ describe('Plugin', () => { }) }) - it('should only keep the last matching path of a middleware stack', done => { - const app = express() - const router = express.Router() - - router.use('/', (req, res, next) => next()) - router.use('*', (req, res, next) => next()) - router.use('/bar', (req, res, next) => next()) - router.use('/bar', (req, res, next) => { - res.status(200).send() - }) - - app.use('/', (req, res, next) => next()) - app.use('*', (req, res, next) => next()) - app.use('/foo/bar', (req, res, next) => next()) - app.use('/foo', router) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /foo/bar') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/foo/bar`) - .catch(done) - }) - }) - it('should support asynchronous routers', done => { const app = express() const router = express.Router() @@ -1124,121 +951,6 @@ describe('Plugin', () => { }) }) - it('should handle middleware errors', done => { - const app = express() - const error = new Error('boom') - - app.use((req, res, next) => next(error)) - // eslint-disable-next-line n/handle-callback-err - app.use((error, req, res, next) => res.status(500).send()) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('error', 1) - expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('error', 1) - expect(spans[3].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[3].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[3].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[3].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user`, { - validateStatus: status => status === 500 - }) - .catch(done) - }) - }) - - it('should handle middleware exceptions', done => { - const app = express() - const error = new Error('boom') - - app.use((req, res) => { throw error }) - // eslint-disable-next-line n/handle-callback-err - app.use((error, req, res, next) => res.status(500).send()) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('error', 1) - expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('error', 1) - expect(spans[3].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[3].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[3].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user`, { - validateStatus: status => status === 500 - }) - .catch(done) - }) - }) - - it('should support capturing groups in routes', done => { - const app = express() - - app.get('/:path(*)', (req, res) => { - res.status(200).send() - }) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /:path(*)') - expect(spans[0].meta).to.have.property('http.url', `http://localhost:${port}/user`) - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user`) - .catch(done) - }) - }) - - it('should keep the properties untouched on nested router handlers', () => { - const router = express.Router() - const childRouter = express.Router() - - childRouter.get('/:id', (req, res) => { - res.status(200).send() - }) - - router.use('/users', childRouter) - - const layer = router.stack.find(layer => layer.regexp.test('/users')) - - expect(layer.handle).to.have.ownProperty('stack') - }) - it('should keep user stores untouched', done => { const app = express() const storage = new AsyncLocalStorage() @@ -1372,6 +1084,298 @@ describe('Plugin', () => { }) }) }) + + if (semver.intersects(version, '<5.0.0')) { + it('should do automatic instrumentation on middleware', done => { + const app = express() + const router = express.Router() + + router.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + app.use(function named (req, res, next) { next() }) + app.use('/app', router) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /app/user/:id') + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[1]).to.have.property('resource', 'query') + expect(spans[1]).to.have.property('name', 'express.middleware') + expect(spans[1].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[1].meta).to.have.property('component', 'express') + expect(spans[2]).to.have.property('resource', 'expressInit') + expect(spans[2]).to.have.property('name', 'express.middleware') + expect(spans[2].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[2].meta).to.have.property('component', 'express') + expect(spans[3]).to.have.property('resource', 'named') + expect(spans[3]).to.have.property('name', 'express.middleware') + expect(spans[3].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[3].meta).to.have.property('component', 'express') + expect(spans[4]).to.have.property('resource', 'router') + expect(spans[4]).to.have.property('name', 'express.middleware') + expect(spans[4].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[4].meta).to.have.property('component', 'express') + expect(spans[5].resource).to.match(/^bound\s.*$/) + expect(spans[5]).to.have.property('name', 'express.middleware') + expect(spans[5].parent_id.toString()).to.equal(spans[4].span_id.toString()) + expect(spans[5].meta).to.have.property('component', 'express') + expect(spans[6]).to.have.property('resource', '') + expect(spans[6]).to.have.property('name', 'express.middleware') + expect(spans[6].parent_id.toString()).to.equal(spans[5].span_id.toString()) + expect(spans[6].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/app/user/1`) + .catch(done) + }) + }) + + it('should do automatic instrumentation on middleware that break the async context', done => { + let next + + const app = express() + const interval = setInterval(() => { + if (next) { + next() + clearInterval(interval) + } + }) + + app.use(function breaking (req, res, _next) { + next = _next + }) + app.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /user/:id') + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[3]).to.have.property('resource', 'breaking') + expect(spans[3]).to.have.property('name', 'express.middleware') + expect(spans[3].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user/1`) + .catch(done) + }) + }) + + it('should handle errors on middleware that break the async context', done => { + let next + + const error = new Error('boom') + const app = express() + const interval = setInterval(() => { + if (next) { + next() + clearInterval(interval) + } + }) + + app.use(function breaking (req, res, _next) { + next = _next + }) + app.use(() => { throw error }) + // eslint-disable-next-line n/handle-callback-err + app.use((err, req, res, next) => next()) + app.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[4]).to.have.property('name', 'express.middleware') + expect(spans[4].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[4].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user/1`) + .catch(done) + }) + }) + + // express v5 doesn't support regex paths + it('should only keep the last matching path of a middleware stack', done => { + const app = express() + const router = express.Router() + + router.use('/', (req, res, next) => next()) + router.use('*', (req, res, next) => next()) + router.use('/bar', (req, res, next) => next()) + router.use('/bar', (req, res, next) => { + res.status(200).send() + }) + + app.use('/', (req, res, next) => next()) + app.use('*', (req, res, next) => next()) + app.use('/foo/bar', (req, res, next) => next()) + app.use('/foo', router) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /foo/bar') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/foo/bar`) + .catch(done) + }) + }) + + it('should handle middleware errors', done => { + const app = express() + const error = new Error('boom') + + app.use((req, res, next) => next(error)) + // eslint-disable-next-line n/handle-callback-err + app.use((error, req, res, next) => res.status(500).send()) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('error', 1) + expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[3]).to.have.property('error', 1) + expect(spans[3].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[3].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[3].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[3].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`, { + validateStatus: status => status === 500 + }) + .catch(done) + }) + }) + + it('should handle middleware exceptions', done => { + const app = express() + const error = new Error('boom') + + app.use((req, res) => { throw error }) + // eslint-disable-next-line n/handle-callback-err + app.use((error, req, res, next) => res.status(500).send()) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('error', 1) + expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[3]).to.have.property('error', 1) + expect(spans[3].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[3].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[3].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`, { + validateStatus: status => status === 500 + }) + .catch(done) + }) + }) + + it('should support capturing groups in routes', done => { + const app = express() + + app.get('/:path(*)', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /:path(*)') + expect(spans[0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`) + .catch(done) + }) + }) + + it('should keep the properties untouched on nested router handlers', () => { + const router = express.Router() + const childRouter = express.Router() + + childRouter.get('/:id', (req, res) => { + res.status(200).send() + }) + + router.use('/users', childRouter) + + const layer = router.stack.find(layer => layer.regexp.test('/users')) + + expect(layer.handle).to.have.ownProperty('stack') + }) + } }) describe('with configuration', () => { diff --git a/packages/datadog-plugin-express/test/integration-test/client.spec.js b/packages/datadog-plugin-express/test/integration-test/client.spec.js index a5c08d60ecb..054dcb81c58 100644 --- a/packages/datadog-plugin-express/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-express/test/integration-test/client.spec.js @@ -7,6 +7,7 @@ const { spawnPluginIntegrationTestProc } = require('../../../../integration-tests/helpers') const { assert } = require('chai') +const semver = require('semver') describe('esm', () => { let agent @@ -34,18 +35,36 @@ describe('esm', () => { await agent.stop() }) - it('is instrumented', async () => { - proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) - - return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { - assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) - assert.isArray(payload) - assert.strictEqual(payload.length, 1) - assert.isArray(payload[0]) - assert.strictEqual(payload[0].length, 4) - assert.propertyVal(payload[0][0], 'name', 'express.request') - assert.propertyVal(payload[0][1], 'name', 'express.middleware') - }) - }).timeout(50000) + // express less than <5.0 uses their own router, which creates more middleware spans than the router + // that is used for v5+ + if (semver.intersects(version, '<5.0.0')) { + it('is instrumented', async () => { + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + + return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { + assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) + assert.isArray(payload) + assert.strictEqual(payload.length, 1) + assert.isArray(payload[0]) + assert.strictEqual(payload[0].length, 4) + assert.propertyVal(payload[0][0], 'name', 'express.request') + assert.propertyVal(payload[0][1], 'name', 'express.middleware') + }) + }).timeout(50000) + } else { + it('is instrumented', async () => { + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + + return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { + assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) + assert.isArray(payload) + assert.strictEqual(payload.length, 1) + assert.isArray(payload[0]) + assert.strictEqual(payload[0].length, 3) + assert.propertyVal(payload[0][0], 'name', 'express.request') + assert.propertyVal(payload[0][1], 'name', 'express.middleware') + }) + }).timeout(50000) + } }) }) From 21513a9faf10e0352327b0c5cbb4cd03a17d3d91 Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Nov 2024 12:44:11 -0500 Subject: [PATCH 03/31] change tests around --- .../datadog-plugin-express/test/index.spec.js | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 92e378df933..60c421eff1e 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -1375,6 +1375,247 @@ describe('Plugin', function () { expect(layer.handle).to.have.ownProperty('stack') }) + } else { + it('should do automatic instrumentation on middleware', done => { + const app = express() + const router = express.Router() + + router.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + app.use(function named (req, res, next) { next() }) + app.use('/app', router) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /app/user/:id') + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[1]).to.have.property('resource', 'named') + expect(spans[1]).to.have.property('name', 'express.middleware') + expect(spans[1].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[1].meta).to.have.property('component', 'express') + expect(spans[2]).to.have.property('resource', 'router') + expect(spans[2]).to.have.property('name', 'express.middleware') + expect(spans[2].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[2].meta).to.have.property('component', 'express') + expect(spans[3]).to.have.property('resource', 'handle') + expect(spans[3]).to.have.property('name', 'express.middleware') + expect(spans[3].meta).to.have.property('component', 'express') + expect(spans[4]).to.have.property('name', 'express.middleware') + expect(spans[4].parent_id.toString()).to.equal(spans[3].span_id.toString()) + expect(spans[4].meta).to.have.property('component', 'express') + expect(spans[4]).to.have.property('resource', '') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/app/user/1`) + .catch(done) + }) + }) + + it('should do automatic instrumentation on middleware that break the async context', done => { + let next + + const app = express() + const interval = setInterval(() => { + if (next) { + next() + clearInterval(interval) + } + }) + + app.use(function breaking (req, res, _next) { + next = _next + }) + app.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /user/:id') + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[1]).to.have.property('resource', 'breaking') + expect(spans[1]).to.have.property('name', 'express.middleware') + expect(spans[1].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user/1`) + .catch(done) + }) + }) + + it('should handle errors on middleware that break the async context', done => { + let next + + const error = new Error('boom') + const app = express() + const interval = setInterval(() => { + if (next) { + next() + clearInterval(interval) + } + }) + + app.use(function breaking (req, res, _next) { + next = _next + }) + app.use(() => { throw error }) + // eslint-disable-next-line n/handle-callback-err + app.use((err, req, res, next) => next()) + app.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[1]).to.have.property('name', 'express.middleware') + expect(spans[2]).to.have.property('name', 'express.middleware') + expect(spans[2].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[2].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user/1`) + .catch(done) + }) + }) + + // express v5 doesn't support regex paths unless theres a name after the regex ie: *foo + it('should only keep the last matching path of a middleware stack', done => { + const app = express() + const router = express.Router() + + router.use('/', (req, res, next) => next()) + router.use('*foo', (req, res, next) => next()) + router.use('/bar', (req, res, next) => next()) + router.use('/bar', (req, res, next) => { + res.status(200).send() + }) + + app.use('/', (req, res, next) => next()) + app.use('*foo', (req, res, next) => next()) + app.use('/foo/bar', (req, res, next) => next()) + app.use('/foo', router) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /foo/bar') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/foo/bar`) + .catch(done) + }) + }) + + it('should handle middleware errors', done => { + const app = express() + const error = new Error('boom') + + app.use((req, res, next) => next(error)) + // eslint-disable-next-line n/handle-callback-err + app.use((error, req, res, next) => res.status(500).send()) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('error', 1) + expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[1]).to.have.property('error', 1) + expect(spans[1].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[1].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[1].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[1].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`, { + validateStatus: status => status === 500 + }) + .catch(done) + }) + }) + + it('should handle middleware exceptions', done => { + const app = express() + const error = new Error('boom') + + app.use((req, res) => { throw error }) + // eslint-disable-next-line n/handle-callback-err + app.use((error, req, res, next) => res.status(500).send()) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('error', 1) + expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[1]).to.have.property('error', 1) + expect(spans[1].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[1].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[1].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`, { + validateStatus: status => status === 500 + }) + .catch(done) + }) + }) } }) From d488f598bb9ae297e837e0445ac7de08454f532d Mon Sep 17 00:00:00 2001 From: William Conti Date: Wed, 13 Nov 2024 12:48:39 -0500 Subject: [PATCH 04/31] dont wrap params for now --- packages/datadog-instrumentations/src/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 92a850f5588..c1134059c53 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -25,7 +25,7 @@ function createWrapRouterMethod (name) { const lastIndex = arguments.length - 1 const name = original._name || original.name const req = arguments[arguments.length > 3 ? 1 : 0] - wrapParams(req) + // wrapParams(req) const next = arguments[lastIndex] if (typeof next === 'function') { From 782752d8161513d1c0907d0a5d3d0d6a457fdfa3 Mon Sep 17 00:00:00 2001 From: ishabi Date: Mon, 18 Nov 2024 16:58:45 +0100 Subject: [PATCH 05/31] fix express 5 intrumentation --- .../datadog-instrumentations/src/express.js | 4 +- .../datadog-instrumentations/src/router.js | 54 ++++--- .../src/appsec/iast/taint-tracking/plugin.js | 10 ++ .../iast/taint-tracking/source-types.js | 1 + ...cker-fingerprinting.express.plugin.spec.js | 108 +++++++------- ...ingerprinting.passport-http.plugin.spec.js | 134 +++++++++--------- ...ngerprinting.passport-local.plugin.spec.js | 132 ++++++++--------- ...-injection-analyzer.express.plugin.spec.js | 2 +- .../plugin.express.plugin.spec.js | 4 +- .../taint-tracking.express.plugin.spec.js | 10 +- .../appsec/index.body-parser.plugin.spec.js | 90 ++++++------ .../appsec/index.cookie-parser.plugin.spec.js | 100 ++++++------- .../test/appsec/index.express.plugin.spec.js | 18 ++- .../appsec/rasp/ssrf.express.plugin.spec.js | 2 +- 14 files changed, 353 insertions(+), 316 deletions(-) diff --git a/packages/datadog-instrumentations/src/express.js b/packages/datadog-instrumentations/src/express.js index 22332caf32c..0186639b95c 100644 --- a/packages/datadog-instrumentations/src/express.js +++ b/packages/datadog-instrumentations/src/express.js @@ -70,6 +70,8 @@ addHook({ name: 'express', versions: ['>=4 <5.0.0'] }, express => { }) addHook({ name: 'express', versions: ['>=5.0.0'] }, express => { + shimmer.wrap(express.Router, 'use', wrapRouterMethod) + shimmer.wrap(express.Router, 'route', wrapRouterMethod) shimmer.wrap(express.application, 'handle', wrapHandle) shimmer.wrap(express.response, 'json', wrapResponseJson) @@ -98,7 +100,7 @@ function publishQueryParsedAndNext (req, res, next) { addHook({ name: 'express', - versions: ['>=4'], + versions: ['>=4 <5.0.0'], file: 'lib/middleware/query.js' }, query => { return shimmer.wrapFunction(query, query => function () { diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index c1134059c53..1f86a31ebb3 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -168,38 +168,52 @@ function createWrapRouterMethod (name) { return wrapMethod } -const processParamsStartCh = channel('datadog:express:process_params:start') -function wrapParams (req) { - let params - Object.defineProperty(req, 'params', { - get () { return params }, - set (val) { - params = val - if (val && processParamsStartCh.hasSubscribers) { +const wrapRouterMethod = createWrapRouterMethod('router') + +addHook({ name: 'router', versions: ['>=1'] }, Router => { + shimmer.wrap(Router.prototype, 'use', wrapRouterMethod) + shimmer.wrap(Router.prototype, 'route', wrapRouterMethod) + + return Router +}) + +function createWrapLayerMethod () { + const processParamsStartCh = channel('datadog:express:process_params:start') + const queryParserReadCh = channel('datadog:query:read:finish') + + return function wrapMethod (original) { + return function wrappedHandleRequest (req, res, next) { + if (processParamsStartCh.hasSubscribers) { const abortController = new AbortController() processParamsStartCh.publish({ req, - res: req?.res, + res, abortController, params: req?.params }) + if (abortController.signal.aborted) return } - } - }) -} -const wrapRouterMethod = createWrapRouterMethod('router') -const wrapExpressRouterMethod = createWrapRouterMethod('express') + if (queryParserReadCh.hasSubscribers && req) { + const abortController = new AbortController() + const query = req.query -addHook({ name: 'router', versions: ['>=1'] }, Router => { - shimmer.wrap(Router.prototype, 'use', wrapRouterMethod) - shimmer.wrap(Router.prototype, 'route', wrapRouterMethod) + queryParserReadCh.publish({ req, res, query, abortController }) - shimmer.wrap(Router.prototype, 'use', wrapExpressRouterMethod) - shimmer.wrap(Router.prototype, 'route', wrapExpressRouterMethod) + if (abortController.signal.aborted) return + } - return Router + return original.apply(this, arguments) + } + } +} + +addHook({ + name: 'router', file: 'lib/layer.js', versions: ['>=2'] +}, Layer => { + shimmer.wrap(Layer.prototype, 'handleRequest', createWrapLayerMethod()) + return Layer }) module.exports = { createWrapRouterMethod } diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js index ed46cbe5f2e..83a42b50876 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js @@ -12,6 +12,7 @@ const { HTTP_REQUEST_HEADER_NAME, HTTP_REQUEST_PARAMETER, HTTP_REQUEST_PATH_PARAM, + HTTP_REQUEST_QUERY, HTTP_REQUEST_URI } = require('./source-types') const { EXECUTED_SOURCE } = require('../telemetry/iast-metric') @@ -77,6 +78,15 @@ class TaintTrackingPlugin extends SourceIastPlugin { } ) + this.addSub( + { channelName: 'datadog:query:read:finish', tag: HTTP_REQUEST_QUERY }, + ({ req }) => { + if (req && req.query !== null && typeof req.query === 'object') { + this._taintTrackingHandler(HTTP_REQUEST_QUERY, req, 'query') + } + } + ) + this.addSub( { channelName: 'apm:graphql:resolve:start', tag: HTTP_REQUEST_BODY }, (data) => { diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js b/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js index f5c2ca2e8b0..9a810baafb1 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js @@ -9,6 +9,7 @@ module.exports = { HTTP_REQUEST_PARAMETER: 'http.request.parameter', HTTP_REQUEST_PATH: 'http.request.path', HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter', + HTTP_REQUEST_QUERY: 'http.request.query', HTTP_REQUEST_URI: 'http.request.uri', KAFKA_MESSAGE_KEY: 'kafka.message.key', KAFKA_MESSAGE_VALUE: 'kafka.message.value' diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.express.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.express.plugin.spec.js index bc7c918965c..112d634cca9 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.express.plugin.spec.js @@ -8,72 +8,74 @@ const agent = require('../plugins/agent') const appsec = require('../../src/appsec') const Config = require('../../src/config') -describe('Attacker fingerprinting', () => { - let port, server +withVersions('express', 'express', expressVersion => { + describe('Attacker fingerprinting', () => { + let port, server - before(() => { - return agent.load(['express', 'http'], { client: false }) - }) + before(() => { + return agent.load(['express', 'http'], { client: false }) + }) - before((done) => { - const express = require('../../../../versions/express').get() - const bodyParser = require('../../../../versions/body-parser').get() + before((done) => { + const express = require(`../../../../versions/express@${expressVersion}`).get() + const bodyParser = require('../../../../versions/body-parser').get() - const app = express() - app.use(bodyParser.json()) + const app = express() + app.use(bodyParser.json()) - app.post('/', (req, res) => { - res.end('DONE') - }) + app.post('/', (req, res) => { + res.end('DONE') + }) - server = app.listen(port, () => { - port = server.address().port - done() + server = app.listen(port, () => { + port = server.address().port + done() + }) }) - }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - beforeEach(() => { - appsec.enable(new Config( - { - appsec: { - enabled: true, - rules: path.join(__dirname, 'attacker-fingerprinting-rules.json') + beforeEach(() => { + appsec.enable(new Config( + { + appsec: { + enabled: true, + rules: path.join(__dirname, 'attacker-fingerprinting-rules.json') + } } - } - )) - }) + )) + }) - afterEach(() => { - appsec.disable() - }) + afterEach(() => { + appsec.disable() + }) - it('should report http fingerprints', async () => { - await axios.post( - `http://localhost:${port}/?key=testattack`, - { - bodyParam: 'bodyValue' - }, - { - headers: { - headerName: 'headerValue', - 'x-real-ip': '255.255.255.255' + it('should report http fingerprints', async () => { + await axios.post( + `http://localhost:${port}/?key=testattack`, + { + bodyParam: 'bodyValue' + }, + { + headers: { + headerName: 'headerValue', + 'x-real-ip': '255.255.255.255' + } } - } - ) + ) - await agent.use((traces) => { - const span = traces[0][0] - assert.property(span.meta, '_dd.appsec.fp.http.header') - assert.equal(span.meta['_dd.appsec.fp.http.header'], 'hdr-0110000110-6431a3e6-5-55682ec1') - assert.property(span.meta, '_dd.appsec.fp.http.network') - assert.equal(span.meta['_dd.appsec.fp.http.network'], 'net-1-0100000000') - assert.property(span.meta, '_dd.appsec.fp.http.endpoint') - assert.equal(span.meta['_dd.appsec.fp.http.endpoint'], 'http-post-8a5edab2-2c70e12b-be31090f') + await agent.use((traces) => { + const span = traces[0][0] + assert.property(span.meta, '_dd.appsec.fp.http.header') + assert.equal(span.meta['_dd.appsec.fp.http.header'], 'hdr-0110000110-6431a3e6-5-55682ec1') + assert.property(span.meta, '_dd.appsec.fp.http.network') + assert.equal(span.meta['_dd.appsec.fp.http.network'], 'net-1-0100000000') + assert.property(span.meta, '_dd.appsec.fp.http.endpoint') + assert.equal(span.meta['_dd.appsec.fp.http.endpoint'], 'http-post-8a5edab2-2c70e12b-be31090f') + }) }) }) }) diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js index 58b54e2c704..e4f33ccb415 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js @@ -17,91 +17,93 @@ function assertFingerprintInTraces (traces) { assert.equal(span.meta['_dd.appsec.fp.http.endpoint'], 'http-post-7e93fba0--') } -withVersions('passport-http', 'passport-http', version => { - describe('Attacker fingerprinting', () => { - let port, server, axios +withVersions('express', 'express', expressVersion => { + withVersions('passport-http', 'passport-http', version => { + describe('Attacker fingerprinting', () => { + let port, server, axios - before(() => { - return agent.load(['express', 'http'], { client: false }) - }) + before(() => { + return agent.load(['express', 'http'], { client: false }) + }) - before(() => { - appsec.enable(new Config({ - appsec: true - })) - }) + before(() => { + appsec.enable(new Config({ + appsec: true + })) + }) - before((done) => { - const express = require('../../../../versions/express').get() - const bodyParser = require('../../../../versions/body-parser').get() - const passport = require('../../../../versions/passport').get() - const { BasicStrategy } = require(`../../../../versions/passport-http@${version}`).get() - - const app = express() - app.use(bodyParser.json()) - app.use(passport.initialize()) - - passport.use(new BasicStrategy( - function verify (username, password, done) { - if (username === 'success') { - done(null, { - id: 1234, - username - }) - } else { - done(null, false) + before((done) => { + const express = require(`../../../../versions/express@${expressVersion}`).get() + const bodyParser = require('../../../../versions/body-parser').get() + const passport = require('../../../../versions/passport').get() + const { BasicStrategy } = require(`../../../../versions/passport-http@${version}`).get() + + const app = express() + app.use(bodyParser.json()) + app.use(passport.initialize()) + + passport.use(new BasicStrategy( + function verify (username, password, done) { + if (username === 'success') { + done(null, { + id: 1234, + username + }) + } else { + done(null, false) + } } - } - )) + )) - app.post('/login', passport.authenticate('basic', { session: false }), function (req, res) { - res.end() - }) + app.post('/login', passport.authenticate('basic', { session: false }), function (req, res) { + res.end() + }) - server = app.listen(port, () => { - port = server.address().port - axios = Axios.create({ - baseURL: `http://localhost:${port}` + server = app.listen(port, () => { + port = server.address().port + axios = Axios.create({ + baseURL: `http://localhost:${port}` + }) + done() }) - done() }) - }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - after(() => { - appsec.disable() - }) + after(() => { + appsec.disable() + }) + + it('should report http fingerprints on login fail', async () => { + try { + await axios.post( + `http://localhost:${port}/login`, {}, { + auth: { + username: 'fail', + password: '1234' + } + } + ) + } catch (e) {} + + await agent.use(assertFingerprintInTraces) + }) - it('should report http fingerprints on login fail', async () => { - try { + it('should report http fingerprints on login successful', async () => { await axios.post( `http://localhost:${port}/login`, {}, { auth: { - username: 'fail', + username: 'success', password: '1234' } } ) - } catch (e) {} - await agent.use(assertFingerprintInTraces) - }) - - it('should report http fingerprints on login successful', async () => { - await axios.post( - `http://localhost:${port}/login`, {}, { - auth: { - username: 'success', - password: '1234' - } - } - ) - - await agent.use(assertFingerprintInTraces) + await agent.use(assertFingerprintInTraces) + }) }) }) }) diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js index b51aa57de9c..680d312c58d 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js @@ -17,89 +17,91 @@ function assertFingerprintInTraces (traces) { assert.equal(span.meta['_dd.appsec.fp.http.endpoint'], 'http-post-7e93fba0--f29f6224') } -withVersions('passport-local', 'passport-local', version => { - describe('Attacker fingerprinting', () => { - let port, server, axios +withVersions('express', 'express', expressVersion => { + withVersions('passport-local', 'passport-local', version => { + describe('Attacker fingerprinting', () => { + let port, server, axios - before(() => { - return agent.load(['express', 'http'], { client: false }) - }) + before(() => { + return agent.load(['express', 'http'], { client: false }) + }) - before(() => { - appsec.enable(new Config({ - appsec: true - })) - }) + before(() => { + appsec.enable(new Config({ + appsec: true + })) + }) - before((done) => { - const express = require('../../../../versions/express').get() - const bodyParser = require('../../../../versions/body-parser').get() - const passport = require('../../../../versions/passport').get() - const LocalStrategy = require(`../../../../versions/passport-local@${version}`).get() - - const app = express() - app.use(bodyParser.json()) - app.use(passport.initialize()) - - passport.use(new LocalStrategy( - function verify (username, password, done) { - if (username === 'success') { - done(null, { - id: 1234, - username - }) - } else { - done(null, false) + before((done) => { + const express = require(`../../../../versions/express@${expressVersion}`).get() + const bodyParser = require('../../../../versions/body-parser').get() + const passport = require('../../../../versions/passport').get() + const LocalStrategy = require(`../../../../versions/passport-local@${version}`).get() + + const app = express() + app.use(bodyParser.json()) + app.use(passport.initialize()) + + passport.use(new LocalStrategy( + function verify (username, password, done) { + if (username === 'success') { + done(null, { + id: 1234, + username + }) + } else { + done(null, false) + } } - } - )) + )) - app.post('/login', passport.authenticate('local', { session: false }), function (req, res) { - res.end() - }) + app.post('/login', passport.authenticate('local', { session: false }), function (req, res) { + res.end() + }) - server = app.listen(port, () => { - port = server.address().port - axios = Axios.create({ - baseURL: `http://localhost:${port}` + server = app.listen(port, () => { + port = server.address().port + axios = Axios.create({ + baseURL: `http://localhost:${port}` + }) + done() }) - done() }) - }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - after(() => { - appsec.disable() - }) + after(() => { + appsec.disable() + }) + + it('should report http fingerprints on login fail', async () => { + try { + await axios.post( + `http://localhost:${port}/login`, + { + username: 'fail', + password: '1234' + } + ) + } catch (e) {} + + await agent.use(assertFingerprintInTraces) + }) - it('should report http fingerprints on login fail', async () => { - try { + it('should report http fingerprints on login successful', async () => { await axios.post( `http://localhost:${port}/login`, { - username: 'fail', + username: 'success', password: '1234' } ) - } catch (e) {} - await agent.use(assertFingerprintInTraces) - }) - - it('should report http fingerprints on login successful', async () => { - await axios.post( - `http://localhost:${port}/login`, - { - username: 'success', - password: '1234' - } - ) - - await agent.use(assertFingerprintInTraces) + await agent.use(assertFingerprintInTraces) + }) }) }) }) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js index 4177dc78aba..919f6a7cc2f 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js @@ -7,7 +7,7 @@ const os = require('os') const fs = require('fs') const { clearCache } = require('../../../../src/appsec/iast/vulnerability-reporter') -describe('Code injection vulnerability', () => { +describe.skip('Code injection vulnerability', () => { withVersions('express', 'express', '>4.18.0', version => { let i = 0 let evalFunctionsPath diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js index a9a995783f1..4bb07fc735e 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js @@ -6,7 +6,7 @@ const { URL } = require('url') function noop () {} -describe('Taint tracking plugin sources express tests', () => { +describe.skip('Taint tracking plugin sources express tests', () => { withVersions('express', 'express', '>=4.8.0', version => { prepareTestServerForIastInExpress('in express', version, (testThatRequestHasVulnerability, _, config) => { @@ -23,7 +23,7 @@ describe('Taint tracking plugin sources express tests', () => { }, 'COMMAND_INJECTION', 1, noop, makePostRequest) }) - describe('tainted query param', () => { + describe.skip('tainted query param', () => { function makeRequestWithQueryParam (done) { axios.get(`http://localhost:${config.port}/?command=echo`).catch(done) } diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index 7465f6b2408..f085c514445 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -13,7 +13,7 @@ const { HTTP_REQUEST_URI } = require('../../../../../src/appsec/iast/taint-tracking/source-types') -describe('URI sourcing with express', () => { +describe.skip('URI sourcing with express', () => { let express let appListener @@ -45,8 +45,8 @@ describe('URI sourcing with express', () => { iast.disable() }) - - it('should taint uri', done => { + // not supported express5 + it.skip('should taint uri', done => { const app = express() app.get('/path/*', (req, res) => { const store = storage.getStore() @@ -181,7 +181,7 @@ describe('Path params sourcing with express', () => { }) }) - it('should taint path param on router.params callback', function (done) { + it.skip('should taint path param on router.params callback', function (done) { const app = express() app.use('/:parameter1/:parameter2', (req, res) => { @@ -201,7 +201,7 @@ describe('Path params sourcing with express', () => { }) }) - it('should taint path param on router.params callback with custom implementation', function (done) { + it.skip('should taint path param on router.params callback with custom implementation', function (done) { const app = express() app.use('/:parameter1/:parameter2', (req, res) => { diff --git a/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js b/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js index 458a69ee97d..df37ef1a745 100644 --- a/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js @@ -7,62 +7,64 @@ const appsec = require('../../src/appsec') const Config = require('../../src/config') const { json } = require('../../src/appsec/blocked_templates') -withVersions('body-parser', 'body-parser', version => { - describe('Suspicious request blocking - body-parser', () => { - let port, server, requestBody +withVersions('express', 'express', expressVersion => { + withVersions('body-parser', 'body-parser', version => { + describe('Suspicious request blocking - body-parser', () => { + let port, server, requestBody - before(() => { - return agent.load(['express', 'body-parser', 'http'], { client: false }) - }) + before(() => { + return agent.load(['express', 'body-parser', 'http'], { client: false }) + }) - before((done) => { - const express = require('../../../../versions/express').get() - const bodyParser = require(`../../../../versions/body-parser@${version}`).get() + before((done) => { + const express = require(`../../../../versions/express@${expressVersion}`).get() + const bodyParser = require(`../../../../versions/body-parser@${version}`).get() - const app = express() - app.use(bodyParser.json()) - app.post('/', (req, res) => { - requestBody() - res.end('DONE') - }) + const app = express() + app.use(bodyParser.json()) + app.post('/', (req, res) => { + requestBody() + res.end('DONE') + }) - server = app.listen(port, () => { - port = server.address().port - done() + server = app.listen(port, () => { + port = server.address().port + done() + }) }) - }) - beforeEach(async () => { - requestBody = sinon.stub() - appsec.enable(new Config({ appsec: { enabled: true, rules: path.join(__dirname, 'body-parser-rules.json') } })) - }) + beforeEach(async () => { + requestBody = sinon.stub() + appsec.enable(new Config({ appsec: { enabled: true, rules: path.join(__dirname, 'body-parser-rules.json') } })) + }) - afterEach(() => { - appsec.disable() - }) + afterEach(() => { + appsec.disable() + }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - it('should not block the request without an attack', async () => { - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + it('should not block the request without an attack', async () => { + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - expect(requestBody).to.be.calledOnce - expect(res.data).to.be.equal('DONE') - }) + expect(requestBody).to.be.calledOnce + expect(res.data).to.be.equal('DONE') + }) - it('should block the request when attack is detected', async () => { - try { - await axios.post(`http://localhost:${port}/`, { key: 'testattack' }) + it('should block the request when attack is detected', async () => { + try { + await axios.post(`http://localhost:${port}/`, { key: 'testattack' }) - return Promise.reject(new Error('Request should not return 200')) - } catch (e) { - expect(e.response.status).to.be.equals(403) - expect(e.response.data).to.be.deep.equal(JSON.parse(json)) - expect(requestBody).not.to.be.called - } + return Promise.reject(new Error('Request should not return 200')) + } catch (e) { + expect(e.response.status).to.be.equals(403) + expect(e.response.data).to.be.deep.equal(JSON.parse(json)) + expect(requestBody).not.to.be.called + } + }) }) }) }) diff --git a/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js b/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js index fed6bbcbf45..8ec77ce8465 100644 --- a/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js @@ -8,66 +8,70 @@ const appsec = require('../../src/appsec') const Config = require('../../src/config') const { json } = require('../../src/appsec/blocked_templates') -withVersions('cookie-parser', 'cookie-parser', version => { - describe('Suspicious request blocking - cookie-parser', () => { - let port, server, requestCookie +withVersions('express', 'express', expressVersion => { + withVersions('cookie-parser', 'cookie-parser', version => { + describe('Suspicious request blocking - cookie-parser', () => { + let port, server, requestCookie - before(() => { - return agent.load(['express', 'cookie-parser', 'http'], { client: false }) - }) + before(() => { + return agent.load(['express', 'cookie-parser', 'http'], { client: false }) + }) - before((done) => { - const express = require('../../../../versions/express').get() - const cookieParser = require(`../../../../versions/cookie-parser@${version}`).get() + before((done) => { + const express = require(`../../../../versions/express@${expressVersion}`).get() + const cookieParser = require(`../../../../versions/cookie-parser@${version}`).get() - const app = express() - app.use(cookieParser()) - app.post('/', (req, res) => { - requestCookie() - res.end('DONE') - }) + const app = express() + app.use(cookieParser()) + app.post('/', (req, res) => { + requestCookie() + res.end('DONE') + }) - server = app.listen(port, () => { - port = server.address().port - done() + server = app.listen(port, () => { + port = server.address().port + done() + }) }) - }) - beforeEach(async () => { - requestCookie = sinon.stub() - appsec.enable(new Config({ appsec: { enabled: true, rules: path.join(__dirname, 'cookie-parser-rules.json') } })) - }) + beforeEach(async () => { + requestCookie = sinon.stub() + appsec.enable(new Config({ + appsec: { enabled: true, rules: path.join(__dirname, 'cookie-parser-rules.json') } + })) + }) - afterEach(() => { - appsec.disable() - }) + afterEach(() => { + appsec.disable() + }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - it('should not block the request without an attack', async () => { - const res = await axios.post(`http://localhost:${port}/`, {}) + it('should not block the request without an attack', async () => { + const res = await axios.post(`http://localhost:${port}/`, {}) - sinon.assert.calledOnce(requestCookie) - assert.strictEqual(res.data, 'DONE') - }) + sinon.assert.calledOnce(requestCookie) + assert.strictEqual(res.data, 'DONE') + }) - it('should block the request when attack is detected', async () => { - try { - await axios.post(`http://localhost:${port}/`, {}, { - headers: { - Cookie: 'key=testattack' - } - }) + it('should block the request when attack is detected', async () => { + try { + await axios.post(`http://localhost:${port}/`, {}, { + headers: { + Cookie: 'key=testattack' + } + }) - return Promise.reject(new Error('Request should not return 200')) - } catch (e) { - assert.strictEqual(e.response.status, 403) - assert.deepEqual(e.response.data, JSON.parse(json)) - sinon.assert.notCalled(requestCookie) - } + return Promise.reject(new Error('Request should not return 200')) + } catch (e) { + assert.strictEqual(e.response.status, 403) + assert.deepEqual(e.response.data, JSON.parse(json)) + sinon.assert.notCalled(requestCookie) + } + }) }) }) }) diff --git a/packages/dd-trace/test/appsec/index.express.plugin.spec.js b/packages/dd-trace/test/appsec/index.express.plugin.spec.js index bb674015f78..ef2639af601 100644 --- a/packages/dd-trace/test/appsec/index.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.express.plugin.spec.js @@ -11,7 +11,7 @@ const { json } = require('../../src/appsec/blocked_templates') const zlib = require('zlib') withVersions('express', 'express', version => { - describe('Suspicious request blocking - path parameters', () => { + describe.skip('Suspicious request blocking - path parameters', () => { let server, paramCallbackSpy, axios before(() => { @@ -19,7 +19,7 @@ withVersions('express', 'express', version => { }) before((done) => { - const express = require('../../../../versions/express').get() + const express = require(`../../../../versions/express@${version}`).get() const app = express() @@ -44,11 +44,9 @@ withVersions('express', 'express', version => { paramCallbackSpy = sinon.spy(paramCallback) - app.param(() => { - return paramCallbackSpy - }) + app.param('callbackedParameter', paramCallbackSpy) - app.param('callbackedParameter') + // app.param('callbackedParameter') getPort().then((port) => { server = app.listen(port, () => { @@ -161,7 +159,7 @@ withVersions('express', 'express', version => { }) }) - describe('path parameter callback', () => { + describe.skip('path parameter callback', () => { it('should not block the request when attack is not detected', async () => { const res = await axios.get('/callback-path-param/safe_param') assert.equal(res.status, 200) @@ -183,7 +181,7 @@ withVersions('express', 'express', version => { }) }) - describe('Suspicious request blocking - query', () => { + describe.skip('Suspicious request blocking - query', () => { let server, requestBody, axios before(() => { @@ -191,7 +189,7 @@ withVersions('express', 'express', version => { }) before((done) => { - const express = require('../../../../versions/express').get() + const express = require(`../../../../versions/express@${version}`).get() const app = express() @@ -256,7 +254,7 @@ withVersions('express', 'express', version => { }) before((done) => { - const express = require('../../../../versions/express').get() + const express = require(`../../../../versions/express@${version}`).get() const bodyParser = require('../../../../versions/body-parser').get() const app = express() diff --git a/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js index 6b5ba45ad0a..4524643b4f2 100644 --- a/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js @@ -10,7 +10,7 @@ const { checkRaspExecutedAndNotThreat, checkRaspExecutedAndHasThreat } = require function noop () {} -describe('RASP - ssrf', () => { +describe.skip('RASP - ssrf', () => { withVersions('express', 'express', expressVersion => { let app, server, axios From bd9dd64fd3f59bad3501a02881cdc9a2e7503d3f Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 20 Nov 2024 14:49:54 +0100 Subject: [PATCH 06/31] instrument path and query params and response --- .../datadog-instrumentations/src/express.js | 46 ++++++++++++++++--- .../datadog-instrumentations/src/router.js | 15 ++---- .../src/appsec/iast/taint-tracking/plugin.js | 6 +-- packages/dd-trace/src/appsec/rasp/utils.js | 3 +- ...-injection-analyzer.express.plugin.spec.js | 2 +- .../taint-tracking.express.plugin.spec.js | 2 +- .../test/appsec/index.express.plugin.spec.js | 2 +- .../appsec/rasp/lfi.express.plugin.spec.js | 5 +- .../appsec/rasp/ssrf.express.plugin.spec.js | 5 +- 9 files changed, 58 insertions(+), 28 deletions(-) diff --git a/packages/datadog-instrumentations/src/express.js b/packages/datadog-instrumentations/src/express.js index 0186639b95c..719cf91ee99 100644 --- a/packages/datadog-instrumentations/src/express.js +++ b/packages/datadog-instrumentations/src/express.js @@ -70,17 +70,21 @@ addHook({ name: 'express', versions: ['>=4 <5.0.0'] }, express => { }) addHook({ name: 'express', versions: ['>=5.0.0'] }, express => { - shimmer.wrap(express.Router, 'use', wrapRouterMethod) - shimmer.wrap(express.Router, 'route', wrapRouterMethod) shimmer.wrap(express.application, 'handle', wrapHandle) - - shimmer.wrap(express.response, 'json', wrapResponseJson) - shimmer.wrap(express.response, 'jsonp', wrapResponseJson) - shimmer.wrap(express.response, 'render', wrapResponseRender) + shimmer.wrap(express.Router.prototype, 'use', wrapRouterMethod) + shimmer.wrap(express.Router.prototype, 'route', wrapRouterMethod) return express }) +addHook({ name: 'express', file: ['lib/response.js'], versions: ['>=5.0.0'] }, response => { + shimmer.wrap(response, 'json', wrapResponseJson) + shimmer.wrap(response, 'jsonp', wrapResponseJson) + shimmer.wrap(response, 'render', wrapResponseRender) + + return response +}) + const queryParserReadCh = channel('datadog:query:read:finish') function publishQueryParsedAndNext (req, res, next) { @@ -145,3 +149,33 @@ addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'] }, express => { shimmer.wrap(express.Router, 'process_params', wrapProcessParamsMethod(2)) return express }) + +addHook({ name: 'express', file: ['lib/express.js'], versions: ['>=5.0.0'] }, express => { + shimmer.wrapFunction(express, function (original) { + const app = original.call(this, arguments) + const requestProto = Object.getPrototypeOf(app.request) + const requestDescriptor = Object.getOwnPropertyDescriptor(requestProto, 'query') + + shimmer.wrap(requestDescriptor, 'get', function (originalGet) { + return function wrappedGet () { + const query = originalGet.apply(this, arguments) + + if (queryParserReadCh.hasSubscribers) { + const abortController = new AbortController() + + queryParserReadCh.publish({ req: this, res: this.res, query, abortController }) + + if (abortController.signal.aborted) return + } + + return query + } + }) + + Object.defineProperty(requestProto, 'query', requestDescriptor) + + return original + }) + + return express +}) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 1f86a31ebb3..44b275ad10d 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -25,9 +25,11 @@ function createWrapRouterMethod (name) { const lastIndex = arguments.length - 1 const name = original._name || original.name const req = arguments[arguments.length > 3 ? 1 : 0] - // wrapParams(req) const next = arguments[lastIndex] + // explicitly call getter for express5 + req.query + if (typeof next === 'function') { arguments[lastIndex] = wrapNext(req, next) } @@ -178,8 +180,8 @@ addHook({ name: 'router', versions: ['>=1'] }, Router => { }) function createWrapLayerMethod () { + // TODO: create a dedicate channel for this const processParamsStartCh = channel('datadog:express:process_params:start') - const queryParserReadCh = channel('datadog:query:read:finish') return function wrapMethod (original) { return function wrappedHandleRequest (req, res, next) { @@ -195,15 +197,6 @@ function createWrapLayerMethod () { if (abortController.signal.aborted) return } - if (queryParserReadCh.hasSubscribers && req) { - const abortController = new AbortController() - const query = req.query - - queryParserReadCh.publish({ req, res, query, abortController }) - - if (abortController.signal.aborted) return - } - return original.apply(this, arguments) } } diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js index 83a42b50876..71aba6101eb 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js @@ -80,9 +80,9 @@ class TaintTrackingPlugin extends SourceIastPlugin { this.addSub( { channelName: 'datadog:query:read:finish', tag: HTTP_REQUEST_QUERY }, - ({ req }) => { - if (req && req.query !== null && typeof req.query === 'object') { - this._taintTrackingHandler(HTTP_REQUEST_QUERY, req, 'query') + ({ query }) => { + if (query !== null && typeof query === 'object') { + this._taintTrackingHandler(HTTP_REQUEST_QUERY, query) } } ) diff --git a/packages/dd-trace/src/appsec/rasp/utils.js b/packages/dd-trace/src/appsec/rasp/utils.js index a454a71b8c6..5cfac2de524 100644 --- a/packages/dd-trace/src/appsec/rasp/utils.js +++ b/packages/dd-trace/src/appsec/rasp/utils.js @@ -46,7 +46,8 @@ function handleResult (actions, req, res, abortController, config) { if (blockingAction) { const rootSpan = web.root(req) // Should block only in express - if (rootSpan?.context()._name === 'express.request') { + const name = rootSpan?.context()._name + if (name === 'express.request' || name === 'web.request') { const abortError = new DatadogRaspAbortError(req, res, blockingAction) abortController.abort(abortError) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js index 919f6a7cc2f..4177dc78aba 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/code-injection-analyzer.express.plugin.spec.js @@ -7,7 +7,7 @@ const os = require('os') const fs = require('fs') const { clearCache } = require('../../../../src/appsec/iast/vulnerability-reporter') -describe.skip('Code injection vulnerability', () => { +describe('Code injection vulnerability', () => { withVersions('express', 'express', '>4.18.0', version => { let i = 0 let evalFunctionsPath diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index f085c514445..416ce899afe 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -13,7 +13,7 @@ const { HTTP_REQUEST_URI } = require('../../../../../src/appsec/iast/taint-tracking/source-types') -describe.skip('URI sourcing with express', () => { +describe('URI sourcing with express', () => { let express let appListener diff --git a/packages/dd-trace/test/appsec/index.express.plugin.spec.js b/packages/dd-trace/test/appsec/index.express.plugin.spec.js index ef2639af601..a2b100b4f0b 100644 --- a/packages/dd-trace/test/appsec/index.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.express.plugin.spec.js @@ -181,7 +181,7 @@ withVersions('express', 'express', version => { }) }) - describe.skip('Suspicious request blocking - query', () => { + describe('Suspicious request blocking - query', () => { let server, requestBody, axios before(() => { diff --git a/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js index b5b825cc628..10689b1478b 100644 --- a/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js @@ -102,7 +102,7 @@ describe('RASP - lfi', () => { describe(description, () => { const getAppFn = options.getAppFn ?? getApp - it('should block param from the request', async () => { + it('should block param from the request', () => { app = getAppFn(fn, args, options) const file = args[vulnerableIndex] @@ -404,7 +404,8 @@ describe('RASP - lfi', () => { }) }) - describe('without express', () => { + // to be fixed + describe.skip('without express', () => { let app, server before(() => { diff --git a/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js index 4524643b4f2..cc816116ace 100644 --- a/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js @@ -10,7 +10,7 @@ const { checkRaspExecutedAndNotThreat, checkRaspExecutedAndHasThreat } = require function noop () {} -describe.skip('RASP - ssrf', () => { +describe('RASP - ssrf', () => { withVersions('express', 'express', expressVersion => { let app, server, axios @@ -208,7 +208,8 @@ describe.skip('RASP - ssrf', () => { }) }) - describe('without express', () => { + // to be fixed + describe.skip('without express', () => { let app, server, axios before(() => { From d217138191b841d3da05fd55fb9e277c63d0c7d1 Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 20 Nov 2024 15:11:36 +0100 Subject: [PATCH 07/31] skip uri tests --- .../iast/taint-tracking/plugin.express.plugin.spec.js | 4 ++-- .../sources/taint-tracking.express.plugin.spec.js | 10 ++++++++-- .../dd-trace/test/appsec/index.express.plugin.spec.js | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js index 4bb07fc735e..a9a995783f1 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.express.plugin.spec.js @@ -6,7 +6,7 @@ const { URL } = require('url') function noop () {} -describe.skip('Taint tracking plugin sources express tests', () => { +describe('Taint tracking plugin sources express tests', () => { withVersions('express', 'express', '>=4.8.0', version => { prepareTestServerForIastInExpress('in express', version, (testThatRequestHasVulnerability, _, config) => { @@ -23,7 +23,7 @@ describe.skip('Taint tracking plugin sources express tests', () => { }, 'COMMAND_INJECTION', 1, noop, makePostRequest) }) - describe.skip('tainted query param', () => { + describe('tainted query param', () => { function makeRequestWithQueryParam (done) { axios.get(`http://localhost:${config.port}/?command=echo`).catch(done) } diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index 416ce899afe..a2f7b935aee 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -45,8 +45,12 @@ describe('URI sourcing with express', () => { iast.disable() }) - // not supported express5 - it.skip('should taint uri', done => { + + it('should taint uri', function (done) { + // not supported express5 + if (!semver.satisfies(version, '<5')) { + this.skip() + } const app = express() app.get('/path/*', (req, res) => { const store = storage.getStore() @@ -181,6 +185,7 @@ describe('Path params sourcing with express', () => { }) }) + // to be fixed it.skip('should taint path param on router.params callback', function (done) { const app = express() @@ -201,6 +206,7 @@ describe('Path params sourcing with express', () => { }) }) + // to be fixed it.skip('should taint path param on router.params callback with custom implementation', function (done) { const app = express() diff --git a/packages/dd-trace/test/appsec/index.express.plugin.spec.js b/packages/dd-trace/test/appsec/index.express.plugin.spec.js index a2b100b4f0b..2fd88896388 100644 --- a/packages/dd-trace/test/appsec/index.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.express.plugin.spec.js @@ -11,7 +11,7 @@ const { json } = require('../../src/appsec/blocked_templates') const zlib = require('zlib') withVersions('express', 'express', version => { - describe.skip('Suspicious request blocking - path parameters', () => { + describe('Suspicious request blocking - path parameters', () => { let server, paramCallbackSpy, axios before(() => { @@ -175,6 +175,7 @@ withVersions('express', 'express', version => { } catch (e) { assert.equal(e.response.status, 403) assert.deepEqual(e.response.data, JSON.parse(json)) + // to be fixed sinon.assert.notCalled(paramCallbackSpy) } }) From cebb04983eeaa451bfafefcc88ecdc660bea4150 Mon Sep 17 00:00:00 2001 From: ishabi Date: Thu, 21 Nov 2024 09:14:51 +0100 Subject: [PATCH 08/31] fix http tests --- packages/dd-trace/src/appsec/index.js | 5 +++-- packages/dd-trace/src/appsec/rasp/utils.js | 2 +- .../dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js | 3 +-- .../dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js | 3 +-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/dd-trace/src/appsec/index.js b/packages/dd-trace/src/appsec/index.js index 4748148a2de..d195c7db23d 100644 --- a/packages/dd-trace/src/appsec/index.js +++ b/packages/dd-trace/src/appsec/index.js @@ -164,8 +164,9 @@ function incomingHttpEndTranslator ({ req, res }) { } // we need to keep this to support nextjs - if (req.query !== null && typeof req.query === 'object') { - persistent[addresses.HTTP_INCOMING_QUERY] = req.query + const query = req.query + if (query !== null && typeof query === 'object') { + persistent[addresses.HTTP_INCOMING_QUERY] = query } if (apiSecuritySampler.sampleRequest(req, res, true)) { diff --git a/packages/dd-trace/src/appsec/rasp/utils.js b/packages/dd-trace/src/appsec/rasp/utils.js index 5cfac2de524..e6aa96aca81 100644 --- a/packages/dd-trace/src/appsec/rasp/utils.js +++ b/packages/dd-trace/src/appsec/rasp/utils.js @@ -47,7 +47,7 @@ function handleResult (actions, req, res, abortController, config) { const rootSpan = web.root(req) // Should block only in express const name = rootSpan?.context()._name - if (name === 'express.request' || name === 'web.request') { + if (name === 'express.request') { const abortError = new DatadogRaspAbortError(req, res, blockingAction) abortController.abort(abortError) diff --git a/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js index 10689b1478b..210c3849ece 100644 --- a/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/lfi.express.plugin.spec.js @@ -404,8 +404,7 @@ describe('RASP - lfi', () => { }) }) - // to be fixed - describe.skip('without express', () => { + describe('without express', () => { let app, server before(() => { diff --git a/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js b/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js index cc816116ace..6b5ba45ad0a 100644 --- a/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/ssrf.express.plugin.spec.js @@ -208,8 +208,7 @@ describe('RASP - ssrf', () => { }) }) - // to be fixed - describe.skip('without express', () => { + describe('without express', () => { let app, server, axios before(() => { From 1d6707bc8a7e9b70012e0239fefd1928ad04a54f Mon Sep 17 00:00:00 2001 From: ishabi Date: Thu, 21 Nov 2024 15:18:17 +0100 Subject: [PATCH 09/31] fix express plugin tests --- .../datadog-plugin-express/test/index.spec.js | 859 +++++++----------- .../test/integration-test/client.spec.js | 47 +- 2 files changed, 340 insertions(+), 566 deletions(-) diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 60c421eff1e..0c0e03f5408 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -2,14 +2,14 @@ const { AsyncLocalStorage } = require('async_hooks') const axios = require('axios') +const semver = require('semver') const { ERROR_MESSAGE, ERROR_STACK, ERROR_TYPE } = require('../../dd-trace/src/constants') const agent = require('../../dd-trace/test/plugins/agent') const plugin = require('../src') -const semver = require('semver') const sort = spans => spans.sort((a, b) => a.start.toString() >= b.start.toString() ? 1 : -1) -describe('Plugin', function () { +describe('Plugin', () => { let tracer let express let appListener @@ -198,6 +198,162 @@ describe('Plugin', function () { }) }) + it('should do automatic instrumentation on middleware', done => { + const app = express() + const router = express.Router() + + router.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + app.use(function named (req, res, next) { next() }) + app.use('/app', router) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /app/user/:id') + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[0].meta).to.have.property('component', 'express') + + if (semver.intersects(version, '<5.0.0')) { + expect(spans[1]).to.have.property('resource', 'query') + expect(spans[1]).to.have.property('name', 'express.middleware') + expect(spans[1].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[1].meta).to.have.property('component', 'express') + expect(spans[2]).to.have.property('resource', 'expressInit') + expect(spans[2]).to.have.property('name', 'express.middleware') + expect(spans[2].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[2].meta).to.have.property('component', 'express') + } + + const namedSpanIndex = semver.intersects(version, '<5.0.0') ? 3 : 1 + + expect(spans[namedSpanIndex]).to.have.property('resource', 'named') + expect(spans[namedSpanIndex]).to.have.property('name', 'express.middleware') + expect(spans[namedSpanIndex].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[namedSpanIndex].meta).to.have.property('component', 'express') + + expect(spans[namedSpanIndex + 1]).to.have.property('resource', 'router') + expect(spans[namedSpanIndex + 1]).to.have.property('name', 'express.middleware') + expect(spans[namedSpanIndex + 1].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[namedSpanIndex + 1].meta).to.have.property('component', 'express') + + // expect(spans[namedSpanIndex + 2].resource).to.match(/^bound\s.*$/) called handle in express5 + expect(spans[namedSpanIndex + 2]).to.have.property('name', 'express.middleware') + expect(spans[namedSpanIndex + 2].parent_id.toString()).to.equal( + spans[namedSpanIndex + 1].span_id.toString() + ) + expect(spans[namedSpanIndex + 2].meta).to.have.property('component', 'express') + + expect(spans[namedSpanIndex + 3]).to.have.property('resource', '') + expect(spans[namedSpanIndex + 3]).to.have.property('name', 'express.middleware') + expect(spans[namedSpanIndex + 3].parent_id.toString()).to.equal( + spans[namedSpanIndex + 2].span_id.toString() + ) + expect(spans[namedSpanIndex + 3].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/app/user/1`) + .catch(done) + }) + }) + + it('should do automatic instrumentation on middleware that break the async context', done => { + let next + + const app = express() + const interval = setInterval(() => { + if (next) { + next() + clearInterval(interval) + } + }) + + app.use(function breaking (req, res, _next) { + next = _next + }) + app.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + const breakingSpanIndex = semver.intersects(version, '<5.0.0') ? 3 : 1 + + expect(spans[0]).to.have.property('resource', 'GET /user/:id') + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[breakingSpanIndex]).to.have.property('resource', 'breaking') + expect(spans[breakingSpanIndex]).to.have.property('name', 'express.middleware') + expect(spans[breakingSpanIndex].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user/1`) + .catch(done) + }) + }) + + it('should handle errors on middleware that break the async context', done => { + let next + + const error = new Error('boom') + const app = express() + const interval = setInterval(() => { + if (next) { + next() + clearInterval(interval) + } + }) + + app.use(function breaking (req, res, _next) { + next = _next + }) + app.use(() => { throw error }) + // eslint-disable-next-line n/handle-callback-err + app.use((err, req, res, next) => next()) + app.get('/user/:id', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + const errorSpanIndex = semver.intersects(version, '<5.0.0') ? 4 : 2 + + expect(spans[0]).to.have.property('name', 'express.request') + expect(spans[errorSpanIndex]).to.have.property('name', 'express.middleware') + expect(spans[errorSpanIndex].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[errorSpanIndex].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user/1`) + .catch(done) + }) + }) + it('should surround matchers based on regular expressions', done => { const app = express() const router = express.Router() @@ -254,6 +410,44 @@ describe('Plugin', function () { }) }) + it('should only keep the last matching path of a middleware stack', done => { + const app = express() + const router = express.Router() + + // supported only in express<5 + if (semver.intersects(version, '<5.0.0')) { + router.use('*', (req, res, next) => next()) + app.use('*', (req, res, next) => next()) + } + + router.use('/', (req, res, next) => next()) + router.use('/bar', (req, res, next) => next()) + router.use('/bar', (req, res, next) => { + res.status(200).send() + }) + + app.use('/', (req, res, next) => next()) + app.use('/foo/bar', (req, res, next) => next()) + app.use('/foo', router) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /foo/bar') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/foo/bar`) + .catch(done) + }) + }) + it('should support asynchronous routers', done => { const app = express() const router = express.Router() @@ -951,6 +1145,134 @@ describe('Plugin', function () { }) }) + it('should handle middleware errors', done => { + const app = express() + const error = new Error('boom') + + app.use((req, res, next) => next(error)) + // eslint-disable-next-line n/handle-callback-err + app.use((error, req, res, next) => res.status(500).send()) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + const secondErrorIndex = spans.length - 2 + + expect(spans[0]).to.have.property('error', 1) + expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[secondErrorIndex]).to.have.property('error', 1) + expect(spans[secondErrorIndex].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[secondErrorIndex].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[secondErrorIndex].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[secondErrorIndex].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`, { + validateStatus: status => status === 500 + }) + .catch(done) + }) + }) + + it('should handle middleware exceptions', done => { + const app = express() + const error = new Error('boom') + + app.use((req, res) => { throw error }) + // eslint-disable-next-line n/handle-callback-err + app.use((error, req, res, next) => res.status(500).send()) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + const secondErrorIndex = spans.length - 2 + + expect(spans[0]).to.have.property('error', 1) + expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + expect(spans[secondErrorIndex]).to.have.property('error', 1) + expect(spans[secondErrorIndex].meta).to.have.property(ERROR_TYPE, error.name) + expect(spans[secondErrorIndex].meta).to.have.property(ERROR_MESSAGE, error.message) + expect(spans[secondErrorIndex].meta).to.have.property(ERROR_STACK, error.stack) + expect(spans[0].meta).to.have.property('component', 'express') + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`, { + validateStatus: status => status === 500 + }) + .catch(done) + }) + }) + + it('should support capturing groups in routes', done => { + // not supported in Express5 + if (semver.intersects(version, '>=5.0.0')) { + done() + return + } + + const app = express() + + app.get('/:path(*)', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /:path(*)') + expect(spans[0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`) + .catch(done) + }) + }) + + it('should keep the properties untouched on nested router handlers', () => { + const router = express.Router() + const childRouter = express.Router() + + childRouter.get('/:id', (req, res) => { + res.status(200).send() + }) + + router.use('/users', childRouter) + + const layer = router.stack.find(layer => { + if (semver.intersects(version, '>=5.0.0')) { + return layer.matchers.find(matcher => matcher('/users')) + } + return layer.regexp.test('/users') + }) + + expect(layer.handle).to.have.ownProperty('stack') + }) + it('should keep user stores untouched', done => { const app = express() const storage = new AsyncLocalStorage() @@ -1084,539 +1406,6 @@ describe('Plugin', function () { }) }) }) - - if (semver.intersects(version, '<5.0.0')) { - it('should do automatic instrumentation on middleware', done => { - const app = express() - const router = express.Router() - - router.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - app.use(function named (req, res, next) { next() }) - app.use('/app', router) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /app/user/:id') - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[1]).to.have.property('resource', 'query') - expect(spans[1]).to.have.property('name', 'express.middleware') - expect(spans[1].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[1].meta).to.have.property('component', 'express') - expect(spans[2]).to.have.property('resource', 'expressInit') - expect(spans[2]).to.have.property('name', 'express.middleware') - expect(spans[2].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[2].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('resource', 'named') - expect(spans[3]).to.have.property('name', 'express.middleware') - expect(spans[3].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[3].meta).to.have.property('component', 'express') - expect(spans[4]).to.have.property('resource', 'router') - expect(spans[4]).to.have.property('name', 'express.middleware') - expect(spans[4].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[4].meta).to.have.property('component', 'express') - expect(spans[5].resource).to.match(/^bound\s.*$/) - expect(spans[5]).to.have.property('name', 'express.middleware') - expect(spans[5].parent_id.toString()).to.equal(spans[4].span_id.toString()) - expect(spans[5].meta).to.have.property('component', 'express') - expect(spans[6]).to.have.property('resource', '') - expect(spans[6]).to.have.property('name', 'express.middleware') - expect(spans[6].parent_id.toString()).to.equal(spans[5].span_id.toString()) - expect(spans[6].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/app/user/1`) - .catch(done) - }) - }) - - it('should do automatic instrumentation on middleware that break the async context', done => { - let next - - const app = express() - const interval = setInterval(() => { - if (next) { - next() - clearInterval(interval) - } - }) - - app.use(function breaking (req, res, _next) { - next = _next - }) - app.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /user/:id') - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('resource', 'breaking') - expect(spans[3]).to.have.property('name', 'express.middleware') - expect(spans[3].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user/1`) - .catch(done) - }) - }) - - it('should handle errors on middleware that break the async context', done => { - let next - - const error = new Error('boom') - const app = express() - const interval = setInterval(() => { - if (next) { - next() - clearInterval(interval) - } - }) - - app.use(function breaking (req, res, _next) { - next = _next - }) - app.use(() => { throw error }) - // eslint-disable-next-line n/handle-callback-err - app.use((err, req, res, next) => next()) - app.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[4]).to.have.property('name', 'express.middleware') - expect(spans[4].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[4].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user/1`) - .catch(done) - }) - }) - - // express v5 doesn't support regex paths - it('should only keep the last matching path of a middleware stack', done => { - const app = express() - const router = express.Router() - - router.use('/', (req, res, next) => next()) - router.use('*', (req, res, next) => next()) - router.use('/bar', (req, res, next) => next()) - router.use('/bar', (req, res, next) => { - res.status(200).send() - }) - - app.use('/', (req, res, next) => next()) - app.use('*', (req, res, next) => next()) - app.use('/foo/bar', (req, res, next) => next()) - app.use('/foo', router) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /foo/bar') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/foo/bar`) - .catch(done) - }) - }) - - it('should handle middleware errors', done => { - const app = express() - const error = new Error('boom') - - app.use((req, res, next) => next(error)) - // eslint-disable-next-line n/handle-callback-err - app.use((error, req, res, next) => res.status(500).send()) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('error', 1) - expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('error', 1) - expect(spans[3].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[3].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[3].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[3].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user`, { - validateStatus: status => status === 500 - }) - .catch(done) - }) - }) - - it('should handle middleware exceptions', done => { - const app = express() - const error = new Error('boom') - - app.use((req, res) => { throw error }) - // eslint-disable-next-line n/handle-callback-err - app.use((error, req, res, next) => res.status(500).send()) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('error', 1) - expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('error', 1) - expect(spans[3].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[3].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[3].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user`, { - validateStatus: status => status === 500 - }) - .catch(done) - }) - }) - - it('should support capturing groups in routes', done => { - const app = express() - - app.get('/:path(*)', (req, res) => { - res.status(200).send() - }) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /:path(*)') - expect(spans[0].meta).to.have.property('http.url', `http://localhost:${port}/user`) - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user`) - .catch(done) - }) - }) - - it('should keep the properties untouched on nested router handlers', () => { - const router = express.Router() - const childRouter = express.Router() - - childRouter.get('/:id', (req, res) => { - res.status(200).send() - }) - - router.use('/users', childRouter) - - const layer = router.stack.find(layer => layer.regexp.test('/users')) - - expect(layer.handle).to.have.ownProperty('stack') - }) - } else { - it('should do automatic instrumentation on middleware', done => { - const app = express() - const router = express.Router() - - router.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - app.use(function named (req, res, next) { next() }) - app.use('/app', router) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /app/user/:id') - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[1]).to.have.property('resource', 'named') - expect(spans[1]).to.have.property('name', 'express.middleware') - expect(spans[1].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[1].meta).to.have.property('component', 'express') - expect(spans[2]).to.have.property('resource', 'router') - expect(spans[2]).to.have.property('name', 'express.middleware') - expect(spans[2].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[2].meta).to.have.property('component', 'express') - expect(spans[3]).to.have.property('resource', 'handle') - expect(spans[3]).to.have.property('name', 'express.middleware') - expect(spans[3].meta).to.have.property('component', 'express') - expect(spans[4]).to.have.property('name', 'express.middleware') - expect(spans[4].parent_id.toString()).to.equal(spans[3].span_id.toString()) - expect(spans[4].meta).to.have.property('component', 'express') - expect(spans[4]).to.have.property('resource', '') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/app/user/1`) - .catch(done) - }) - }) - - it('should do automatic instrumentation on middleware that break the async context', done => { - let next - - const app = express() - const interval = setInterval(() => { - if (next) { - next() - clearInterval(interval) - } - }) - - app.use(function breaking (req, res, _next) { - next = _next - }) - app.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /user/:id') - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[1]).to.have.property('resource', 'breaking') - expect(spans[1]).to.have.property('name', 'express.middleware') - expect(spans[1].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user/1`) - .catch(done) - }) - }) - - it('should handle errors on middleware that break the async context', done => { - let next - - const error = new Error('boom') - const app = express() - const interval = setInterval(() => { - if (next) { - next() - clearInterval(interval) - } - }) - - app.use(function breaking (req, res, _next) { - next = _next - }) - app.use(() => { throw error }) - // eslint-disable-next-line n/handle-callback-err - app.use((err, req, res, next) => next()) - app.get('/user/:id', (req, res) => { - res.status(200).send() - }) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[1]).to.have.property('name', 'express.middleware') - expect(spans[2]).to.have.property('name', 'express.middleware') - expect(spans[2].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[2].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user/1`) - .catch(done) - }) - }) - - // express v5 doesn't support regex paths unless theres a name after the regex ie: *foo - it('should only keep the last matching path of a middleware stack', done => { - const app = express() - const router = express.Router() - - router.use('/', (req, res, next) => next()) - router.use('*foo', (req, res, next) => next()) - router.use('/bar', (req, res, next) => next()) - router.use('/bar', (req, res, next) => { - res.status(200).send() - }) - - app.use('/', (req, res, next) => next()) - app.use('*foo', (req, res, next) => next()) - app.use('/foo/bar', (req, res, next) => next()) - app.use('/foo', router) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /foo/bar') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/foo/bar`) - .catch(done) - }) - }) - - it('should handle middleware errors', done => { - const app = express() - const error = new Error('boom') - - app.use((req, res, next) => next(error)) - // eslint-disable-next-line n/handle-callback-err - app.use((error, req, res, next) => res.status(500).send()) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('error', 1) - expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[1]).to.have.property('error', 1) - expect(spans[1].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[1].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[1].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[1].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user`, { - validateStatus: status => status === 500 - }) - .catch(done) - }) - }) - - it('should handle middleware exceptions', done => { - const app = express() - const error = new Error('boom') - - app.use((req, res) => { throw error }) - // eslint-disable-next-line n/handle-callback-err - app.use((error, req, res, next) => res.status(500).send()) - - appListener = app.listen(0, 'localhost', () => { - const port = appListener.address().port - - agent - .use(traces => { - const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('error', 1) - expect(spans[0].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[0].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[0].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - expect(spans[1]).to.have.property('error', 1) - expect(spans[1].meta).to.have.property(ERROR_TYPE, error.name) - expect(spans[1].meta).to.have.property(ERROR_MESSAGE, error.message) - expect(spans[1].meta).to.have.property(ERROR_STACK, error.stack) - expect(spans[0].meta).to.have.property('component', 'express') - }) - .then(done) - .catch(done) - - axios - .get(`http://localhost:${port}/user`, { - validateStatus: status => status === 500 - }) - .catch(done) - }) - }) - } }) describe('with configuration', () => { diff --git a/packages/datadog-plugin-express/test/integration-test/client.spec.js b/packages/datadog-plugin-express/test/integration-test/client.spec.js index 054dcb81c58..2a8a8eb89b1 100644 --- a/packages/datadog-plugin-express/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-express/test/integration-test/client.spec.js @@ -35,36 +35,21 @@ describe('esm', () => { await agent.stop() }) - // express less than <5.0 uses their own router, which creates more middleware spans than the router - // that is used for v5+ - if (semver.intersects(version, '<5.0.0')) { - it('is instrumented', async () => { - proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) - - return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { - assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) - assert.isArray(payload) - assert.strictEqual(payload.length, 1) - assert.isArray(payload[0]) - assert.strictEqual(payload[0].length, 4) - assert.propertyVal(payload[0][0], 'name', 'express.request') - assert.propertyVal(payload[0][1], 'name', 'express.middleware') - }) - }).timeout(50000) - } else { - it('is instrumented', async () => { - proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) - - return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { - assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) - assert.isArray(payload) - assert.strictEqual(payload.length, 1) - assert.isArray(payload[0]) - assert.strictEqual(payload[0].length, 3) - assert.propertyVal(payload[0][0], 'name', 'express.request') - assert.propertyVal(payload[0][1], 'name', 'express.middleware') - }) - }).timeout(50000) - } + it('is instrumented', async () => { + proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) + // express less than <5.0 uses their own router, which creates more middleware spans than the router + // that is used for v5+ + const numberofSpans = semver.intersects(version, '<5.0.0') ? 4 : 3 + + return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { + assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) + assert.isArray(payload) + assert.strictEqual(payload.length, 1) + assert.isArray(payload[0]) + assert.strictEqual(payload[0].length, numberofSpans) + assert.propertyVal(payload[0][0], 'name', 'express.request') + assert.propertyVal(payload[0][1], 'name', 'express.middleware') + }) + }).timeout(50000) }) }) From d292bc2292016e142d7b5cd2693b0acf5faca602 Mon Sep 17 00:00:00 2001 From: ishabi Date: Fri, 22 Nov 2024 09:53:35 +0100 Subject: [PATCH 10/31] router param instrumentation --- .../datadog-instrumentations/src/express.js | 4 +- .../datadog-instrumentations/src/router.js | 39 ++++++++++++++++--- packages/dd-trace/src/appsec/channels.js | 1 + .../src/appsec/iast/taint-tracking/plugin.js | 9 +++++ packages/dd-trace/src/appsec/index.js | 5 ++- packages/dd-trace/src/appsec/rasp/utils.js | 3 +- .../appsec/iast/taint-tracking/plugin.spec.js | 28 ++++++++++--- .../taint-tracking.express.plugin.spec.js | 18 +++------ .../test/appsec/index.express.plugin.spec.js | 6 +-- packages/dd-trace/test/appsec/index.spec.js | 4 ++ 10 files changed, 86 insertions(+), 31 deletions(-) diff --git a/packages/datadog-instrumentations/src/express.js b/packages/datadog-instrumentations/src/express.js index 719cf91ee99..be38ee78102 100644 --- a/packages/datadog-instrumentations/src/express.js +++ b/packages/datadog-instrumentations/src/express.js @@ -57,7 +57,7 @@ function wrapResponseRender (render) { } } -addHook({ name: 'express', versions: ['>=4 <5.0.0'] }, express => { +addHook({ name: 'express', versions: ['>=4.0.0 <5.0.0'] }, express => { shimmer.wrap(express.application, 'handle', wrapHandle) shimmer.wrap(express.Router, 'use', wrapRouterMethod) shimmer.wrap(express.Router, 'route', wrapRouterMethod) @@ -104,7 +104,7 @@ function publishQueryParsedAndNext (req, res, next) { addHook({ name: 'express', - versions: ['>=4 <5.0.0'], + versions: ['>=4.0.0 <5.0.0'], file: 'lib/middleware/query.js' }, query => { return shimmer.wrapFunction(query, query => function () { diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 44b275ad10d..c272b58cd50 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -179,10 +179,9 @@ addHook({ name: 'router', versions: ['>=1'] }, Router => { return Router }) -function createWrapLayerMethod () { - // TODO: create a dedicate channel for this - const processParamsStartCh = channel('datadog:express:process_params:start') +const processParamsStartCh = channel('datadog:express:process_params:start') +function createWrapLayerMethod () { return function wrapMethod (original) { return function wrappedHandleRequest (req, res, next) { if (processParamsStartCh.hasSubscribers) { @@ -191,8 +190,8 @@ function createWrapLayerMethod () { processParamsStartCh.publish({ req, res, - abortController, - params: req?.params + params: req?.params, + abortController }) if (abortController.signal.aborted) return } @@ -209,4 +208,34 @@ addHook({ return Layer }) +function wrapProcessParamsMethod (original) { + return function wrappedProcessParams (name, fn) { + const wrappedFn = function wrappedParamCallback (req, res, next, param) { + if (processParamsStartCh.hasSubscribers) { + const abortController = new AbortController() + + processParamsStartCh.publish({ + req, + res, + params: req?.params, + abortController + }) + + if (abortController.signal.aborted) return + } + + return fn(req, res, next, param, name) + } + + return original.call(this, name, wrappedFn) + } +} + +addHook({ + name: 'router', versions: ['>=2'] +}, router => { + shimmer.wrap(router.prototype, 'param', wrapProcessParamsMethod) + return router +}) + module.exports = { createWrapRouterMethod } diff --git a/packages/dd-trace/src/appsec/channels.js b/packages/dd-trace/src/appsec/channels.js index 8e7f27211c6..1368e937dc9 100644 --- a/packages/dd-trace/src/appsec/channels.js +++ b/packages/dd-trace/src/appsec/channels.js @@ -19,6 +19,7 @@ module.exports = { nextBodyParsed: dc.channel('apm:next:body-parsed'), nextQueryParsed: dc.channel('apm:next:query-parsed'), expressProcessParams: dc.channel('datadog:express:process_params:start'), + routerParam: dc.channel('datadog:router:param:start'), responseBody: dc.channel('datadog:express:response:json:start'), responseWriteHead: dc.channel('apm:http:server:response:writeHead:start'), httpClientRequestStart: dc.channel('apm:http:client:request:start'), diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js index 71aba6101eb..025234c3c56 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js @@ -78,6 +78,15 @@ class TaintTrackingPlugin extends SourceIastPlugin { } ) + this.addSub( + { channelName: 'datadog:router:param:start', tag: HTTP_REQUEST_PATH_PARAM }, + ({ req }) => { + if (req && req.params !== null && typeof req.params === 'object') { + this._taintTrackingHandler(HTTP_REQUEST_PATH_PARAM, req, 'params') + } + } + ) + this.addSub( { channelName: 'datadog:query:read:finish', tag: HTTP_REQUEST_QUERY }, ({ query }) => { diff --git a/packages/dd-trace/src/appsec/index.js b/packages/dd-trace/src/appsec/index.js index d195c7db23d..d4f4adc6554 100644 --- a/packages/dd-trace/src/appsec/index.js +++ b/packages/dd-trace/src/appsec/index.js @@ -16,7 +16,8 @@ const { expressProcessParams, responseBody, responseWriteHead, - responseSetHeader + responseSetHeader, + routerParam } = require('./channels') const waf = require('./waf') const addresses = require('./addresses') @@ -67,6 +68,7 @@ function enable (_config) { nextBodyParsed.subscribe(onRequestBodyParsed) nextQueryParsed.subscribe(onRequestQueryParsed) expressProcessParams.subscribe(onRequestProcessParams) + routerParam.subscribe(onRequestProcessParams) responseBody.subscribe(onResponseBody) responseWriteHead.subscribe(onResponseWriteHead) responseSetHeader.subscribe(onResponseSetHeader) @@ -311,6 +313,7 @@ function disable () { if (nextBodyParsed.hasSubscribers) nextBodyParsed.unsubscribe(onRequestBodyParsed) if (nextQueryParsed.hasSubscribers) nextQueryParsed.unsubscribe(onRequestQueryParsed) if (expressProcessParams.hasSubscribers) expressProcessParams.unsubscribe(onRequestProcessParams) + if (routerParam.hasSubscribers) routerParam.unsubscribe(onRequestProcessParams) if (responseBody.hasSubscribers) responseBody.unsubscribe(onResponseBody) if (responseWriteHead.hasSubscribers) responseWriteHead.unsubscribe(onResponseWriteHead) if (responseSetHeader.hasSubscribers) responseSetHeader.unsubscribe(onResponseSetHeader) diff --git a/packages/dd-trace/src/appsec/rasp/utils.js b/packages/dd-trace/src/appsec/rasp/utils.js index e6aa96aca81..a454a71b8c6 100644 --- a/packages/dd-trace/src/appsec/rasp/utils.js +++ b/packages/dd-trace/src/appsec/rasp/utils.js @@ -46,8 +46,7 @@ function handleResult (actions, req, res, abortController, config) { if (blockingAction) { const rootSpan = web.root(req) // Should block only in express - const name = rootSpan?.context()._name - if (name === 'express.request') { + if (rootSpan?.context()._name === 'express.request') { const abortError = new DatadogRaspAbortError(req, res, blockingAction) abortController.abort(abortError) diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js index 1a21b0a5b08..14cc58e6355 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js @@ -16,6 +16,7 @@ const queryParseFinishChannel = dc.channel('datadog:qs:parse:finish') const bodyParserFinishChannel = dc.channel('datadog:body-parser:read:finish') const cookieParseFinishCh = dc.channel('datadog:cookie:parse:finish') const processParamsStartCh = dc.channel('datadog:express:process_params:start') +const routerParamStartCh = dc.channel('datadog:router:param:start') describe('IAST Taint tracking plugin', () => { let taintTrackingPlugin @@ -42,16 +43,18 @@ describe('IAST Taint tracking plugin', () => { }) it('Should subscribe to body parser, qs, cookie and process_params channel', () => { - expect(taintTrackingPlugin._subscriptions).to.have.lengthOf(9) + expect(taintTrackingPlugin._subscriptions).to.have.lengthOf(11) expect(taintTrackingPlugin._subscriptions[0]._channel.name).to.equals('datadog:body-parser:read:finish') expect(taintTrackingPlugin._subscriptions[1]._channel.name).to.equals('datadog:multer:read:finish') expect(taintTrackingPlugin._subscriptions[2]._channel.name).to.equals('datadog:qs:parse:finish') expect(taintTrackingPlugin._subscriptions[3]._channel.name).to.equals('apm:express:middleware:next') expect(taintTrackingPlugin._subscriptions[4]._channel.name).to.equals('datadog:cookie:parse:finish') expect(taintTrackingPlugin._subscriptions[5]._channel.name).to.equals('datadog:express:process_params:start') - expect(taintTrackingPlugin._subscriptions[6]._channel.name).to.equals('apm:graphql:resolve:start') - expect(taintTrackingPlugin._subscriptions[7]._channel.name).to.equals('datadog:url:parse:finish') - expect(taintTrackingPlugin._subscriptions[8]._channel.name).to.equals('datadog:url:getter:finish') + expect(taintTrackingPlugin._subscriptions[6]._channel.name).to.equals('datadog:router:param:start') + expect(taintTrackingPlugin._subscriptions[7]._channel.name).to.equals('datadog:query:read:finish') + expect(taintTrackingPlugin._subscriptions[8]._channel.name).to.equals('apm:graphql:resolve:start') + expect(taintTrackingPlugin._subscriptions[9]._channel.name).to.equals('datadog:url:parse:finish') + expect(taintTrackingPlugin._subscriptions[10]._channel.name).to.equals('datadog:url:getter:finish') }) describe('taint sources', () => { @@ -209,7 +212,7 @@ describe('IAST Taint tracking plugin', () => { ) }) - it('Should taint request params when process params event is published', () => { + it('Should taint request params when process params event is published with processParamsStartCh', () => { const req = { params: { parameter1: 'tainted1' @@ -224,6 +227,21 @@ describe('IAST Taint tracking plugin', () => { ) }) + it('Should taint request params when process params event is published with routerParamStartCh', () => { + const req = { + params: { + parameter1: 'tainted1' + } + } + + routerParamStartCh.publish({ req }) + expect(taintTrackingOperations.taintObject).to.be.calledOnceWith( + iastContext, + req.params, + HTTP_REQUEST_PATH_PARAM + ) + }) + it('Should not taint request params when process params event is published with non params request', () => { const req = {} diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index a2f7b935aee..ce348382a2f 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -80,11 +80,11 @@ describe('Path params sourcing with express', () => { let appListener withVersions('express', 'express', version => { - const checkParamIsTaintedAndNext = (req, res, next, param) => { + const checkParamIsTaintedAndNext = (req, res, next, param, name) => { const store = storage.getStore() const iastContext = iastContextFunctions.getIastContext(store) - const pathParamValue = param + const pathParamValue = name ? req.params[name] : req.params const isParameterTainted = isTainted(iastContext, pathParamValue) expect(isParameterTainted).to.be.true const taintedParameterValueRanges = getRanges(iastContext, pathParamValue) @@ -185,8 +185,7 @@ describe('Path params sourcing with express', () => { }) }) - // to be fixed - it.skip('should taint path param on router.params callback', function (done) { + it('should taint path param on router.params callback', function (done) { const app = express() app.use('/:parameter1/:parameter2', (req, res) => { @@ -206,20 +205,15 @@ describe('Path params sourcing with express', () => { }) }) - // to be fixed - it.skip('should taint path param on router.params callback with custom implementation', function (done) { + it('should taint path param on router.params callback with custom implementation', function (done) { const app = express() app.use('/:parameter1/:parameter2', (req, res) => { res.status(200).send() }) - app.param((param, option) => { - return checkParamIsTaintedAndNext - }) - - app.param('parameter1') - app.param('parameter2') + app.param('parameter1', checkParamIsTaintedAndNext) + app.param('parameter2', checkParamIsTaintedAndNext) appListener = app.listen(0, 'localhost', () => { const port = appListener.address().port diff --git a/packages/dd-trace/test/appsec/index.express.plugin.spec.js b/packages/dd-trace/test/appsec/index.express.plugin.spec.js index 2fd88896388..6ce418b6bbb 100644 --- a/packages/dd-trace/test/appsec/index.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.express.plugin.spec.js @@ -46,8 +46,6 @@ withVersions('express', 'express', version => { app.param('callbackedParameter', paramCallbackSpy) - // app.param('callbackedParameter') - getPort().then((port) => { server = app.listen(port, () => { axios = Axios.create({ baseURL: `http://localhost:${port}` }) @@ -159,7 +157,7 @@ withVersions('express', 'express', version => { }) }) - describe.skip('path parameter callback', () => { + describe('path parameter callback', () => { it('should not block the request when attack is not detected', async () => { const res = await axios.get('/callback-path-param/safe_param') assert.equal(res.status, 200) @@ -182,7 +180,7 @@ withVersions('express', 'express', version => { }) }) - describe('Suspicious request blocking - query', () => { + describe.skip('Suspicious request blocking - query', () => { let server, requestBody, axios before(() => { diff --git a/packages/dd-trace/test/appsec/index.spec.js b/packages/dd-trace/test/appsec/index.spec.js index 26a1c709cd9..4ec92f7b0e6 100644 --- a/packages/dd-trace/test/appsec/index.spec.js +++ b/packages/dd-trace/test/appsec/index.spec.js @@ -15,6 +15,7 @@ const { nextBodyParsed, nextQueryParsed, expressProcessParams, + routerParam, responseBody, responseWriteHead, responseSetHeader @@ -178,6 +179,7 @@ describe('AppSec Index', function () { expect(nextBodyParsed.hasSubscribers).to.be.false expect(nextQueryParsed.hasSubscribers).to.be.false expect(expressProcessParams.hasSubscribers).to.be.false + expect(routerParam.hasSubscribers).to.be.false expect(responseWriteHead.hasSubscribers).to.be.false expect(responseSetHeader.hasSubscribers).to.be.false @@ -190,6 +192,7 @@ describe('AppSec Index', function () { expect(nextBodyParsed.hasSubscribers).to.be.true expect(nextQueryParsed.hasSubscribers).to.be.true expect(expressProcessParams.hasSubscribers).to.be.true + expect(routerParam.hasSubscribers).to.be.true expect(responseWriteHead.hasSubscribers).to.be.true expect(responseSetHeader.hasSubscribers).to.be.true }) @@ -271,6 +274,7 @@ describe('AppSec Index', function () { expect(nextBodyParsed.hasSubscribers).to.be.false expect(nextQueryParsed.hasSubscribers).to.be.false expect(expressProcessParams.hasSubscribers).to.be.false + expect(routerParam.hasSubscribers).to.be.false expect(responseWriteHead.hasSubscribers).to.be.false expect(responseSetHeader.hasSubscribers).to.be.false }) From 864324c4045bc23c2d26457cdb60618655e46042 Mon Sep 17 00:00:00 2001 From: ishabi Date: Fri, 22 Nov 2024 15:07:59 +0100 Subject: [PATCH 11/31] fix express tests and add req.query middleware --- .../datadog-instrumentations/src/router.js | 27 +- .../test/body-parser.spec.js | 136 ++++----- .../test/cookie-parser.spec.js | 104 +++---- .../test/express-mongo-sanitize.spec.js | 284 +++++++++--------- .../test/express.spec.js | 2 +- .../test/multer.spec.js | 162 +++++----- .../test/passport-http.spec.js | 228 +++++++------- .../test/passport-local.spec.js | 208 ++++++------- 8 files changed, 589 insertions(+), 562 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index c272b58cd50..b1ec2574ce1 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -27,9 +27,6 @@ function createWrapRouterMethod (name) { const req = arguments[arguments.length > 3 ? 1 : 0] const next = arguments[lastIndex] - // explicitly call getter for express5 - req.query - if (typeof next === 'function') { arguments[lastIndex] = wrapNext(req, next) } @@ -173,10 +170,28 @@ function createWrapRouterMethod (name) { const wrapRouterMethod = createWrapRouterMethod('router') addHook({ name: 'router', versions: ['>=1'] }, Router => { - shimmer.wrap(Router.prototype, 'use', wrapRouterMethod) - shimmer.wrap(Router.prototype, 'route', wrapRouterMethod) + const originalRouter = Router + + function WrappedRouter (options) { + const router = originalRouter.call(this, options) + + // Add query parsing middleware + if (!router._queryMiddlewareAdded) { + router._queryMiddlewareAdded = true + router.use(function queryParsingMiddleware (req, res, next) { + req.query + next() + }) + } + + return router + } + WrappedRouter.prototype = originalRouter.prototype + + shimmer.wrap(WrappedRouter.prototype, 'use', wrapRouterMethod) + shimmer.wrap(WrappedRouter.prototype, 'route', wrapRouterMethod) - return Router + return WrappedRouter }) const processParamsStartCh = channel('datadog:express:process_params:start') diff --git a/packages/datadog-instrumentations/test/body-parser.spec.js b/packages/datadog-instrumentations/test/body-parser.spec.js index 482ba5e772d..b116d6be751 100644 --- a/packages/datadog-instrumentations/test/body-parser.spec.js +++ b/packages/datadog-instrumentations/test/body-parser.spec.js @@ -5,93 +5,95 @@ const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') const { storage } = require('../../datadog-core') -withVersions('body-parser', 'body-parser', version => { - describe('body parser instrumentation', () => { - const bodyParserReadCh = dc.channel('datadog:body-parser:read:finish') - let port, server, middlewareProcessBodyStub - - before(() => { - return agent.load(['http', 'express', 'body-parser'], { client: false }) - }) - - before((done) => { - const express = require('../../../versions/express').get() - const bodyParser = require(`../../../versions/body-parser@${version}`).get() - const app = express() - app.use(bodyParser.json()) - app.post('/', (req, res) => { - middlewareProcessBodyStub() - res.end('DONE') +withVersions('express', 'express', expressVersion => { + withVersions('body-parser', 'body-parser', version => { + describe('body parser instrumentation', () => { + const bodyParserReadCh = dc.channel('datadog:body-parser:read:finish') + let port, server, middlewareProcessBodyStub + + before(() => { + return agent.load(['http', 'express', 'body-parser'], { client: false }) }) - server = app.listen(0, () => { - port = server.address().port - done() + + before((done) => { + const express = require(`../../../versions/express@${expressVersion}`).get() + const bodyParser = require(`../../../versions/body-parser@${version}`).get() + const app = express() + app.use(bodyParser.json()) + app.post('/', (req, res) => { + middlewareProcessBodyStub() + res.end('DONE') + }) + server = app.listen(0, () => { + port = server.address().port + done() + }) }) - }) - beforeEach(async () => { - middlewareProcessBodyStub = sinon.stub() - }) + beforeEach(async () => { + middlewareProcessBodyStub = sinon.stub() + }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - it('should not abort the request by default', async () => { - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + it('should not abort the request by default', async () => { + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - expect(middlewareProcessBodyStub).to.be.calledOnce - expect(res.data).to.be.equal('DONE') - }) + expect(middlewareProcessBodyStub).to.be.calledOnce + expect(res.data).to.be.equal('DONE') + }) - it('should not abort the request with non blocker subscription', async () => { - function noop () {} - bodyParserReadCh.subscribe(noop) + it('should not abort the request with non blocker subscription', async () => { + function noop () {} + bodyParserReadCh.subscribe(noop) - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - expect(middlewareProcessBodyStub).to.be.calledOnce - expect(res.data).to.be.equal('DONE') + expect(middlewareProcessBodyStub).to.be.calledOnce + expect(res.data).to.be.equal('DONE') - bodyParserReadCh.unsubscribe(noop) - }) + bodyParserReadCh.unsubscribe(noop) + }) - it('should abort the request when abortController.abort() is called', async () => { - function blockRequest ({ res, abortController }) { - res.end('BLOCKED') - abortController.abort() - } - bodyParserReadCh.subscribe(blockRequest) + it('should abort the request when abortController.abort() is called', async () => { + function blockRequest ({ res, abortController }) { + res.end('BLOCKED') + abortController.abort() + } + bodyParserReadCh.subscribe(blockRequest) - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - expect(middlewareProcessBodyStub).not.to.be.called - expect(res.data).to.be.equal('BLOCKED') + expect(middlewareProcessBodyStub).not.to.be.called + expect(res.data).to.be.equal('BLOCKED') - bodyParserReadCh.unsubscribe(blockRequest) - }) + bodyParserReadCh.unsubscribe(blockRequest) + }) - it('should not lose the http async context', async () => { - let store - let payload + it('should not lose the http async context', async () => { + let store + let payload - function handler (data) { - store = storage.getStore() - payload = data - } - bodyParserReadCh.subscribe(handler) + function handler (data) { + store = storage.getStore() + payload = data + } + bodyParserReadCh.subscribe(handler) - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - expect(store).to.have.property('req', payload.req) - expect(store).to.have.property('res', payload.res) - expect(store).to.have.property('span') + expect(store).to.have.property('req', payload.req) + expect(store).to.have.property('res', payload.res) + expect(store).to.have.property('span') - expect(middlewareProcessBodyStub).to.be.calledOnce - expect(res.data).to.be.equal('DONE') + expect(middlewareProcessBodyStub).to.be.calledOnce + expect(res.data).to.be.equal('DONE') - bodyParserReadCh.unsubscribe(handler) + bodyParserReadCh.unsubscribe(handler) + }) }) }) }) diff --git a/packages/datadog-instrumentations/test/cookie-parser.spec.js b/packages/datadog-instrumentations/test/cookie-parser.spec.js index 799d434dd05..1e4a4277f48 100644 --- a/packages/datadog-instrumentations/test/cookie-parser.spec.js +++ b/packages/datadog-instrumentations/test/cookie-parser.spec.js @@ -5,71 +5,73 @@ const dc = require('dc-polyfill') const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') -withVersions('cookie-parser', 'cookie-parser', version => { - describe('cookie parser instrumentation', () => { - const cookieParserReadCh = dc.channel('datadog:cookie-parser:read:finish') - let port, server, middlewareProcessCookieStub - - before(() => { - return agent.load(['express', 'cookie-parser'], { client: false }) - }) - - before((done) => { - const express = require('../../../versions/express').get() - const cookieParser = require(`../../../versions/cookie-parser@${version}`).get() - const app = express() - app.use(cookieParser()) - app.post('/', (req, res) => { - middlewareProcessCookieStub() - res.end('DONE') +withVersions('express', 'express', expressVersion => { + withVersions('cookie-parser', 'cookie-parser', version => { + describe('cookie parser instrumentation', () => { + const cookieParserReadCh = dc.channel('datadog:cookie-parser:read:finish') + let port, server, middlewareProcessCookieStub + + before(() => { + return agent.load(['express', 'cookie-parser'], { client: false }) }) - server = app.listen(0, () => { - port = server.address().port - done() + + before((done) => { + const express = require(`../../../versions/express@${expressVersion}`).get() + const cookieParser = require(`../../../versions/cookie-parser@${version}`).get() + const app = express() + app.use(cookieParser()) + app.post('/', (req, res) => { + middlewareProcessCookieStub() + res.end('DONE') + }) + server = app.listen(0, () => { + port = server.address().port + done() + }) }) - }) - beforeEach(async () => { - middlewareProcessCookieStub = sinon.stub() - }) + beforeEach(async () => { + middlewareProcessCookieStub = sinon.stub() + }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - it('should not abort the request by default', async () => { - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + it('should not abort the request by default', async () => { + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - sinon.assert.calledOnce(middlewareProcessCookieStub) - assert.equal(res.data, 'DONE') - }) + sinon.assert.calledOnce(middlewareProcessCookieStub) + assert.equal(res.data, 'DONE') + }) - it('should not abort the request with non blocker subscription', async () => { - function noop () {} - cookieParserReadCh.subscribe(noop) + it('should not abort the request with non blocker subscription', async () => { + function noop () {} + cookieParserReadCh.subscribe(noop) - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - sinon.assert.calledOnce(middlewareProcessCookieStub) - assert.equal(res.data, 'DONE') + sinon.assert.calledOnce(middlewareProcessCookieStub) + assert.equal(res.data, 'DONE') - cookieParserReadCh.unsubscribe(noop) - }) + cookieParserReadCh.unsubscribe(noop) + }) - it('should abort the request when abortController.abort() is called', async () => { - function blockRequest ({ res, abortController }) { - res.end('BLOCKED') - abortController.abort() - } - cookieParserReadCh.subscribe(blockRequest) + it('should abort the request when abortController.abort() is called', async () => { + function blockRequest ({ res, abortController }) { + res.end('BLOCKED') + abortController.abort() + } + cookieParserReadCh.subscribe(blockRequest) - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - sinon.assert.notCalled(middlewareProcessCookieStub) - assert.equal(res.data, 'BLOCKED') + sinon.assert.notCalled(middlewareProcessCookieStub) + assert.equal(res.data, 'BLOCKED') - cookieParserReadCh.unsubscribe(blockRequest) + cookieParserReadCh.unsubscribe(blockRequest) + }) }) }) }) diff --git a/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js b/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js index 3fcf981e528..9252f1c25d7 100644 --- a/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js +++ b/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js @@ -4,202 +4,204 @@ const agent = require('../../dd-trace/test/plugins/agent') const { channel } = require('dc-polyfill') const axios = require('axios') describe('express-mongo-sanitize', () => { - withVersions('express-mongo-sanitize', 'express-mongo-sanitize', version => { - describe('middleware', () => { - const sanitizeMiddlewareFinished = channel('datadog:express-mongo-sanitize:filter:finish') - let port, server, requestBody + withVersions('express', 'express', expressVersion => { + withVersions('express-mongo-sanitize', 'express-mongo-sanitize', version => { + describe('middleware', () => { + const sanitizeMiddlewareFinished = channel('datadog:express-mongo-sanitize:filter:finish') + let port, server, requestBody - before(() => { - return agent.load(['express', 'express-mongo-sanitize'], { client: false }) - }) + before(() => { + return agent.load(['express', 'express-mongo-sanitize'], { client: false }) + }) - before((done) => { - const express = require('../../../versions/express').get() - const expressMongoSanitize = require(`../../../versions/express-mongo-sanitize@${version}`).get() - const app = express() + before((done) => { + const express = require(`../../../versions/express@${expressVersion}`).get() + const expressMongoSanitize = require(`../../../versions/express-mongo-sanitize@${version}`).get() + const app = express() - app.use(expressMongoSanitize()) - app.all('/', (req, res) => { - requestBody(req, res) - res.end() - }) + app.use(expressMongoSanitize()) + app.all('/', (req, res) => { + requestBody(req, res) + res.end() + }) - server = app.listen(0, () => { - port = server.address().port - done() + server = app.listen(0, () => { + port = server.address().port + done() + }) }) - }) - beforeEach(() => { - requestBody = sinon.stub() - }) + beforeEach(() => { + requestBody = sinon.stub() + }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - describe('without subscriptions', () => { - it('it continues working without sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.false + describe('without subscriptions', () => { + it('it continues working without sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.false - await axios.get(`http://localhost:${port}/?param=paramvalue`) + await axios.get(`http://localhost:${port}/?param=paramvalue`) - expect(requestBody).to.be.calledOnce - expect(requestBody.firstCall.args[0].query.param).to.be.equal('paramvalue') - }) + expect(requestBody).to.be.calledOnce + expect(requestBody.firstCall.args[0].query.param).to.be.equal('paramvalue') + }) - it('it continues working with sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.false + it('it continues working with sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.false - await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) + await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) - expect(requestBody).to.be.calledOnce - expect(requestBody.firstCall.args[0].query.param.$eq).to.be.undefined + expect(requestBody).to.be.calledOnce + expect(requestBody.firstCall.args[0].query.param.$eq).to.be.undefined + }) }) - }) - describe('with subscriptions', () => { - let subscription + describe('with subscriptions', () => { + let subscription - beforeEach(() => { - subscription = sinon.stub() - sanitizeMiddlewareFinished.subscribe(subscription) - }) + beforeEach(() => { + subscription = sinon.stub() + sanitizeMiddlewareFinished.subscribe(subscription) + }) - afterEach(() => { - sanitizeMiddlewareFinished.unsubscribe(subscription) - }) + afterEach(() => { + sanitizeMiddlewareFinished.unsubscribe(subscription) + }) - it('it continues working without sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true + it('it continues working without sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true - await axios.get(`http://localhost:${port}/?param=paramvalue`) + await axios.get(`http://localhost:${port}/?param=paramvalue`) - expect(requestBody).to.be.calledOnce - expect(requestBody.firstCall.args[0].query.param).to.be.equal('paramvalue') - }) + expect(requestBody).to.be.calledOnce + expect(requestBody.firstCall.args[0].query.param).to.be.equal('paramvalue') + }) - it('it continues working with sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true + it('it continues working with sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true - await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) + await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) - expect(requestBody).to.be.calledOnce - expect(requestBody.firstCall.args[0].query.param.$eq).to.be.undefined - }) + expect(requestBody).to.be.calledOnce + expect(requestBody.firstCall.args[0].query.param.$eq).to.be.undefined + }) - it('subscription is called with expected parameters without sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true + it('subscription is called with expected parameters without sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true - await axios.get(`http://localhost:${port}/?param=paramvalue`) + await axios.get(`http://localhost:${port}/?param=paramvalue`) - expect(subscription).to.be.calledOnce - expect(subscription.firstCall.args[0].sanitizedProperties) - .to.be.deep.equal(['body', 'params', 'headers', 'query']) - expect(subscription.firstCall.args[0].req.query.param).to.be.equal('paramvalue') - }) + expect(subscription).to.be.calledOnce + expect(subscription.firstCall.args[0].sanitizedProperties) + .to.be.deep.equal(['body', 'params', 'headers', 'query']) + expect(subscription.firstCall.args[0].req.query.param).to.be.equal('paramvalue') + }) - it('subscription is called with expected parameters with sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true + it('subscription is called with expected parameters with sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true - await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) + await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) - expect(subscription).to.be.calledOnce - expect(subscription.firstCall.args[0].sanitizedProperties) - .to.be.deep.equal(['body', 'params', 'headers', 'query']) - expect(subscription.firstCall.args[0].req.query.param.$eq).to.be.undefined + expect(subscription).to.be.calledOnce + expect(subscription.firstCall.args[0].sanitizedProperties) + .to.be.deep.equal(['body', 'params', 'headers', 'query']) + expect(subscription.firstCall.args[0].req.query.param.$eq).to.be.undefined + }) }) }) - }) - describe('sanitize method', () => { - const sanitizeFinished = channel('datadog:express-mongo-sanitize:sanitize:finish') - let expressMongoSanitize + describe('sanitize method', () => { + const sanitizeFinished = channel('datadog:express-mongo-sanitize:sanitize:finish') + let expressMongoSanitize - before(() => { - return agent.load(['express-mongo-sanitize'], { client: false }) - }) + before(() => { + return agent.load(['express-mongo-sanitize'], { client: false }) + }) - before(() => { - expressMongoSanitize = require(`../../../versions/express-mongo-sanitize@${version}`).get() - }) + before(() => { + expressMongoSanitize = require(`../../../versions/express-mongo-sanitize@${version}`).get() + }) - after(() => { - return agent.close({ ritmReset: false }) - }) + after(() => { + return agent.close({ ritmReset: false }) + }) - describe('without subscriptions', () => { - it('it works as expected without modifications', () => { - expect(sanitizeFinished.hasSubscribers).to.be.false + describe('without subscriptions', () => { + it('it works as expected without modifications', () => { + expect(sanitizeFinished.hasSubscribers).to.be.false - const objectToSanitize = { - safeKey: 'safeValue' - } + const objectToSanitize = { + safeKey: 'safeValue' + } - const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) + const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) - expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) - }) + expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) + }) - it('it works as expected with modifications', () => { - expect(sanitizeFinished.hasSubscribers).to.be.false + it('it works as expected with modifications', () => { + expect(sanitizeFinished.hasSubscribers).to.be.false - const objectToSanitize = { - unsafeKey: { - $ne: 'test' - }, - safeKey: 'safeValue' - } + const objectToSanitize = { + unsafeKey: { + $ne: 'test' + }, + safeKey: 'safeValue' + } - const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) + const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) - expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) - expect(sanitizedObject.unsafeKey.$ne).to.be.undefined + expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) + expect(sanitizedObject.unsafeKey.$ne).to.be.undefined + }) }) - }) - describe('with subscriptions', () => { - let subscription + describe('with subscriptions', () => { + let subscription - beforeEach(() => { - subscription = sinon.stub() - sanitizeFinished.subscribe(subscription) - }) + beforeEach(() => { + subscription = sinon.stub() + sanitizeFinished.subscribe(subscription) + }) - afterEach(() => { - sanitizeFinished.unsubscribe(subscription) - subscription = undefined - }) + afterEach(() => { + sanitizeFinished.unsubscribe(subscription) + subscription = undefined + }) - it('it works as expected without modifications', () => { - expect(sanitizeFinished.hasSubscribers).to.be.true + it('it works as expected without modifications', () => { + expect(sanitizeFinished.hasSubscribers).to.be.true - const objectToSanitize = { - safeKey: 'safeValue' - } + const objectToSanitize = { + safeKey: 'safeValue' + } - const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) + const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) - expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) - expect(subscription).to.be.calledOnceWith({ sanitizedObject }) - }) + expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) + expect(subscription).to.be.calledOnceWith({ sanitizedObject }) + }) - it('it works as expected with modifications', () => { - expect(sanitizeFinished.hasSubscribers).to.be.true + it('it works as expected with modifications', () => { + expect(sanitizeFinished.hasSubscribers).to.be.true - const objectToSanitize = { - unsafeKey: { - $ne: 'test' - }, - safeKey: 'safeValue' - } + const objectToSanitize = { + unsafeKey: { + $ne: 'test' + }, + safeKey: 'safeValue' + } - const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) + const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) - expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) - expect(sanitizedObject.unsafeKey.$ne).to.be.undefined - expect(subscription).to.be.calledOnceWith({ sanitizedObject }) + expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) + expect(sanitizedObject.unsafeKey.$ne).to.be.undefined + expect(subscription).to.be.calledOnceWith({ sanitizedObject }) + }) }) }) }) diff --git a/packages/datadog-instrumentations/test/express.spec.js b/packages/datadog-instrumentations/test/express.spec.js index d21b9be3e0a..534bfd041e8 100644 --- a/packages/datadog-instrumentations/test/express.spec.js +++ b/packages/datadog-instrumentations/test/express.spec.js @@ -14,7 +14,7 @@ withVersions('express', 'express', version => { }) before((done) => { - const express = require('../../../versions/express').get() + const express = require(`../../../versions/express@${version}`).get() const app = express() app.get('/', (req, res) => { requestBody() diff --git a/packages/datadog-instrumentations/test/multer.spec.js b/packages/datadog-instrumentations/test/multer.spec.js index f7edcee6cd3..70c8c35b6e4 100644 --- a/packages/datadog-instrumentations/test/multer.spec.js +++ b/packages/datadog-instrumentations/test/multer.spec.js @@ -5,104 +5,106 @@ const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') const { storage } = require('../../datadog-core') -withVersions('multer', 'multer', version => { - describe('multer parser instrumentation', () => { - const multerReadCh = dc.channel('datadog:multer:read:finish') - let port, server, middlewareProcessBodyStub, formData - - before(() => { - return agent.load(['http', 'express', 'multer'], { client: false }) - }) - - before((done) => { - const express = require('../../../versions/express').get() - const multer = require(`../../../versions/multer@${version}`).get() - const uploadToMemory = multer({ storage: multer.memoryStorage(), limits: { fileSize: 200000 } }) - - const app = express() - - app.post('/', uploadToMemory.single('file'), (req, res) => { - middlewareProcessBodyStub(req.body.key) - res.end('DONE') - }) - server = app.listen(0, () => { - port = server.address().port - done() +withVersions('express', 'express', expressVersion => { + withVersions('multer', 'multer', version => { + describe('multer parser instrumentation', () => { + const multerReadCh = dc.channel('datadog:multer:read:finish') + let port, server, middlewareProcessBodyStub, formData + + before(() => { + return agent.load(['http', 'express', 'multer'], { client: false }) }) - }) - beforeEach(async () => { - middlewareProcessBodyStub = sinon.stub() - - formData = new FormData() - formData.append('key', 'value') - }) - - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + before((done) => { + const express = require(`../../../versions/express@${expressVersion}`).get() + const multer = require(`../../../versions/multer@${version}`).get() + const uploadToMemory = multer({ storage: multer.memoryStorage(), limits: { fileSize: 200000 } }) + + const app = express() + + app.post('/', uploadToMemory.single('file'), (req, res) => { + middlewareProcessBodyStub(req.body.key) + res.end('DONE') + }) + server = app.listen(0, () => { + port = server.address().port + done() + }) + }) - it('should not abort the request by default', async () => { - const res = await axios.post(`http://localhost:${port}/`, formData) + beforeEach(async () => { + middlewareProcessBodyStub = sinon.stub() - expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) - expect(res.data).to.be.equal('DONE') - }) + formData = new FormData() + formData.append('key', 'value') + }) - it('should not abort the request with non blocker subscription', async () => { - function noop () {} - multerReadCh.subscribe(noop) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - try { + it('should not abort the request by default', async () => { const res = await axios.post(`http://localhost:${port}/`, formData) expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) expect(res.data).to.be.equal('DONE') - } finally { - multerReadCh.unsubscribe(noop) - } - }) + }) - it('should abort the request when abortController.abort() is called', async () => { - function blockRequest ({ res, abortController }) { - res.end('BLOCKED') - abortController.abort() - } - multerReadCh.subscribe(blockRequest) + it('should not abort the request with non blocker subscription', async () => { + function noop () {} + multerReadCh.subscribe(noop) - try { - const res = await axios.post(`http://localhost:${port}/`, formData) + try { + const res = await axios.post(`http://localhost:${port}/`, formData) - expect(middlewareProcessBodyStub).not.to.be.called - expect(res.data).to.be.equal('BLOCKED') - } finally { - multerReadCh.unsubscribe(blockRequest) - } - }) + expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) + expect(res.data).to.be.equal('DONE') + } finally { + multerReadCh.unsubscribe(noop) + } + }) - it('should not lose the http async context', async () => { - let store - let payload + it('should abort the request when abortController.abort() is called', async () => { + function blockRequest ({ res, abortController }) { + res.end('BLOCKED') + abortController.abort() + } + multerReadCh.subscribe(blockRequest) + + try { + const res = await axios.post(`http://localhost:${port}/`, formData) + + expect(middlewareProcessBodyStub).not.to.be.called + expect(res.data).to.be.equal('BLOCKED') + } finally { + multerReadCh.unsubscribe(blockRequest) + } + }) - function handler (data) { - store = storage.getStore() - payload = data - } - multerReadCh.subscribe(handler) + it('should not lose the http async context', async () => { + let store + let payload - try { - const res = await axios.post(`http://localhost:${port}/`, formData) + function handler (data) { + store = storage.getStore() + payload = data + } + multerReadCh.subscribe(handler) - expect(store).to.have.property('req', payload.req) - expect(store).to.have.property('res', payload.res) - expect(store).to.have.property('span') + try { + const res = await axios.post(`http://localhost:${port}/`, formData) - expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) - expect(res.data).to.be.equal('DONE') - } finally { - multerReadCh.unsubscribe(handler) - } + expect(store).to.have.property('req', payload.req) + expect(store).to.have.property('res', payload.res) + expect(store).to.have.property('span') + + expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) + expect(res.data).to.be.equal('DONE') + } finally { + multerReadCh.unsubscribe(handler) + } + }) }) }) }) diff --git a/packages/datadog-instrumentations/test/passport-http.spec.js b/packages/datadog-instrumentations/test/passport-http.spec.js index 2918c935e20..dc513f5ccbc 100644 --- a/packages/datadog-instrumentations/test/passport-http.spec.js +++ b/packages/datadog-instrumentations/test/passport-http.spec.js @@ -4,139 +4,141 @@ const agent = require('../../dd-trace/test/plugins/agent') const axios = require('axios') const dc = require('dc-polyfill') -withVersions('passport-http', 'passport-http', version => { - describe('passport-http instrumentation', () => { - const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') - let port, server, subscriberStub - - before(() => { - return agent.load(['express', 'passport', 'passport-http'], { client: false }) - }) +withVersions('express', 'express', '>=4.21.1', expressVersion => { + withVersions('passport-http', 'passport-http', version => { + describe('passport-http instrumentation', () => { + const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') + let port, server, subscriberStub + + before(() => { + return agent.load(['express', 'passport', 'passport-http'], { client: false }) + }) - before((done) => { - const express = require('../../../versions/express').get() - const passport = require('../../../versions/passport').get() - const BasicStrategy = require(`../../../versions/passport-http@${version}`).get().BasicStrategy - const app = express() - - passport.use(new BasicStrategy((username, password, done) => { - const users = [{ - _id: 1, - username: 'test', - password: '1234', - email: 'testuser@ddog.com' - }] - - const user = users.find(user => (user.username === username) && (user.password === password)) - - if (!user) { - return done(null, false) - } else { - return done(null, user) + before((done) => { + const express = require(`../../../versions/express@${expressVersion}`).get() + const passport = require('../../../versions/passport').get() + const BasicStrategy = require(`../../../versions/passport-http@${version}`).get().BasicStrategy + const app = express() + + passport.use(new BasicStrategy((username, password, done) => { + const users = [{ + _id: 1, + username: 'test', + password: '1234', + email: 'testuser@ddog.com' + }] + + const user = users.find(user => (user.username === username) && (user.password === password)) + + if (!user) { + return done(null, false) + } else { + return done(null, user) + } } - } - )) - - app.use(passport.initialize()) - app.use(express.json()) - - app.get('/', - passport.authenticate('basic', { - successRedirect: '/grant', - failureRedirect: '/deny', - passReqToCallback: false, - session: false + )) + + app.use(passport.initialize()) + app.use(express.json()) + + app.get('/', + passport.authenticate('basic', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: false, + session: false + }) + ) + + app.post('/req', + passport.authenticate('basic', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: true, + session: false + }) + ) + + app.get('/grant', (req, res) => { + res.send('Granted') }) - ) - - app.post('/req', - passport.authenticate('basic', { - successRedirect: '/grant', - failureRedirect: '/deny', - passReqToCallback: true, - session: false + + app.get('/deny', (req, res) => { + res.send('Denied') }) - ) - app.get('/grant', (req, res) => { - res.send('Granted') - }) + passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { + subscriberStub(arguments[0]) + }) - app.get('/deny', (req, res) => { - res.send('Denied') + server = app.listen(0, () => { + port = server.address().port + done() + }) }) - passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { - subscriberStub(arguments[0]) + beforeEach(() => { + subscriberStub = sinon.stub() }) - server = app.listen(0, () => { - port = server.address().port - done() + after(() => { + server.close() + return agent.close({ ritmReset: false }) }) - }) - - beforeEach(() => { - subscriberStub = sinon.stub() - }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + it('should call subscriber with proper arguments on success', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1234 + Authorization: 'Basic dGVzdDoxMjM0' + } + }) - it('should call subscriber with proper arguments on success', async () => { - const res = await axios.get(`http://localhost:${port}/`, { - headers: { - // test:1234 - Authorization: 'Basic dGVzdDoxMjM0' - } + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) }) - expect(res.status).to.equal(200) - expect(res.data).to.equal('Granted') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'http', username: 'test' }, - user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } - } - ) - }) + it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1234 + Authorization: 'Basic dGVzdDoxMjM0' + } + }) - it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { - const res = await axios.get(`http://localhost:${port}/`, { - headers: { - // test:1234 - Authorization: 'Basic dGVzdDoxMjM0' - } + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) }) - expect(res.status).to.equal(200) - expect(res.data).to.equal('Granted') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'http', username: 'test' }, - user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } - } - ) - }) + it('should call subscriber with proper arguments on failure', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1 + Authorization: 'Basic dGVzdDox' + } + }) - it('should call subscriber with proper arguments on failure', async () => { - const res = await axios.get(`http://localhost:${port}/`, { - headers: { - // test:1 - Authorization: 'Basic dGVzdDox' - } + expect(res.status).to.equal(200) + expect(res.data).to.equal('Denied') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: false + } + ) }) - - expect(res.status).to.equal(200) - expect(res.data).to.equal('Denied') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'http', username: 'test' }, - user: false - } - ) }) }) }) diff --git a/packages/datadog-instrumentations/test/passport-local.spec.js b/packages/datadog-instrumentations/test/passport-local.spec.js index d54f02b289f..a48b13e085e 100644 --- a/packages/datadog-instrumentations/test/passport-local.spec.js +++ b/packages/datadog-instrumentations/test/passport-local.spec.js @@ -4,125 +4,127 @@ const agent = require('../../dd-trace/test/plugins/agent') const axios = require('axios') const dc = require('dc-polyfill') -withVersions('passport-local', 'passport-local', version => { - describe('passport-local instrumentation', () => { - const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') - let port, server, subscriberStub - - before(() => { - return agent.load(['express', 'passport', 'passport-local'], { client: false }) - }) +withVersions('express', 'express', '>=4.21.1', expressVersion => { + withVersions('passport-local', 'passport-local', version => { + describe('passport-local instrumentation', () => { + const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') + let port, server, subscriberStub + + before(() => { + return agent.load(['express', 'passport', 'passport-local'], { client: false }) + }) - before((done) => { - const express = require('../../../versions/express').get() - const passport = require('../../../versions/passport').get() - const LocalStrategy = require(`../../../versions/passport-local@${version}`).get().Strategy - const app = express() - - passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, - (username, password, done) => { - const users = [{ - _id: 1, - username: 'test', - password: '1234', - email: 'testuser@ddog.com' - }] - - const user = users.find(user => (user.username === username) && (user.password === password)) - - if (!user) { - return done(null, false) - } else { - return done(null, user) + before((done) => { + const express = require(`../../../versions/express@${expressVersion}`).get() + const passport = require('../../../versions/passport').get() + const LocalStrategy = require(`../../../versions/passport-local@${version}`).get().Strategy + const app = express() + + passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, + (username, password, done) => { + const users = [{ + _id: 1, + username: 'test', + password: '1234', + email: 'testuser@ddog.com' + }] + + const user = users.find(user => (user.username === username) && (user.password === password)) + + if (!user) { + return done(null, false) + } else { + return done(null, user) + } } - } - )) - - app.use(passport.initialize()) - app.use(express.json()) - - app.post('/', - passport.authenticate('local', { - successRedirect: '/grant', - failureRedirect: '/deny', - passReqToCallback: false, - session: false + )) + + app.use(passport.initialize()) + app.use(express.json()) + + app.post('/', + passport.authenticate('local', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: false, + session: false + }) + ) + + app.post('/req', + passport.authenticate('local', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: true, + session: false + }) + ) + + app.get('/grant', (req, res) => { + res.send('Granted') }) - ) - - app.post('/req', - passport.authenticate('local', { - successRedirect: '/grant', - failureRedirect: '/deny', - passReqToCallback: true, - session: false + + app.get('/deny', (req, res) => { + res.send('Denied') }) - ) - app.get('/grant', (req, res) => { - res.send('Granted') - }) + passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { + subscriberStub(arguments[0]) + }) - app.get('/deny', (req, res) => { - res.send('Denied') + server = app.listen(0, () => { + port = server.address().port + done() + }) }) - passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { - subscriberStub(arguments[0]) + beforeEach(() => { + subscriberStub = sinon.stub() }) - server = app.listen(0, () => { - port = server.address().port - done() + after(() => { + server.close() + return agent.close({ ritmReset: false }) }) - }) - beforeEach(() => { - subscriberStub = sinon.stub() - }) + it('should call subscriber with proper arguments on success', async () => { + const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1234' }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) - it('should call subscriber with proper arguments on success', async () => { - const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1234' }) - - expect(res.status).to.equal(200) - expect(res.data).to.equal('Granted') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'local', username: 'test' }, - user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } - } - ) - }) + it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { + const res = await axios.post(`http://localhost:${port}/req`, { username: 'test', password: '1234' }) - it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { - const res = await axios.post(`http://localhost:${port}/req`, { username: 'test', password: '1234' }) - - expect(res.status).to.equal(200) - expect(res.data).to.equal('Granted') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'local', username: 'test' }, - user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } - } - ) - }) + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) + + it('should call subscriber with proper arguments on failure', async () => { + const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1' }) - it('should call subscriber with proper arguments on failure', async () => { - const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1' }) - - expect(res.status).to.equal(200) - expect(res.data).to.equal('Denied') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'local', username: 'test' }, - user: false - } - ) + expect(res.status).to.equal(200) + expect(res.data).to.equal('Denied') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: false + } + ) + }) }) }) }) From 4d71ea895f68808c916c9f7d2b8dc5f4e3807999 Mon Sep 17 00:00:00 2001 From: ishabi Date: Fri, 22 Nov 2024 15:58:24 +0100 Subject: [PATCH 12/31] add custom middleware --- .../datadog-instrumentations/src/router.js | 2 +- .../test/body-parser.spec.js | 136 +++++---- .../test/cookie-parser.spec.js | 104 ++++--- .../test/express-mongo-sanitize.spec.js | 284 +++++++++--------- .../test/multer.spec.js | 162 +++++----- .../test/passport-http.spec.js | 228 +++++++------- .../test/passport-local.spec.js | 208 +++++++------ .../datadog-plugin-express/test/index.spec.js | 86 +++--- .../test/integration-test/client.spec.js | 4 +- ...ingerprinting.passport-http.plugin.spec.js | 134 ++++----- ...ngerprinting.passport-local.plugin.spec.js | 132 ++++---- .../appsec/index.body-parser.plugin.spec.js | 90 +++--- .../appsec/index.cookie-parser.plugin.spec.js | 100 +++--- .../test/appsec/index.express.plugin.spec.js | 1 - 14 files changed, 825 insertions(+), 846 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index b1ec2574ce1..4eedef41752 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -178,7 +178,7 @@ addHook({ name: 'router', versions: ['>=1'] }, Router => { // Add query parsing middleware if (!router._queryMiddlewareAdded) { router._queryMiddlewareAdded = true - router.use(function queryParsingMiddleware (req, res, next) { + router.use(function query (req, res, next) { req.query next() }) diff --git a/packages/datadog-instrumentations/test/body-parser.spec.js b/packages/datadog-instrumentations/test/body-parser.spec.js index b116d6be751..482ba5e772d 100644 --- a/packages/datadog-instrumentations/test/body-parser.spec.js +++ b/packages/datadog-instrumentations/test/body-parser.spec.js @@ -5,95 +5,93 @@ const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') const { storage } = require('../../datadog-core') -withVersions('express', 'express', expressVersion => { - withVersions('body-parser', 'body-parser', version => { - describe('body parser instrumentation', () => { - const bodyParserReadCh = dc.channel('datadog:body-parser:read:finish') - let port, server, middlewareProcessBodyStub - - before(() => { - return agent.load(['http', 'express', 'body-parser'], { client: false }) - }) +withVersions('body-parser', 'body-parser', version => { + describe('body parser instrumentation', () => { + const bodyParserReadCh = dc.channel('datadog:body-parser:read:finish') + let port, server, middlewareProcessBodyStub - before((done) => { - const express = require(`../../../versions/express@${expressVersion}`).get() - const bodyParser = require(`../../../versions/body-parser@${version}`).get() - const app = express() - app.use(bodyParser.json()) - app.post('/', (req, res) => { - middlewareProcessBodyStub() - res.end('DONE') - }) - server = app.listen(0, () => { - port = server.address().port - done() - }) - }) + before(() => { + return agent.load(['http', 'express', 'body-parser'], { client: false }) + }) - beforeEach(async () => { - middlewareProcessBodyStub = sinon.stub() + before((done) => { + const express = require('../../../versions/express').get() + const bodyParser = require(`../../../versions/body-parser@${version}`).get() + const app = express() + app.use(bodyParser.json()) + app.post('/', (req, res) => { + middlewareProcessBodyStub() + res.end('DONE') }) - - after(() => { - server.close() - return agent.close({ ritmReset: false }) + server = app.listen(0, () => { + port = server.address().port + done() }) + }) - it('should not abort the request by default', async () => { - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + beforeEach(async () => { + middlewareProcessBodyStub = sinon.stub() + }) - expect(middlewareProcessBodyStub).to.be.calledOnce - expect(res.data).to.be.equal('DONE') - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - it('should not abort the request with non blocker subscription', async () => { - function noop () {} - bodyParserReadCh.subscribe(noop) + it('should not abort the request by default', async () => { + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + expect(middlewareProcessBodyStub).to.be.calledOnce + expect(res.data).to.be.equal('DONE') + }) - expect(middlewareProcessBodyStub).to.be.calledOnce - expect(res.data).to.be.equal('DONE') + it('should not abort the request with non blocker subscription', async () => { + function noop () {} + bodyParserReadCh.subscribe(noop) - bodyParserReadCh.unsubscribe(noop) - }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - it('should abort the request when abortController.abort() is called', async () => { - function blockRequest ({ res, abortController }) { - res.end('BLOCKED') - abortController.abort() - } - bodyParserReadCh.subscribe(blockRequest) + expect(middlewareProcessBodyStub).to.be.calledOnce + expect(res.data).to.be.equal('DONE') - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + bodyParserReadCh.unsubscribe(noop) + }) - expect(middlewareProcessBodyStub).not.to.be.called - expect(res.data).to.be.equal('BLOCKED') + it('should abort the request when abortController.abort() is called', async () => { + function blockRequest ({ res, abortController }) { + res.end('BLOCKED') + abortController.abort() + } + bodyParserReadCh.subscribe(blockRequest) - bodyParserReadCh.unsubscribe(blockRequest) - }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - it('should not lose the http async context', async () => { - let store - let payload + expect(middlewareProcessBodyStub).not.to.be.called + expect(res.data).to.be.equal('BLOCKED') - function handler (data) { - store = storage.getStore() - payload = data - } - bodyParserReadCh.subscribe(handler) + bodyParserReadCh.unsubscribe(blockRequest) + }) - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + it('should not lose the http async context', async () => { + let store + let payload - expect(store).to.have.property('req', payload.req) - expect(store).to.have.property('res', payload.res) - expect(store).to.have.property('span') + function handler (data) { + store = storage.getStore() + payload = data + } + bodyParserReadCh.subscribe(handler) - expect(middlewareProcessBodyStub).to.be.calledOnce - expect(res.data).to.be.equal('DONE') + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - bodyParserReadCh.unsubscribe(handler) - }) + expect(store).to.have.property('req', payload.req) + expect(store).to.have.property('res', payload.res) + expect(store).to.have.property('span') + + expect(middlewareProcessBodyStub).to.be.calledOnce + expect(res.data).to.be.equal('DONE') + + bodyParserReadCh.unsubscribe(handler) }) }) }) diff --git a/packages/datadog-instrumentations/test/cookie-parser.spec.js b/packages/datadog-instrumentations/test/cookie-parser.spec.js index 1e4a4277f48..799d434dd05 100644 --- a/packages/datadog-instrumentations/test/cookie-parser.spec.js +++ b/packages/datadog-instrumentations/test/cookie-parser.spec.js @@ -5,73 +5,71 @@ const dc = require('dc-polyfill') const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') -withVersions('express', 'express', expressVersion => { - withVersions('cookie-parser', 'cookie-parser', version => { - describe('cookie parser instrumentation', () => { - const cookieParserReadCh = dc.channel('datadog:cookie-parser:read:finish') - let port, server, middlewareProcessCookieStub - - before(() => { - return agent.load(['express', 'cookie-parser'], { client: false }) - }) +withVersions('cookie-parser', 'cookie-parser', version => { + describe('cookie parser instrumentation', () => { + const cookieParserReadCh = dc.channel('datadog:cookie-parser:read:finish') + let port, server, middlewareProcessCookieStub - before((done) => { - const express = require(`../../../versions/express@${expressVersion}`).get() - const cookieParser = require(`../../../versions/cookie-parser@${version}`).get() - const app = express() - app.use(cookieParser()) - app.post('/', (req, res) => { - middlewareProcessCookieStub() - res.end('DONE') - }) - server = app.listen(0, () => { - port = server.address().port - done() - }) - }) + before(() => { + return agent.load(['express', 'cookie-parser'], { client: false }) + }) - beforeEach(async () => { - middlewareProcessCookieStub = sinon.stub() + before((done) => { + const express = require('../../../versions/express').get() + const cookieParser = require(`../../../versions/cookie-parser@${version}`).get() + const app = express() + app.use(cookieParser()) + app.post('/', (req, res) => { + middlewareProcessCookieStub() + res.end('DONE') }) - - after(() => { - server.close() - return agent.close({ ritmReset: false }) + server = app.listen(0, () => { + port = server.address().port + done() }) + }) - it('should not abort the request by default', async () => { - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + beforeEach(async () => { + middlewareProcessCookieStub = sinon.stub() + }) - sinon.assert.calledOnce(middlewareProcessCookieStub) - assert.equal(res.data, 'DONE') - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - it('should not abort the request with non blocker subscription', async () => { - function noop () {} - cookieParserReadCh.subscribe(noop) + it('should not abort the request by default', async () => { + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + sinon.assert.calledOnce(middlewareProcessCookieStub) + assert.equal(res.data, 'DONE') + }) - sinon.assert.calledOnce(middlewareProcessCookieStub) - assert.equal(res.data, 'DONE') + it('should not abort the request with non blocker subscription', async () => { + function noop () {} + cookieParserReadCh.subscribe(noop) - cookieParserReadCh.unsubscribe(noop) - }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - it('should abort the request when abortController.abort() is called', async () => { - function blockRequest ({ res, abortController }) { - res.end('BLOCKED') - abortController.abort() - } - cookieParserReadCh.subscribe(blockRequest) + sinon.assert.calledOnce(middlewareProcessCookieStub) + assert.equal(res.data, 'DONE') - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + cookieParserReadCh.unsubscribe(noop) + }) - sinon.assert.notCalled(middlewareProcessCookieStub) - assert.equal(res.data, 'BLOCKED') + it('should abort the request when abortController.abort() is called', async () => { + function blockRequest ({ res, abortController }) { + res.end('BLOCKED') + abortController.abort() + } + cookieParserReadCh.subscribe(blockRequest) - cookieParserReadCh.unsubscribe(blockRequest) - }) + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + + sinon.assert.notCalled(middlewareProcessCookieStub) + assert.equal(res.data, 'BLOCKED') + + cookieParserReadCh.unsubscribe(blockRequest) }) }) }) diff --git a/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js b/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js index 9252f1c25d7..3fcf981e528 100644 --- a/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js +++ b/packages/datadog-instrumentations/test/express-mongo-sanitize.spec.js @@ -4,204 +4,202 @@ const agent = require('../../dd-trace/test/plugins/agent') const { channel } = require('dc-polyfill') const axios = require('axios') describe('express-mongo-sanitize', () => { - withVersions('express', 'express', expressVersion => { - withVersions('express-mongo-sanitize', 'express-mongo-sanitize', version => { - describe('middleware', () => { - const sanitizeMiddlewareFinished = channel('datadog:express-mongo-sanitize:filter:finish') - let port, server, requestBody + withVersions('express-mongo-sanitize', 'express-mongo-sanitize', version => { + describe('middleware', () => { + const sanitizeMiddlewareFinished = channel('datadog:express-mongo-sanitize:filter:finish') + let port, server, requestBody - before(() => { - return agent.load(['express', 'express-mongo-sanitize'], { client: false }) - }) - - before((done) => { - const express = require(`../../../versions/express@${expressVersion}`).get() - const expressMongoSanitize = require(`../../../versions/express-mongo-sanitize@${version}`).get() - const app = express() + before(() => { + return agent.load(['express', 'express-mongo-sanitize'], { client: false }) + }) - app.use(expressMongoSanitize()) - app.all('/', (req, res) => { - requestBody(req, res) - res.end() - }) + before((done) => { + const express = require('../../../versions/express').get() + const expressMongoSanitize = require(`../../../versions/express-mongo-sanitize@${version}`).get() + const app = express() - server = app.listen(0, () => { - port = server.address().port - done() - }) + app.use(expressMongoSanitize()) + app.all('/', (req, res) => { + requestBody(req, res) + res.end() }) - beforeEach(() => { - requestBody = sinon.stub() + server = app.listen(0, () => { + port = server.address().port + done() }) + }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + beforeEach(() => { + requestBody = sinon.stub() + }) - describe('without subscriptions', () => { - it('it continues working without sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.false + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - await axios.get(`http://localhost:${port}/?param=paramvalue`) + describe('without subscriptions', () => { + it('it continues working without sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.false - expect(requestBody).to.be.calledOnce - expect(requestBody.firstCall.args[0].query.param).to.be.equal('paramvalue') - }) + await axios.get(`http://localhost:${port}/?param=paramvalue`) - it('it continues working with sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.false + expect(requestBody).to.be.calledOnce + expect(requestBody.firstCall.args[0].query.param).to.be.equal('paramvalue') + }) + + it('it continues working with sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.false - await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) + await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) - expect(requestBody).to.be.calledOnce - expect(requestBody.firstCall.args[0].query.param.$eq).to.be.undefined - }) + expect(requestBody).to.be.calledOnce + expect(requestBody.firstCall.args[0].query.param.$eq).to.be.undefined }) + }) - describe('with subscriptions', () => { - let subscription + describe('with subscriptions', () => { + let subscription - beforeEach(() => { - subscription = sinon.stub() - sanitizeMiddlewareFinished.subscribe(subscription) - }) + beforeEach(() => { + subscription = sinon.stub() + sanitizeMiddlewareFinished.subscribe(subscription) + }) - afterEach(() => { - sanitizeMiddlewareFinished.unsubscribe(subscription) - }) + afterEach(() => { + sanitizeMiddlewareFinished.unsubscribe(subscription) + }) - it('it continues working without sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true + it('it continues working without sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true - await axios.get(`http://localhost:${port}/?param=paramvalue`) + await axios.get(`http://localhost:${port}/?param=paramvalue`) - expect(requestBody).to.be.calledOnce - expect(requestBody.firstCall.args[0].query.param).to.be.equal('paramvalue') - }) + expect(requestBody).to.be.calledOnce + expect(requestBody.firstCall.args[0].query.param).to.be.equal('paramvalue') + }) - it('it continues working with sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true + it('it continues working with sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true - await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) + await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) - expect(requestBody).to.be.calledOnce - expect(requestBody.firstCall.args[0].query.param.$eq).to.be.undefined - }) + expect(requestBody).to.be.calledOnce + expect(requestBody.firstCall.args[0].query.param.$eq).to.be.undefined + }) - it('subscription is called with expected parameters without sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true + it('subscription is called with expected parameters without sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true - await axios.get(`http://localhost:${port}/?param=paramvalue`) + await axios.get(`http://localhost:${port}/?param=paramvalue`) - expect(subscription).to.be.calledOnce - expect(subscription.firstCall.args[0].sanitizedProperties) - .to.be.deep.equal(['body', 'params', 'headers', 'query']) - expect(subscription.firstCall.args[0].req.query.param).to.be.equal('paramvalue') - }) + expect(subscription).to.be.calledOnce + expect(subscription.firstCall.args[0].sanitizedProperties) + .to.be.deep.equal(['body', 'params', 'headers', 'query']) + expect(subscription.firstCall.args[0].req.query.param).to.be.equal('paramvalue') + }) - it('subscription is called with expected parameters with sanitization request', async () => { - expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true + it('subscription is called with expected parameters with sanitization request', async () => { + expect(sanitizeMiddlewareFinished.hasSubscribers).to.be.true - await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) + await axios.get(`http://localhost:${port}/?param[$eq]=paramvalue`) - expect(subscription).to.be.calledOnce - expect(subscription.firstCall.args[0].sanitizedProperties) - .to.be.deep.equal(['body', 'params', 'headers', 'query']) - expect(subscription.firstCall.args[0].req.query.param.$eq).to.be.undefined - }) + expect(subscription).to.be.calledOnce + expect(subscription.firstCall.args[0].sanitizedProperties) + .to.be.deep.equal(['body', 'params', 'headers', 'query']) + expect(subscription.firstCall.args[0].req.query.param.$eq).to.be.undefined }) }) + }) - describe('sanitize method', () => { - const sanitizeFinished = channel('datadog:express-mongo-sanitize:sanitize:finish') - let expressMongoSanitize + describe('sanitize method', () => { + const sanitizeFinished = channel('datadog:express-mongo-sanitize:sanitize:finish') + let expressMongoSanitize - before(() => { - return agent.load(['express-mongo-sanitize'], { client: false }) - }) + before(() => { + return agent.load(['express-mongo-sanitize'], { client: false }) + }) - before(() => { - expressMongoSanitize = require(`../../../versions/express-mongo-sanitize@${version}`).get() - }) + before(() => { + expressMongoSanitize = require(`../../../versions/express-mongo-sanitize@${version}`).get() + }) - after(() => { - return agent.close({ ritmReset: false }) - }) + after(() => { + return agent.close({ ritmReset: false }) + }) - describe('without subscriptions', () => { - it('it works as expected without modifications', () => { - expect(sanitizeFinished.hasSubscribers).to.be.false + describe('without subscriptions', () => { + it('it works as expected without modifications', () => { + expect(sanitizeFinished.hasSubscribers).to.be.false - const objectToSanitize = { - safeKey: 'safeValue' - } + const objectToSanitize = { + safeKey: 'safeValue' + } - const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) + const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) - expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) - }) + expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) + }) - it('it works as expected with modifications', () => { - expect(sanitizeFinished.hasSubscribers).to.be.false + it('it works as expected with modifications', () => { + expect(sanitizeFinished.hasSubscribers).to.be.false - const objectToSanitize = { - unsafeKey: { - $ne: 'test' - }, - safeKey: 'safeValue' - } + const objectToSanitize = { + unsafeKey: { + $ne: 'test' + }, + safeKey: 'safeValue' + } - const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) + const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) - expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) - expect(sanitizedObject.unsafeKey.$ne).to.be.undefined - }) + expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) + expect(sanitizedObject.unsafeKey.$ne).to.be.undefined }) + }) - describe('with subscriptions', () => { - let subscription + describe('with subscriptions', () => { + let subscription - beforeEach(() => { - subscription = sinon.stub() - sanitizeFinished.subscribe(subscription) - }) + beforeEach(() => { + subscription = sinon.stub() + sanitizeFinished.subscribe(subscription) + }) - afterEach(() => { - sanitizeFinished.unsubscribe(subscription) - subscription = undefined - }) + afterEach(() => { + sanitizeFinished.unsubscribe(subscription) + subscription = undefined + }) - it('it works as expected without modifications', () => { - expect(sanitizeFinished.hasSubscribers).to.be.true + it('it works as expected without modifications', () => { + expect(sanitizeFinished.hasSubscribers).to.be.true - const objectToSanitize = { - safeKey: 'safeValue' - } + const objectToSanitize = { + safeKey: 'safeValue' + } - const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) + const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) - expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) - expect(subscription).to.be.calledOnceWith({ sanitizedObject }) - }) + expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) + expect(subscription).to.be.calledOnceWith({ sanitizedObject }) + }) - it('it works as expected with modifications', () => { - expect(sanitizeFinished.hasSubscribers).to.be.true + it('it works as expected with modifications', () => { + expect(sanitizeFinished.hasSubscribers).to.be.true - const objectToSanitize = { - unsafeKey: { - $ne: 'test' - }, - safeKey: 'safeValue' - } + const objectToSanitize = { + unsafeKey: { + $ne: 'test' + }, + safeKey: 'safeValue' + } - const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) + const sanitizedObject = expressMongoSanitize.sanitize(objectToSanitize) - expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) - expect(sanitizedObject.unsafeKey.$ne).to.be.undefined - expect(subscription).to.be.calledOnceWith({ sanitizedObject }) - }) + expect(sanitizedObject.safeKey).to.be.equal(objectToSanitize.safeKey) + expect(sanitizedObject.unsafeKey.$ne).to.be.undefined + expect(subscription).to.be.calledOnceWith({ sanitizedObject }) }) }) }) diff --git a/packages/datadog-instrumentations/test/multer.spec.js b/packages/datadog-instrumentations/test/multer.spec.js index 70c8c35b6e4..f7edcee6cd3 100644 --- a/packages/datadog-instrumentations/test/multer.spec.js +++ b/packages/datadog-instrumentations/test/multer.spec.js @@ -5,106 +5,104 @@ const axios = require('axios') const agent = require('../../dd-trace/test/plugins/agent') const { storage } = require('../../datadog-core') -withVersions('express', 'express', expressVersion => { - withVersions('multer', 'multer', version => { - describe('multer parser instrumentation', () => { - const multerReadCh = dc.channel('datadog:multer:read:finish') - let port, server, middlewareProcessBodyStub, formData - - before(() => { - return agent.load(['http', 'express', 'multer'], { client: false }) - }) +withVersions('multer', 'multer', version => { + describe('multer parser instrumentation', () => { + const multerReadCh = dc.channel('datadog:multer:read:finish') + let port, server, middlewareProcessBodyStub, formData - before((done) => { - const express = require(`../../../versions/express@${expressVersion}`).get() - const multer = require(`../../../versions/multer@${version}`).get() - const uploadToMemory = multer({ storage: multer.memoryStorage(), limits: { fileSize: 200000 } }) - - const app = express() - - app.post('/', uploadToMemory.single('file'), (req, res) => { - middlewareProcessBodyStub(req.body.key) - res.end('DONE') - }) - server = app.listen(0, () => { - port = server.address().port - done() - }) - }) + before(() => { + return agent.load(['http', 'express', 'multer'], { client: false }) + }) - beforeEach(async () => { - middlewareProcessBodyStub = sinon.stub() + before((done) => { + const express = require('../../../versions/express').get() + const multer = require(`../../../versions/multer@${version}`).get() + const uploadToMemory = multer({ storage: multer.memoryStorage(), limits: { fileSize: 200000 } }) - formData = new FormData() - formData.append('key', 'value') - }) + const app = express() - after(() => { - server.close() - return agent.close({ ritmReset: false }) + app.post('/', uploadToMemory.single('file'), (req, res) => { + middlewareProcessBodyStub(req.body.key) + res.end('DONE') }) + server = app.listen(0, () => { + port = server.address().port + done() + }) + }) + + beforeEach(async () => { + middlewareProcessBodyStub = sinon.stub() + + formData = new FormData() + formData.append('key', 'value') + }) + + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) + + it('should not abort the request by default', async () => { + const res = await axios.post(`http://localhost:${port}/`, formData) + + expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) + expect(res.data).to.be.equal('DONE') + }) + + it('should not abort the request with non blocker subscription', async () => { + function noop () {} + multerReadCh.subscribe(noop) - it('should not abort the request by default', async () => { + try { const res = await axios.post(`http://localhost:${port}/`, formData) expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) expect(res.data).to.be.equal('DONE') - }) - - it('should not abort the request with non blocker subscription', async () => { - function noop () {} - multerReadCh.subscribe(noop) + } finally { + multerReadCh.unsubscribe(noop) + } + }) - try { - const res = await axios.post(`http://localhost:${port}/`, formData) + it('should abort the request when abortController.abort() is called', async () => { + function blockRequest ({ res, abortController }) { + res.end('BLOCKED') + abortController.abort() + } + multerReadCh.subscribe(blockRequest) - expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) - expect(res.data).to.be.equal('DONE') - } finally { - multerReadCh.unsubscribe(noop) - } - }) + try { + const res = await axios.post(`http://localhost:${port}/`, formData) - it('should abort the request when abortController.abort() is called', async () => { - function blockRequest ({ res, abortController }) { - res.end('BLOCKED') - abortController.abort() - } - multerReadCh.subscribe(blockRequest) - - try { - const res = await axios.post(`http://localhost:${port}/`, formData) - - expect(middlewareProcessBodyStub).not.to.be.called - expect(res.data).to.be.equal('BLOCKED') - } finally { - multerReadCh.unsubscribe(blockRequest) - } - }) + expect(middlewareProcessBodyStub).not.to.be.called + expect(res.data).to.be.equal('BLOCKED') + } finally { + multerReadCh.unsubscribe(blockRequest) + } + }) - it('should not lose the http async context', async () => { - let store - let payload + it('should not lose the http async context', async () => { + let store + let payload - function handler (data) { - store = storage.getStore() - payload = data - } - multerReadCh.subscribe(handler) + function handler (data) { + store = storage.getStore() + payload = data + } + multerReadCh.subscribe(handler) - try { - const res = await axios.post(`http://localhost:${port}/`, formData) + try { + const res = await axios.post(`http://localhost:${port}/`, formData) - expect(store).to.have.property('req', payload.req) - expect(store).to.have.property('res', payload.res) - expect(store).to.have.property('span') + expect(store).to.have.property('req', payload.req) + expect(store).to.have.property('res', payload.res) + expect(store).to.have.property('span') - expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) - expect(res.data).to.be.equal('DONE') - } finally { - multerReadCh.unsubscribe(handler) - } - }) + expect(middlewareProcessBodyStub).to.be.calledOnceWithExactly(formData.get('key')) + expect(res.data).to.be.equal('DONE') + } finally { + multerReadCh.unsubscribe(handler) + } }) }) }) diff --git a/packages/datadog-instrumentations/test/passport-http.spec.js b/packages/datadog-instrumentations/test/passport-http.spec.js index dc513f5ccbc..2918c935e20 100644 --- a/packages/datadog-instrumentations/test/passport-http.spec.js +++ b/packages/datadog-instrumentations/test/passport-http.spec.js @@ -4,141 +4,139 @@ const agent = require('../../dd-trace/test/plugins/agent') const axios = require('axios') const dc = require('dc-polyfill') -withVersions('express', 'express', '>=4.21.1', expressVersion => { - withVersions('passport-http', 'passport-http', version => { - describe('passport-http instrumentation', () => { - const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') - let port, server, subscriberStub - - before(() => { - return agent.load(['express', 'passport', 'passport-http'], { client: false }) - }) +withVersions('passport-http', 'passport-http', version => { + describe('passport-http instrumentation', () => { + const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') + let port, server, subscriberStub + + before(() => { + return agent.load(['express', 'passport', 'passport-http'], { client: false }) + }) - before((done) => { - const express = require(`../../../versions/express@${expressVersion}`).get() - const passport = require('../../../versions/passport').get() - const BasicStrategy = require(`../../../versions/passport-http@${version}`).get().BasicStrategy - const app = express() - - passport.use(new BasicStrategy((username, password, done) => { - const users = [{ - _id: 1, - username: 'test', - password: '1234', - email: 'testuser@ddog.com' - }] - - const user = users.find(user => (user.username === username) && (user.password === password)) - - if (!user) { - return done(null, false) - } else { - return done(null, user) - } + before((done) => { + const express = require('../../../versions/express').get() + const passport = require('../../../versions/passport').get() + const BasicStrategy = require(`../../../versions/passport-http@${version}`).get().BasicStrategy + const app = express() + + passport.use(new BasicStrategy((username, password, done) => { + const users = [{ + _id: 1, + username: 'test', + password: '1234', + email: 'testuser@ddog.com' + }] + + const user = users.find(user => (user.username === username) && (user.password === password)) + + if (!user) { + return done(null, false) + } else { + return done(null, user) } - )) - - app.use(passport.initialize()) - app.use(express.json()) - - app.get('/', - passport.authenticate('basic', { - successRedirect: '/grant', - failureRedirect: '/deny', - passReqToCallback: false, - session: false - }) - ) - - app.post('/req', - passport.authenticate('basic', { - successRedirect: '/grant', - failureRedirect: '/deny', - passReqToCallback: true, - session: false - }) - ) - - app.get('/grant', (req, res) => { - res.send('Granted') + } + )) + + app.use(passport.initialize()) + app.use(express.json()) + + app.get('/', + passport.authenticate('basic', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: false, + session: false }) - - app.get('/deny', (req, res) => { - res.send('Denied') + ) + + app.post('/req', + passport.authenticate('basic', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: true, + session: false }) + ) - passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { - subscriberStub(arguments[0]) - }) + app.get('/grant', (req, res) => { + res.send('Granted') + }) - server = app.listen(0, () => { - port = server.address().port - done() - }) + app.get('/deny', (req, res) => { + res.send('Denied') }) - beforeEach(() => { - subscriberStub = sinon.stub() + passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { + subscriberStub(arguments[0]) }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) + server = app.listen(0, () => { + port = server.address().port + done() }) + }) - it('should call subscriber with proper arguments on success', async () => { - const res = await axios.get(`http://localhost:${port}/`, { - headers: { - // test:1234 - Authorization: 'Basic dGVzdDoxMjM0' - } - }) + beforeEach(() => { + subscriberStub = sinon.stub() + }) + + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - expect(res.status).to.equal(200) - expect(res.data).to.equal('Granted') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'http', username: 'test' }, - user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } - } - ) + it('should call subscriber with proper arguments on success', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1234 + Authorization: 'Basic dGVzdDoxMjM0' + } }) - it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { - const res = await axios.get(`http://localhost:${port}/`, { - headers: { - // test:1234 - Authorization: 'Basic dGVzdDoxMjM0' - } - }) + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) - expect(res.status).to.equal(200) - expect(res.data).to.equal('Granted') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'http', username: 'test' }, - user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } - } - ) + it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1234 + Authorization: 'Basic dGVzdDoxMjM0' + } }) - it('should call subscriber with proper arguments on failure', async () => { - const res = await axios.get(`http://localhost:${port}/`, { - headers: { - // test:1 - Authorization: 'Basic dGVzdDox' - } - }) + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) - expect(res.status).to.equal(200) - expect(res.data).to.equal('Denied') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'http', username: 'test' }, - user: false - } - ) + it('should call subscriber with proper arguments on failure', async () => { + const res = await axios.get(`http://localhost:${port}/`, { + headers: { + // test:1 + Authorization: 'Basic dGVzdDox' + } }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Denied') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'http', username: 'test' }, + user: false + } + ) }) }) }) diff --git a/packages/datadog-instrumentations/test/passport-local.spec.js b/packages/datadog-instrumentations/test/passport-local.spec.js index a48b13e085e..d54f02b289f 100644 --- a/packages/datadog-instrumentations/test/passport-local.spec.js +++ b/packages/datadog-instrumentations/test/passport-local.spec.js @@ -4,127 +4,125 @@ const agent = require('../../dd-trace/test/plugins/agent') const axios = require('axios') const dc = require('dc-polyfill') -withVersions('express', 'express', '>=4.21.1', expressVersion => { - withVersions('passport-local', 'passport-local', version => { - describe('passport-local instrumentation', () => { - const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') - let port, server, subscriberStub - - before(() => { - return agent.load(['express', 'passport', 'passport-local'], { client: false }) - }) +withVersions('passport-local', 'passport-local', version => { + describe('passport-local instrumentation', () => { + const passportVerifyChannel = dc.channel('datadog:passport:verify:finish') + let port, server, subscriberStub - before((done) => { - const express = require(`../../../versions/express@${expressVersion}`).get() - const passport = require('../../../versions/passport').get() - const LocalStrategy = require(`../../../versions/passport-local@${version}`).get().Strategy - const app = express() - - passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, - (username, password, done) => { - const users = [{ - _id: 1, - username: 'test', - password: '1234', - email: 'testuser@ddog.com' - }] - - const user = users.find(user => (user.username === username) && (user.password === password)) - - if (!user) { - return done(null, false) - } else { - return done(null, user) - } - } - )) - - app.use(passport.initialize()) - app.use(express.json()) - - app.post('/', - passport.authenticate('local', { - successRedirect: '/grant', - failureRedirect: '/deny', - passReqToCallback: false, - session: false - }) - ) - - app.post('/req', - passport.authenticate('local', { - successRedirect: '/grant', - failureRedirect: '/deny', - passReqToCallback: true, - session: false - }) - ) - - app.get('/grant', (req, res) => { - res.send('Granted') - }) + before(() => { + return agent.load(['express', 'passport', 'passport-local'], { client: false }) + }) - app.get('/deny', (req, res) => { - res.send('Denied') + before((done) => { + const express = require('../../../versions/express').get() + const passport = require('../../../versions/passport').get() + const LocalStrategy = require(`../../../versions/passport-local@${version}`).get().Strategy + const app = express() + + passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, + (username, password, done) => { + const users = [{ + _id: 1, + username: 'test', + password: '1234', + email: 'testuser@ddog.com' + }] + + const user = users.find(user => (user.username === username) && (user.password === password)) + + if (!user) { + return done(null, false) + } else { + return done(null, user) + } + } + )) + + app.use(passport.initialize()) + app.use(express.json()) + + app.post('/', + passport.authenticate('local', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: false, + session: false }) - - passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { - subscriberStub(arguments[0]) + ) + + app.post('/req', + passport.authenticate('local', { + successRedirect: '/grant', + failureRedirect: '/deny', + passReqToCallback: true, + session: false }) + ) - server = app.listen(0, () => { - port = server.address().port - done() - }) + app.get('/grant', (req, res) => { + res.send('Granted') }) - beforeEach(() => { - subscriberStub = sinon.stub() + app.get('/deny', (req, res) => { + res.send('Denied') }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) + passportVerifyChannel.subscribe(function ({ credentials, user, err, info }) { + subscriberStub(arguments[0]) }) - it('should call subscriber with proper arguments on success', async () => { - const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1234' }) - - expect(res.status).to.equal(200) - expect(res.data).to.equal('Granted') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'local', username: 'test' }, - user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } - } - ) + server = app.listen(0, () => { + port = server.address().port + done() }) + }) - it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { - const res = await axios.post(`http://localhost:${port}/req`, { username: 'test', password: '1234' }) + beforeEach(() => { + subscriberStub = sinon.stub() + }) - expect(res.status).to.equal(200) - expect(res.data).to.equal('Granted') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'local', username: 'test' }, - user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } - } - ) - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - it('should call subscriber with proper arguments on failure', async () => { - const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1' }) + it('should call subscriber with proper arguments on success', async () => { + const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1234' }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) - expect(res.status).to.equal(200) - expect(res.data).to.equal('Denied') - expect(subscriberStub).to.be.calledOnceWithExactly( - { - credentials: { type: 'local', username: 'test' }, - user: false - } - ) - }) + it('should call subscriber with proper arguments on success with passReqToCallback set to true', async () => { + const res = await axios.post(`http://localhost:${port}/req`, { username: 'test', password: '1234' }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Granted') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: { _id: 1, username: 'test', password: '1234', email: 'testuser@ddog.com' } + } + ) + }) + + it('should call subscriber with proper arguments on failure', async () => { + const res = await axios.post(`http://localhost:${port}/`, { username: 'test', password: '1' }) + + expect(res.status).to.equal(200) + expect(res.data).to.equal('Denied') + expect(subscriberStub).to.be.calledOnceWithExactly( + { + credentials: { type: 'local', username: 'test' }, + user: false + } + ) }) }) }) diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 0c0e03f5408..bab36ac0572 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -215,47 +215,51 @@ describe('Plugin', () => { agent .use(traces => { const spans = sort(traces[0]) - - expect(spans[0]).to.have.property('resource', 'GET /app/user/:id') - expect(spans[0]).to.have.property('name', 'express.request') - expect(spans[0].meta).to.have.property('component', 'express') - - if (semver.intersects(version, '<5.0.0')) { - expect(spans[1]).to.have.property('resource', 'query') - expect(spans[1]).to.have.property('name', 'express.middleware') - expect(spans[1].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[1].meta).to.have.property('component', 'express') - expect(spans[2]).to.have.property('resource', 'expressInit') - expect(spans[2]).to.have.property('name', 'express.middleware') - expect(spans[2].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[2].meta).to.have.property('component', 'express') + const isExpress4 = semver.intersects(version, '<5.0.0') + let index = 0 + + expect(spans[index]).to.have.property('resource', 'GET /app/user/:id') + expect(spans[index]).to.have.property('name', 'express.request') + expect(spans[index].meta).to.have.property('component', 'express') + index++ + + expect(spans[index]).to.have.property('resource', 'query') + expect(spans[index]).to.have.property('name', 'express.middleware') + expect(spans[index].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[index].meta).to.have.property('component', 'express') + index++ + + if (isExpress4) { + expect(spans[index]).to.have.property('resource', 'expressInit') + expect(spans[index]).to.have.property('name', 'express.middleware') + expect(spans[index].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[index].meta).to.have.property('component', 'express') + index++ } - const namedSpanIndex = semver.intersects(version, '<5.0.0') ? 3 : 1 - - expect(spans[namedSpanIndex]).to.have.property('resource', 'named') - expect(spans[namedSpanIndex]).to.have.property('name', 'express.middleware') - expect(spans[namedSpanIndex].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[namedSpanIndex].meta).to.have.property('component', 'express') - - expect(spans[namedSpanIndex + 1]).to.have.property('resource', 'router') - expect(spans[namedSpanIndex + 1]).to.have.property('name', 'express.middleware') - expect(spans[namedSpanIndex + 1].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[namedSpanIndex + 1].meta).to.have.property('component', 'express') - - // expect(spans[namedSpanIndex + 2].resource).to.match(/^bound\s.*$/) called handle in express5 - expect(spans[namedSpanIndex + 2]).to.have.property('name', 'express.middleware') - expect(spans[namedSpanIndex + 2].parent_id.toString()).to.equal( - spans[namedSpanIndex + 1].span_id.toString() - ) - expect(spans[namedSpanIndex + 2].meta).to.have.property('component', 'express') - - expect(spans[namedSpanIndex + 3]).to.have.property('resource', '') - expect(spans[namedSpanIndex + 3]).to.have.property('name', 'express.middleware') - expect(spans[namedSpanIndex + 3].parent_id.toString()).to.equal( - spans[namedSpanIndex + 2].span_id.toString() - ) - expect(spans[namedSpanIndex + 3].meta).to.have.property('component', 'express') + expect(spans[index]).to.have.property('resource', 'named') + expect(spans[index]).to.have.property('name', 'express.middleware') + expect(spans[index].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[index].meta).to.have.property('component', 'express') + index++ + + expect(spans[index]).to.have.property('resource', 'router') + expect(spans[index]).to.have.property('name', 'express.middleware') + expect(spans[index].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[index].meta).to.have.property('component', 'express') + index++ + + expect(spans[index]).to.have.property('name', 'express.middleware') + expect(spans[index].parent_id.toString()).to.equal(spans[index - 1].span_id.toString()) + expect(spans[index].meta).to.have.property('component', 'express') + index++ + + if (!isExpress4) index++ + + expect(spans[index]).to.have.property('resource', '') + expect(spans[index]).to.have.property('name', 'express.middleware') + expect(spans[index].parent_id.toString()).to.equal(spans[index - 1].span_id.toString()) + expect(spans[index].meta).to.have.property('component', 'express') }) .then(done) .catch(done) @@ -291,7 +295,7 @@ describe('Plugin', () => { .use(traces => { const spans = sort(traces[0]) - const breakingSpanIndex = semver.intersects(version, '<5.0.0') ? 3 : 1 + const breakingSpanIndex = semver.intersects(version, '<5.0.0') ? 3 : 2 expect(spans[0]).to.have.property('resource', 'GET /user/:id') expect(spans[0]).to.have.property('name', 'express.request') @@ -337,7 +341,7 @@ describe('Plugin', () => { agent .use(traces => { const spans = sort(traces[0]) - const errorSpanIndex = semver.intersects(version, '<5.0.0') ? 4 : 2 + const errorSpanIndex = semver.intersects(version, '<5.0.0') ? 4 : 3 expect(spans[0]).to.have.property('name', 'express.request') expect(spans[errorSpanIndex]).to.have.property('name', 'express.middleware') diff --git a/packages/datadog-plugin-express/test/integration-test/client.spec.js b/packages/datadog-plugin-express/test/integration-test/client.spec.js index 2a8a8eb89b1..6aba0aef068 100644 --- a/packages/datadog-plugin-express/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-express/test/integration-test/client.spec.js @@ -37,9 +37,7 @@ describe('esm', () => { it('is instrumented', async () => { proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) - // express less than <5.0 uses their own router, which creates more middleware spans than the router - // that is used for v5+ - const numberofSpans = semver.intersects(version, '<5.0.0') ? 4 : 3 + const numberofSpans = semver.intersects(version, '<5.0.0') ? 4 : 5 return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js index e4f33ccb415..58b54e2c704 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-http.plugin.spec.js @@ -17,93 +17,91 @@ function assertFingerprintInTraces (traces) { assert.equal(span.meta['_dd.appsec.fp.http.endpoint'], 'http-post-7e93fba0--') } -withVersions('express', 'express', expressVersion => { - withVersions('passport-http', 'passport-http', version => { - describe('Attacker fingerprinting', () => { - let port, server, axios +withVersions('passport-http', 'passport-http', version => { + describe('Attacker fingerprinting', () => { + let port, server, axios - before(() => { - return agent.load(['express', 'http'], { client: false }) - }) + before(() => { + return agent.load(['express', 'http'], { client: false }) + }) - before(() => { - appsec.enable(new Config({ - appsec: true - })) - }) + before(() => { + appsec.enable(new Config({ + appsec: true + })) + }) - before((done) => { - const express = require(`../../../../versions/express@${expressVersion}`).get() - const bodyParser = require('../../../../versions/body-parser').get() - const passport = require('../../../../versions/passport').get() - const { BasicStrategy } = require(`../../../../versions/passport-http@${version}`).get() - - const app = express() - app.use(bodyParser.json()) - app.use(passport.initialize()) - - passport.use(new BasicStrategy( - function verify (username, password, done) { - if (username === 'success') { - done(null, { - id: 1234, - username - }) - } else { - done(null, false) - } + before((done) => { + const express = require('../../../../versions/express').get() + const bodyParser = require('../../../../versions/body-parser').get() + const passport = require('../../../../versions/passport').get() + const { BasicStrategy } = require(`../../../../versions/passport-http@${version}`).get() + + const app = express() + app.use(bodyParser.json()) + app.use(passport.initialize()) + + passport.use(new BasicStrategy( + function verify (username, password, done) { + if (username === 'success') { + done(null, { + id: 1234, + username + }) + } else { + done(null, false) } - )) + } + )) - app.post('/login', passport.authenticate('basic', { session: false }), function (req, res) { - res.end() - }) - - server = app.listen(port, () => { - port = server.address().port - axios = Axios.create({ - baseURL: `http://localhost:${port}` - }) - done() - }) + app.post('/login', passport.authenticate('basic', { session: false }), function (req, res) { + res.end() }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) - - after(() => { - appsec.disable() + server = app.listen(port, () => { + port = server.address().port + axios = Axios.create({ + baseURL: `http://localhost:${port}` + }) + done() }) + }) - it('should report http fingerprints on login fail', async () => { - try { - await axios.post( - `http://localhost:${port}/login`, {}, { - auth: { - username: 'fail', - password: '1234' - } - } - ) - } catch (e) {} + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - await agent.use(assertFingerprintInTraces) - }) + after(() => { + appsec.disable() + }) - it('should report http fingerprints on login successful', async () => { + it('should report http fingerprints on login fail', async () => { + try { await axios.post( `http://localhost:${port}/login`, {}, { auth: { - username: 'success', + username: 'fail', password: '1234' } } ) + } catch (e) {} - await agent.use(assertFingerprintInTraces) - }) + await agent.use(assertFingerprintInTraces) + }) + + it('should report http fingerprints on login successful', async () => { + await axios.post( + `http://localhost:${port}/login`, {}, { + auth: { + username: 'success', + password: '1234' + } + } + ) + + await agent.use(assertFingerprintInTraces) }) }) }) diff --git a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js index 680d312c58d..b51aa57de9c 100644 --- a/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js +++ b/packages/dd-trace/test/appsec/attacker-fingerprinting.passport-local.plugin.spec.js @@ -17,91 +17,89 @@ function assertFingerprintInTraces (traces) { assert.equal(span.meta['_dd.appsec.fp.http.endpoint'], 'http-post-7e93fba0--f29f6224') } -withVersions('express', 'express', expressVersion => { - withVersions('passport-local', 'passport-local', version => { - describe('Attacker fingerprinting', () => { - let port, server, axios +withVersions('passport-local', 'passport-local', version => { + describe('Attacker fingerprinting', () => { + let port, server, axios - before(() => { - return agent.load(['express', 'http'], { client: false }) - }) + before(() => { + return agent.load(['express', 'http'], { client: false }) + }) - before(() => { - appsec.enable(new Config({ - appsec: true - })) - }) + before(() => { + appsec.enable(new Config({ + appsec: true + })) + }) - before((done) => { - const express = require(`../../../../versions/express@${expressVersion}`).get() - const bodyParser = require('../../../../versions/body-parser').get() - const passport = require('../../../../versions/passport').get() - const LocalStrategy = require(`../../../../versions/passport-local@${version}`).get() - - const app = express() - app.use(bodyParser.json()) - app.use(passport.initialize()) - - passport.use(new LocalStrategy( - function verify (username, password, done) { - if (username === 'success') { - done(null, { - id: 1234, - username - }) - } else { - done(null, false) - } + before((done) => { + const express = require('../../../../versions/express').get() + const bodyParser = require('../../../../versions/body-parser').get() + const passport = require('../../../../versions/passport').get() + const LocalStrategy = require(`../../../../versions/passport-local@${version}`).get() + + const app = express() + app.use(bodyParser.json()) + app.use(passport.initialize()) + + passport.use(new LocalStrategy( + function verify (username, password, done) { + if (username === 'success') { + done(null, { + id: 1234, + username + }) + } else { + done(null, false) } - )) + } + )) - app.post('/login', passport.authenticate('local', { session: false }), function (req, res) { - res.end() - }) - - server = app.listen(port, () => { - port = server.address().port - axios = Axios.create({ - baseURL: `http://localhost:${port}` - }) - done() - }) + app.post('/login', passport.authenticate('local', { session: false }), function (req, res) { + res.end() }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) + server = app.listen(port, () => { + port = server.address().port + axios = Axios.create({ + baseURL: `http://localhost:${port}` + }) + done() }) + }) - after(() => { - appsec.disable() - }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - it('should report http fingerprints on login fail', async () => { - try { - await axios.post( - `http://localhost:${port}/login`, - { - username: 'fail', - password: '1234' - } - ) - } catch (e) {} - - await agent.use(assertFingerprintInTraces) - }) + after(() => { + appsec.disable() + }) - it('should report http fingerprints on login successful', async () => { + it('should report http fingerprints on login fail', async () => { + try { await axios.post( `http://localhost:${port}/login`, { - username: 'success', + username: 'fail', password: '1234' } ) + } catch (e) {} - await agent.use(assertFingerprintInTraces) - }) + await agent.use(assertFingerprintInTraces) + }) + + it('should report http fingerprints on login successful', async () => { + await axios.post( + `http://localhost:${port}/login`, + { + username: 'success', + password: '1234' + } + ) + + await agent.use(assertFingerprintInTraces) }) }) }) diff --git a/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js b/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js index df37ef1a745..458a69ee97d 100644 --- a/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.body-parser.plugin.spec.js @@ -7,64 +7,62 @@ const appsec = require('../../src/appsec') const Config = require('../../src/config') const { json } = require('../../src/appsec/blocked_templates') -withVersions('express', 'express', expressVersion => { - withVersions('body-parser', 'body-parser', version => { - describe('Suspicious request blocking - body-parser', () => { - let port, server, requestBody +withVersions('body-parser', 'body-parser', version => { + describe('Suspicious request blocking - body-parser', () => { + let port, server, requestBody - before(() => { - return agent.load(['express', 'body-parser', 'http'], { client: false }) - }) - - before((done) => { - const express = require(`../../../../versions/express@${expressVersion}`).get() - const bodyParser = require(`../../../../versions/body-parser@${version}`).get() + before(() => { + return agent.load(['express', 'body-parser', 'http'], { client: false }) + }) - const app = express() - app.use(bodyParser.json()) - app.post('/', (req, res) => { - requestBody() - res.end('DONE') - }) + before((done) => { + const express = require('../../../../versions/express').get() + const bodyParser = require(`../../../../versions/body-parser@${version}`).get() - server = app.listen(port, () => { - port = server.address().port - done() - }) + const app = express() + app.use(bodyParser.json()) + app.post('/', (req, res) => { + requestBody() + res.end('DONE') }) - beforeEach(async () => { - requestBody = sinon.stub() - appsec.enable(new Config({ appsec: { enabled: true, rules: path.join(__dirname, 'body-parser-rules.json') } })) + server = app.listen(port, () => { + port = server.address().port + done() }) + }) - afterEach(() => { - appsec.disable() - }) + beforeEach(async () => { + requestBody = sinon.stub() + appsec.enable(new Config({ appsec: { enabled: true, rules: path.join(__dirname, 'body-parser-rules.json') } })) + }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + afterEach(() => { + appsec.disable() + }) - it('should not block the request without an attack', async () => { - const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - expect(requestBody).to.be.calledOnce - expect(res.data).to.be.equal('DONE') - }) + it('should not block the request without an attack', async () => { + const res = await axios.post(`http://localhost:${port}/`, { key: 'value' }) - it('should block the request when attack is detected', async () => { - try { - await axios.post(`http://localhost:${port}/`, { key: 'testattack' }) + expect(requestBody).to.be.calledOnce + expect(res.data).to.be.equal('DONE') + }) - return Promise.reject(new Error('Request should not return 200')) - } catch (e) { - expect(e.response.status).to.be.equals(403) - expect(e.response.data).to.be.deep.equal(JSON.parse(json)) - expect(requestBody).not.to.be.called - } - }) + it('should block the request when attack is detected', async () => { + try { + await axios.post(`http://localhost:${port}/`, { key: 'testattack' }) + + return Promise.reject(new Error('Request should not return 200')) + } catch (e) { + expect(e.response.status).to.be.equals(403) + expect(e.response.data).to.be.deep.equal(JSON.parse(json)) + expect(requestBody).not.to.be.called + } }) }) }) diff --git a/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js b/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js index 8ec77ce8465..fed6bbcbf45 100644 --- a/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.cookie-parser.plugin.spec.js @@ -8,70 +8,66 @@ const appsec = require('../../src/appsec') const Config = require('../../src/config') const { json } = require('../../src/appsec/blocked_templates') -withVersions('express', 'express', expressVersion => { - withVersions('cookie-parser', 'cookie-parser', version => { - describe('Suspicious request blocking - cookie-parser', () => { - let port, server, requestCookie +withVersions('cookie-parser', 'cookie-parser', version => { + describe('Suspicious request blocking - cookie-parser', () => { + let port, server, requestCookie - before(() => { - return agent.load(['express', 'cookie-parser', 'http'], { client: false }) - }) - - before((done) => { - const express = require(`../../../../versions/express@${expressVersion}`).get() - const cookieParser = require(`../../../../versions/cookie-parser@${version}`).get() + before(() => { + return agent.load(['express', 'cookie-parser', 'http'], { client: false }) + }) - const app = express() - app.use(cookieParser()) - app.post('/', (req, res) => { - requestCookie() - res.end('DONE') - }) + before((done) => { + const express = require('../../../../versions/express').get() + const cookieParser = require(`../../../../versions/cookie-parser@${version}`).get() - server = app.listen(port, () => { - port = server.address().port - done() - }) + const app = express() + app.use(cookieParser()) + app.post('/', (req, res) => { + requestCookie() + res.end('DONE') }) - beforeEach(async () => { - requestCookie = sinon.stub() - appsec.enable(new Config({ - appsec: { enabled: true, rules: path.join(__dirname, 'cookie-parser-rules.json') } - })) + server = app.listen(port, () => { + port = server.address().port + done() }) + }) - afterEach(() => { - appsec.disable() - }) + beforeEach(async () => { + requestCookie = sinon.stub() + appsec.enable(new Config({ appsec: { enabled: true, rules: path.join(__dirname, 'cookie-parser-rules.json') } })) + }) - after(() => { - server.close() - return agent.close({ ritmReset: false }) - }) + afterEach(() => { + appsec.disable() + }) - it('should not block the request without an attack', async () => { - const res = await axios.post(`http://localhost:${port}/`, {}) + after(() => { + server.close() + return agent.close({ ritmReset: false }) + }) - sinon.assert.calledOnce(requestCookie) - assert.strictEqual(res.data, 'DONE') - }) + it('should not block the request without an attack', async () => { + const res = await axios.post(`http://localhost:${port}/`, {}) - it('should block the request when attack is detected', async () => { - try { - await axios.post(`http://localhost:${port}/`, {}, { - headers: { - Cookie: 'key=testattack' - } - }) + sinon.assert.calledOnce(requestCookie) + assert.strictEqual(res.data, 'DONE') + }) - return Promise.reject(new Error('Request should not return 200')) - } catch (e) { - assert.strictEqual(e.response.status, 403) - assert.deepEqual(e.response.data, JSON.parse(json)) - sinon.assert.notCalled(requestCookie) - } - }) + it('should block the request when attack is detected', async () => { + try { + await axios.post(`http://localhost:${port}/`, {}, { + headers: { + Cookie: 'key=testattack' + } + }) + + return Promise.reject(new Error('Request should not return 200')) + } catch (e) { + assert.strictEqual(e.response.status, 403) + assert.deepEqual(e.response.data, JSON.parse(json)) + sinon.assert.notCalled(requestCookie) + } }) }) }) diff --git a/packages/dd-trace/test/appsec/index.express.plugin.spec.js b/packages/dd-trace/test/appsec/index.express.plugin.spec.js index 6ce418b6bbb..95ffa66ea24 100644 --- a/packages/dd-trace/test/appsec/index.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.express.plugin.spec.js @@ -173,7 +173,6 @@ withVersions('express', 'express', version => { } catch (e) { assert.equal(e.response.status, 403) assert.deepEqual(e.response.data, JSON.parse(json)) - // to be fixed sinon.assert.notCalled(paramCallbackSpy) } }) From 603c76d79db59a76c929148c3a249aef27e5e483 Mon Sep 17 00:00:00 2001 From: ishabi Date: Fri, 22 Nov 2024 16:41:41 +0100 Subject: [PATCH 13/31] fix router tests --- packages/datadog-instrumentations/src/router.js | 9 ++++++++- packages/datadog-plugin-router/test/index.spec.js | 5 +++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 4eedef41752..e841b3a8508 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -169,7 +169,14 @@ function createWrapRouterMethod (name) { const wrapRouterMethod = createWrapRouterMethod('router') -addHook({ name: 'router', versions: ['>=1'] }, Router => { +addHook({ name: 'router', versions: ['>=1 <2'] }, Router => { + shimmer.wrap(Router.prototype, 'use', wrapRouterMethod) + shimmer.wrap(Router.prototype, 'route', wrapRouterMethod) + + return Router +}) + +addHook({ name: 'router', versions: ['>=2'] }, Router => { const originalRouter = Router function WrappedRouter (options) { diff --git a/packages/datadog-plugin-router/test/index.spec.js b/packages/datadog-plugin-router/test/index.spec.js index ac208f0e2a1..31c3cde8bf5 100644 --- a/packages/datadog-plugin-router/test/index.spec.js +++ b/packages/datadog-plugin-router/test/index.spec.js @@ -71,8 +71,9 @@ describe('Plugin', () => { }) router.use('/parent', childRouter) - expect(router.stack[0].handle.hello).to.equal('goodbye') - expect(router.stack[0].handle.foo).to.equal('bar') + const index = router.stack.length - 1 + expect(router.stack[index].handle.hello).to.equal('goodbye') + expect(router.stack[index].handle.foo).to.equal('bar') }) it('should add the route to the request span', done => { From efa3f197d670b648f3d4fe58104f2a2aa3fdcb64 Mon Sep 17 00:00:00 2001 From: ishabi Date: Sat, 23 Nov 2024 22:40:17 +0100 Subject: [PATCH 14/31] abort if response ended --- .../datadog-instrumentations/src/router.js | 18 ++++++--- .../datadog-plugin-express/test/index.spec.js | 40 +++++++++++-------- .../test/integration-test/client.spec.js | 2 +- .../test/appsec/index.express.plugin.spec.js | 2 +- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index e841b3a8508..36a9be9ce8e 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -182,13 +182,19 @@ addHook({ name: 'router', versions: ['>=2'] }, Router => { function WrappedRouter (options) { const router = originalRouter.call(this, options) - // Add query parsing middleware - if (!router._queryMiddlewareAdded) { - router._queryMiddlewareAdded = true - router.use(function query (req, res, next) { + if (!router._queryParsingWrapped) { + router._queryParsingWrapped = true + + const originalHandle = router.handle + router.handle = function (req, res, next) { req.query - next() - }) + + // If query parsing was aborted, don't continue request handling + if (res.writableEnded) { + return + } + return originalHandle.apply(this, arguments) + } } return router diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index bab36ac0572..9eded3fa96a 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -218,48 +218,54 @@ describe('Plugin', () => { const isExpress4 = semver.intersects(version, '<5.0.0') let index = 0 - expect(spans[index]).to.have.property('resource', 'GET /app/user/:id') - expect(spans[index]).to.have.property('name', 'express.request') - expect(spans[index].meta).to.have.property('component', 'express') - index++ - - expect(spans[index]).to.have.property('resource', 'query') - expect(spans[index]).to.have.property('name', 'express.middleware') - expect(spans[index].parent_id.toString()).to.equal(spans[0].span_id.toString()) - expect(spans[index].meta).to.have.property('component', 'express') - index++ + const rootSpan = spans[index++] + expect(rootSpan).to.have.property('resource', 'GET /app/user/:id') + expect(rootSpan).to.have.property('name', 'express.request') + expect(rootSpan.meta).to.have.property('component', 'express') if (isExpress4) { + expect(spans[index]).to.have.property('resource', 'query') + expect(spans[index]).to.have.property('name', 'express.middleware') + expect(spans[index].parent_id.toString()).to.equal(rootSpan.span_id.toString()) + expect(spans[index].meta).to.have.property('component', 'express') + index++ + expect(spans[index]).to.have.property('resource', 'expressInit') expect(spans[index]).to.have.property('name', 'express.middleware') - expect(spans[index].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[index].parent_id.toString()).to.equal(rootSpan.span_id.toString()) expect(spans[index].meta).to.have.property('component', 'express') index++ } expect(spans[index]).to.have.property('resource', 'named') expect(spans[index]).to.have.property('name', 'express.middleware') - expect(spans[index].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[index].parent_id.toString()).to.equal(rootSpan.span_id.toString()) expect(spans[index].meta).to.have.property('component', 'express') index++ expect(spans[index]).to.have.property('resource', 'router') expect(spans[index]).to.have.property('name', 'express.middleware') - expect(spans[index].parent_id.toString()).to.equal(spans[0].span_id.toString()) + expect(spans[index].parent_id.toString()).to.equal(rootSpan.span_id.toString()) expect(spans[index].meta).to.have.property('component', 'express') index++ + const handleResource = isExpress4 ? /^bound\s.*$/ : 'handle' + if (isExpress4) { + expect(spans[index].resource).to.match(handleResource) + } else { + expect(spans[index]).to.have.property('resource', handleResource) + } expect(spans[index]).to.have.property('name', 'express.middleware') expect(spans[index].parent_id.toString()).to.equal(spans[index - 1].span_id.toString()) expect(spans[index].meta).to.have.property('component', 'express') index++ - if (!isExpress4) index++ - expect(spans[index]).to.have.property('resource', '') expect(spans[index]).to.have.property('name', 'express.middleware') expect(spans[index].parent_id.toString()).to.equal(spans[index - 1].span_id.toString()) expect(spans[index].meta).to.have.property('component', 'express') + + expect(index).to.equal(spans.length - 1) }) .then(done) .catch(done) @@ -295,7 +301,7 @@ describe('Plugin', () => { .use(traces => { const spans = sort(traces[0]) - const breakingSpanIndex = semver.intersects(version, '<5.0.0') ? 3 : 2 + const breakingSpanIndex = semver.intersects(version, '<5.0.0') ? 3 : 1 expect(spans[0]).to.have.property('resource', 'GET /user/:id') expect(spans[0]).to.have.property('name', 'express.request') @@ -341,7 +347,7 @@ describe('Plugin', () => { agent .use(traces => { const spans = sort(traces[0]) - const errorSpanIndex = semver.intersects(version, '<5.0.0') ? 4 : 3 + const errorSpanIndex = semver.intersects(version, '<5.0.0') ? 4 : 2 expect(spans[0]).to.have.property('name', 'express.request') expect(spans[errorSpanIndex]).to.have.property('name', 'express.middleware') diff --git a/packages/datadog-plugin-express/test/integration-test/client.spec.js b/packages/datadog-plugin-express/test/integration-test/client.spec.js index 6aba0aef068..1f884442019 100644 --- a/packages/datadog-plugin-express/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-express/test/integration-test/client.spec.js @@ -37,7 +37,7 @@ describe('esm', () => { it('is instrumented', async () => { proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) - const numberofSpans = semver.intersects(version, '<5.0.0') ? 4 : 5 + const numberofSpans = semver.intersects(version, '<5.0.0') ? 4 : 3 return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) diff --git a/packages/dd-trace/test/appsec/index.express.plugin.spec.js b/packages/dd-trace/test/appsec/index.express.plugin.spec.js index 95ffa66ea24..1c6a8aeb86d 100644 --- a/packages/dd-trace/test/appsec/index.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.express.plugin.spec.js @@ -179,7 +179,7 @@ withVersions('express', 'express', version => { }) }) - describe.skip('Suspicious request blocking - query', () => { + describe('Suspicious request blocking - query', () => { let server, requestBody, axios before(() => { From 6abe0864e356983007e19c5963a562a25cae1ba5 Mon Sep 17 00:00:00 2001 From: ishabi Date: Mon, 25 Nov 2024 09:42:19 +0100 Subject: [PATCH 15/31] instrument node querystring --- .../src/helpers/hooks.js | 2 + .../src/querystring.js | 23 +++++ .../test/querystring.spec.js | 90 +++++++++++++++++++ .../src/appsec/iast/taint-tracking/plugin.js | 15 ++-- .../appsec/iast/taint-tracking/plugin.spec.js | 10 +-- 5 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 packages/datadog-instrumentations/src/querystring.js create mode 100644 packages/datadog-instrumentations/test/querystring.spec.js diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js index 4261d4dae44..572f106dc0b 100644 --- a/packages/datadog-instrumentations/src/helpers/hooks.js +++ b/packages/datadog-instrumentations/src/helpers/hooks.js @@ -95,6 +95,7 @@ module.exports = { 'node:http2': () => require('../http2'), 'node:https': () => require('../http'), 'node:net': () => require('../net'), + 'node:querystring': () => require('../querystring'), 'node:url': () => require('../url'), nyc: () => require('../nyc'), oracledb: () => require('../oracledb'), @@ -112,6 +113,7 @@ module.exports = { pug: () => require('../pug'), q: () => require('../q'), qs: () => require('../qs'), + querystring: () => require('../querystring'), redis: () => require('../redis'), restify: () => require('../restify'), rhea: () => require('../rhea'), diff --git a/packages/datadog-instrumentations/src/querystring.js b/packages/datadog-instrumentations/src/querystring.js new file mode 100644 index 00000000000..d4e78f9bd95 --- /dev/null +++ b/packages/datadog-instrumentations/src/querystring.js @@ -0,0 +1,23 @@ +'use strict' + +const { addHook, channel } = require('./helpers/instrument') +const shimmer = require('../../datadog-shimmer') +const names = ['querystring', 'node:querystring'] + +const querystringParseCh = channel('datadog:querystring:parse:finish') + +addHook({ name: names }, function (querystring) { + shimmer.wrap(querystring, 'parse', function (parse) { + function wrappedMethod () { + const qs = parse.apply(this, arguments) + if (querystringParseCh.hasSubscribers && qs) { + querystringParseCh.publish({ qs }) + } + + return qs + } + + return wrappedMethod + }) + return querystring +}) diff --git a/packages/datadog-instrumentations/test/querystring.spec.js b/packages/datadog-instrumentations/test/querystring.spec.js new file mode 100644 index 00000000000..b7f51e67e5b --- /dev/null +++ b/packages/datadog-instrumentations/test/querystring.spec.js @@ -0,0 +1,90 @@ +'use strict' + +const agent = require('../../dd-trace/test/plugins/agent') +const { channel } = require('../src/helpers/instrument') +const names = ['querystring', 'node:querystring'] + +names.forEach(name => { + describe(name, () => { + const querystring = require(name) + const querystringParseCh = channel('datadog:querystring:parse:finish') + let querystringParseChCb + + before(async () => { + await agent.load('querystring') + }) + + after(() => { + return agent.close() + }) + + beforeEach(() => { + querystringParseChCb = sinon.stub() + querystringParseCh.subscribe(querystringParseChCb) + }) + + afterEach(() => { + querystringParseCh.unsubscribe(querystringParseChCb) + }) + + describe('querystring.parse', () => { + it('should publish parsed empty query string', () => { + const result = querystring.parse('') + + sinon.assert.calledOnceWithExactly(querystringParseChCb, { + qs: result + }, sinon.match.any) + }) + + it('should publish parsed query string with single parameter', () => { + const result = querystring.parse('foo=bar') + + sinon.assert.calledOnceWithExactly(querystringParseChCb, { + qs: result + }, sinon.match.any) + + expect(result).to.deep.equal({ foo: 'bar' }) + }) + + it('should publish parsed query string with multiple parameters', () => { + const result = querystring.parse('foo=bar&baz=qux') + + sinon.assert.calledOnceWithExactly(querystringParseChCb, { + qs: result + }, sinon.match.any) + + expect(result).to.deep.equal({ foo: 'bar', baz: 'qux' }) + }) + + it('should publish parsed query string with encoded values', () => { + const result = querystring.parse('message=hello%20world') + + sinon.assert.calledOnceWithExactly(querystringParseChCb, { + qs: result + }, sinon.match.any) + + expect(result).to.deep.equal({ message: 'hello world' }) + }) + + it('should publish parsed query string with array parameter', () => { + const result = querystring.parse('items=1&items=2') + + sinon.assert.calledOnceWithExactly(querystringParseChCb, { + qs: result + }, sinon.match.any) + + expect(result).to.have.property('items') + }) + + it('should handle null or undefined query string', () => { + const result = querystring.parse(null) + + sinon.assert.calledOnceWithExactly(querystringParseChCb, { + qs: result + }, sinon.match.any) + + expect(result).to.deep.equal({}) + }) + }) + }) +}) diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js index 025234c3c56..cce3de7c800 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js @@ -12,7 +12,6 @@ const { HTTP_REQUEST_HEADER_NAME, HTTP_REQUEST_PARAMETER, HTTP_REQUEST_PATH_PARAM, - HTTP_REQUEST_QUERY, HTTP_REQUEST_URI } = require('./source-types') const { EXECUTED_SOURCE } = require('../telemetry/iast-metric') @@ -51,6 +50,11 @@ class TaintTrackingPlugin extends SourceIastPlugin { ({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs) ) + this.addSub( + { channelName: 'datadog:querystring:parse:finish', tag: HTTP_REQUEST_PARAMETER }, + ({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs) + ) + this.addSub( { channelName: 'apm:express:middleware:next', tag: HTTP_REQUEST_BODY }, ({ req }) => { @@ -87,15 +91,6 @@ class TaintTrackingPlugin extends SourceIastPlugin { } ) - this.addSub( - { channelName: 'datadog:query:read:finish', tag: HTTP_REQUEST_QUERY }, - ({ query }) => { - if (query !== null && typeof query === 'object') { - this._taintTrackingHandler(HTTP_REQUEST_QUERY, query) - } - } - ) - this.addSub( { channelName: 'apm:graphql:resolve:start', tag: HTTP_REQUEST_BODY }, (data) => { diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js index 14cc58e6355..5ac1df8c5b1 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js @@ -47,11 +47,11 @@ describe('IAST Taint tracking plugin', () => { expect(taintTrackingPlugin._subscriptions[0]._channel.name).to.equals('datadog:body-parser:read:finish') expect(taintTrackingPlugin._subscriptions[1]._channel.name).to.equals('datadog:multer:read:finish') expect(taintTrackingPlugin._subscriptions[2]._channel.name).to.equals('datadog:qs:parse:finish') - expect(taintTrackingPlugin._subscriptions[3]._channel.name).to.equals('apm:express:middleware:next') - expect(taintTrackingPlugin._subscriptions[4]._channel.name).to.equals('datadog:cookie:parse:finish') - expect(taintTrackingPlugin._subscriptions[5]._channel.name).to.equals('datadog:express:process_params:start') - expect(taintTrackingPlugin._subscriptions[6]._channel.name).to.equals('datadog:router:param:start') - expect(taintTrackingPlugin._subscriptions[7]._channel.name).to.equals('datadog:query:read:finish') + expect(taintTrackingPlugin._subscriptions[3]._channel.name).to.equals('datadog:querystring:parse:finish') + expect(taintTrackingPlugin._subscriptions[4]._channel.name).to.equals('apm:express:middleware:next') + expect(taintTrackingPlugin._subscriptions[5]._channel.name).to.equals('datadog:cookie:parse:finish') + expect(taintTrackingPlugin._subscriptions[6]._channel.name).to.equals('datadog:express:process_params:start') + expect(taintTrackingPlugin._subscriptions[7]._channel.name).to.equals('datadog:router:param:start') expect(taintTrackingPlugin._subscriptions[8]._channel.name).to.equals('apm:graphql:resolve:start') expect(taintTrackingPlugin._subscriptions[9]._channel.name).to.equals('datadog:url:parse:finish') expect(taintTrackingPlugin._subscriptions[10]._channel.name).to.equals('datadog:url:getter:finish') From d3421509102f2f0c8deb16a245eddf0617592c62 Mon Sep 17 00:00:00 2001 From: ishabi Date: Mon, 25 Nov 2024 10:51:21 +0100 Subject: [PATCH 16/31] lint code --- .../src/querystring.js | 5 ++- .../datadog-instrumentations/src/router.js | 43 +++++++++---------- .../iast/taint-tracking/source-types.js | 1 - 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/packages/datadog-instrumentations/src/querystring.js b/packages/datadog-instrumentations/src/querystring.js index d4e78f9bd95..a95f5c4b480 100644 --- a/packages/datadog-instrumentations/src/querystring.js +++ b/packages/datadog-instrumentations/src/querystring.js @@ -8,7 +8,7 @@ const querystringParseCh = channel('datadog:querystring:parse:finish') addHook({ name: names }, function (querystring) { shimmer.wrap(querystring, 'parse', function (parse) { - function wrappedMethod () { + function wrappedParse () { const qs = parse.apply(this, arguments) if (querystringParseCh.hasSubscribers && qs) { querystringParseCh.publish({ qs }) @@ -17,7 +17,8 @@ addHook({ name: names }, function (querystring) { return qs } - return wrappedMethod + return wrappedParse }) + return querystring }) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 36a9be9ce8e..a476bbc44e8 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -184,21 +184,21 @@ addHook({ name: 'router', versions: ['>=2'] }, Router => { if (!router._queryParsingWrapped) { router._queryParsingWrapped = true - const originalHandle = router.handle router.handle = function (req, res, next) { req.query - // If query parsing was aborted, don't continue request handling if (res.writableEnded) { return } + return originalHandle.apply(this, arguments) } } return router } + WrappedRouter.prototype = originalRouter.prototype shimmer.wrap(WrappedRouter.prototype, 'use', wrapRouterMethod) @@ -207,42 +207,41 @@ addHook({ name: 'router', versions: ['>=2'] }, Router => { return WrappedRouter }) -const processParamsStartCh = channel('datadog:express:process_params:start') +const routerParamStartCh = channel('datadog:router:param:start') -function createWrapLayerMethod () { - return function wrapMethod (original) { - return function wrappedHandleRequest (req, res, next) { - if (processParamsStartCh.hasSubscribers) { - const abortController = new AbortController() +function wrapHandleRequest (original) { + return function wrappedHandleRequest (req, res, next) { + if (routerParamStartCh.hasSubscribers) { + const abortController = new AbortController() - processParamsStartCh.publish({ - req, - res, - params: req?.params, - abortController - }) - if (abortController.signal.aborted) return - } + routerParamStartCh.publish({ + req, + res, + params: req?.params, + abortController + }) - return original.apply(this, arguments) + if (abortController.signal.aborted) return } + + return original.apply(this, arguments) } } addHook({ name: 'router', file: 'lib/layer.js', versions: ['>=2'] }, Layer => { - shimmer.wrap(Layer.prototype, 'handleRequest', createWrapLayerMethod()) + shimmer.wrap(Layer.prototype, 'handleRequest', wrapHandleRequest) return Layer }) -function wrapProcessParamsMethod (original) { +function wrapParam (original) { return function wrappedProcessParams (name, fn) { const wrappedFn = function wrappedParamCallback (req, res, next, param) { - if (processParamsStartCh.hasSubscribers) { + if (routerParamStartCh.hasSubscribers) { const abortController = new AbortController() - processParamsStartCh.publish({ + routerParamStartCh.publish({ req, res, params: req?.params, @@ -262,7 +261,7 @@ function wrapProcessParamsMethod (original) { addHook({ name: 'router', versions: ['>=2'] }, router => { - shimmer.wrap(router.prototype, 'param', wrapProcessParamsMethod) + shimmer.wrap(router.prototype, 'param', wrapParam) return router }) diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js b/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js index 9a810baafb1..f5c2ca2e8b0 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js @@ -9,7 +9,6 @@ module.exports = { HTTP_REQUEST_PARAMETER: 'http.request.parameter', HTTP_REQUEST_PATH: 'http.request.path', HTTP_REQUEST_PATH_PARAM: 'http.request.path.parameter', - HTTP_REQUEST_QUERY: 'http.request.query', HTTP_REQUEST_URI: 'http.request.uri', KAFKA_MESSAGE_KEY: 'kafka.message.key', KAFKA_MESSAGE_VALUE: 'kafka.message.value' From b062e54021fd7beb9a8b3ccf10633edcfbfeb270 Mon Sep 17 00:00:00 2001 From: ishabi Date: Mon, 25 Nov 2024 12:50:52 +0100 Subject: [PATCH 17/31] fix mongodb core and mongoose express version --- .../datadog-plugin-express/test/index.spec.js | 18 ++++-------------- .../taint-tracking.express.plugin.spec.js | 7 ++----- packages/dd-trace/test/plugins/externals.json | 4 ++-- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 9eded3fa96a..9d39222bf81 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -424,19 +424,15 @@ describe('Plugin', () => { const app = express() const router = express.Router() - // supported only in express<5 - if (semver.intersects(version, '<5.0.0')) { - router.use('*', (req, res, next) => next()) - app.use('*', (req, res, next) => next()) - } - router.use('/', (req, res, next) => next()) + router.use('/*splat', (req, res, next) => next()) router.use('/bar', (req, res, next) => next()) router.use('/bar', (req, res, next) => { res.status(200).send() }) app.use('/', (req, res, next) => next()) + app.use('/*splat', (req, res, next) => next()) app.use('/foo/bar', (req, res, next) => next()) app.use('/foo', router) @@ -1232,15 +1228,9 @@ describe('Plugin', () => { }) it('should support capturing groups in routes', done => { - // not supported in Express5 - if (semver.intersects(version, '>=5.0.0')) { - done() - return - } - const app = express() - app.get('/:path(*)', (req, res) => { + app.get('/*user', (req, res) => { res.status(200).send() }) @@ -1251,7 +1241,7 @@ describe('Plugin', () => { .use(traces => { const spans = sort(traces[0]) - expect(spans[0]).to.have.property('resource', 'GET /:path(*)') + expect(spans[0]).to.have.property('resource', 'GET /*user') expect(spans[0].meta).to.have.property('http.url', `http://localhost:${port}/user`) }) .then(done) diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index ce348382a2f..e8bb7e497ca 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -47,12 +47,9 @@ describe('URI sourcing with express', () => { }) it('should taint uri', function (done) { - // not supported express5 - if (!semver.satisfies(version, '<5')) { - this.skip() - } const app = express() - app.get('/path/*', (req, res) => { + const pathPattern = semver.intersects(version, '>=5.0.0') ? '/path/*splat' : '/path/*' + app.get(pathPattern, (req, res) => { const store = storage.getStore() const iastContext = iastContextFunctions.getIastContext(store) const isPathTainted = isTainted(iastContext, req.url) diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 600df395d84..1b1d43150c5 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -98,7 +98,7 @@ }, { "name": "express", - "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.3.0"] + "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.0.0 <5.0.0", ">=4.3.0 <5.0.0", ">=5.0.0"] }, { "name": "body-parser", @@ -332,7 +332,7 @@ }, { "name": "express", - "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.3.0"] + "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.0.0 <5.0.0", ">=4.3.0 <5.0.0", ">=5.0.0"] }, { "name": "body-parser", From 501c3068b04797d36f0b0c6cf41bbf268e124ed9 Mon Sep 17 00:00:00 2001 From: ishabi Date: Mon, 25 Nov 2024 13:33:05 +0100 Subject: [PATCH 18/31] fix mongoose and express tests --- .../nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js index f09264225a9..4df740cd890 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js @@ -10,7 +10,7 @@ const fs = require('fs') const { NODE_MAJOR } = require('../../../../../../version') describe('nosql injection detection in mongodb - whole feature', () => { - withVersions('express', 'express', '>4.18.0', expressVersion => { + withVersions('mongoose', 'express', '>4.18.0 <5.0.0', expressVersion => { withVersions('mongoose', 'mongoose', '>4.0.0', mongooseVersion => { const specificMongooseVersion = require(`../../../../../../versions/mongoose@${mongooseVersion}`).version() if (NODE_MAJOR === 14 && semver.satisfies(specificMongooseVersion, '>=8')) return From 83ff4c8597364988e1c6437ecb67421bc698821f Mon Sep 17 00:00:00 2001 From: ishabi Date: Mon, 25 Nov 2024 13:48:51 +0100 Subject: [PATCH 19/31] escape express5 in testing express-mongo-sanitize --- ...yzer.express-mongo-sanitize.plugin.spec.js | 43 +++++++++-------- ...ion-mongodb-analyzer.mquery.plugin.spec.js | 47 ++++++++++--------- packages/dd-trace/test/plugins/externals.json | 2 +- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js index e05537ce04b..d7623bffc0b 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js @@ -9,7 +9,7 @@ const { prepareTestServerForIastInExpress } = require('../utils') const agent = require('../../../plugins/agent') describe('nosql injection detection in mongodb - whole feature', () => { - withVersions('express', 'express', '>4.18.0', expressVersion => { + withVersions('mongodb', 'express', '>4.18.0 <5.0.0', expressVersion => { withVersions('mongodb', 'mongodb', mongodbVersion => { const mongodb = require(`../../../../../../versions/mongodb@${mongodbVersion}`) @@ -155,27 +155,30 @@ describe('nosql injection detection in mongodb - whole feature', () => { redactionEnabled: false }) - withVersions('express-mongo-sanitize', 'express-mongo-sanitize', expressMongoSanitizeVersion => { - prepareTestServerForIastInExpress('Test with sanitization middleware', expressVersion, (expressApp) => { - const mongoSanitize = - require(`../../../../../../versions/express-mongo-sanitize@${expressMongoSanitizeVersion}`).get() - expressApp.use(mongoSanitize()) - }, (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { - testThatRequestHasNoVulnerability({ - fn: async (req, res) => { - await collection.find({ - key: req.query.key - }) - - res.end() - }, - vulnerability: 'NOSQL_MONGODB_INJECTION', - makeRequest: (done, config) => { - axios.get(`http://localhost:${config.port}/?key=value`).catch(done) - } + // https://github.com/fiznool/express-mongo-sanitize/issues/200 + if (semver.intersects(expressVersion, '<5.0.0')) { + withVersions('express-mongo-sanitize', 'express-mongo-sanitize', expressMongoSanitizeVersion => { + prepareTestServerForIastInExpress('Test with sanitization middleware', expressVersion, (expressApp) => { + const mongoSanitize = + require(`../../../../../../versions/express-mongo-sanitize@${expressMongoSanitizeVersion}`).get() + expressApp.use(mongoSanitize()) + }, (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { + testThatRequestHasNoVulnerability({ + fn: async (req, res) => { + await collection.find({ + key: req.query.key + }) + + res.end() + }, + vulnerability: 'NOSQL_MONGODB_INJECTION', + makeRequest: (done, config) => { + axios.get(`http://localhost:${config.port}/?key=value`).catch(done) + } + }) }) }) - }) + } }) }) }) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js index 7cf71f7a86e..f4b1ee05b5c 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js @@ -9,7 +9,7 @@ const semver = require('semver') const fs = require('fs') describe('nosql injection detection with mquery', () => { - withVersions('express', 'express', '>4.18.0', expressVersion => { + withVersions('mongodb', 'express', '>4.18.0 <5.0.0', expressVersion => { withVersions('mongodb', 'mongodb', mongodbVersion => { const mongodb = require(`../../../../../../versions/mongodb@${mongodbVersion}`) @@ -313,31 +313,34 @@ describe('nosql injection detection with mquery', () => { }, 'NOSQL_MONGODB_INJECTION') }) - withVersions('express-mongo-sanitize', 'express-mongo-sanitize', expressMongoSanitizeVersion => { - prepareTestServerForIastInExpress('Test with sanitization middleware', expressVersion, (expressApp) => { - const mongoSanitize = + // https://github.com/fiznool/express-mongo-sanitize/issues/200 + if (semver.intersects(expressVersion, '<5.0.0')) { + withVersions('express-mongo-sanitize', 'express-mongo-sanitize', expressMongoSanitizeVersion => { + prepareTestServerForIastInExpress('Test with sanitization middleware', expressVersion, (expressApp) => { + const mongoSanitize = require(`../../../../../../versions/express-mongo-sanitize@${expressMongoSanitizeVersion}`).get() - expressApp.use(mongoSanitize()) - }, (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { - testThatRequestHasNoVulnerability({ - fn: async (req, res) => { - const filter = { - name: req.query.key - } - try { - await require(tmpFilePath).vulnerableFindOne(collection, filter) - } catch (e) { - // do nothing + expressApp.use(mongoSanitize()) + }, (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { + testThatRequestHasNoVulnerability({ + fn: async (req, res) => { + const filter = { + name: req.query.key + } + try { + await require(tmpFilePath).vulnerableFindOne(collection, filter) + } catch (e) { + // do nothing + } + res.end() + }, + vulnerability: 'NOSQL_MONGODB_INJECTION', + makeRequest: (done, config) => { + axios.get(`http://localhost:${config.port}/?key=value`).catch(done) } - res.end() - }, - vulnerability: 'NOSQL_MONGODB_INJECTION', - makeRequest: (done, config) => { - axios.get(`http://localhost:${config.port}/?key=value`).catch(done) - } + }) }) }) - }) + } }) }) }) diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 1b1d43150c5..d045e07e1dc 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -98,7 +98,7 @@ }, { "name": "express", - "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.0.0 <5.0.0", ">=4.3.0 <5.0.0", ">=5.0.0"] + "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.0.0 <5.0.0", ">=4.3.0 <5.0.0"] }, { "name": "body-parser", From 9427caf5f3184ff7a2ee8fa024c117f0fdb3d83a Mon Sep 17 00:00:00 2001 From: ishabi Date: Mon, 25 Nov 2024 15:13:51 +0100 Subject: [PATCH 20/31] fix mongodb core tests --- ...yzer.express-mongo-sanitize.plugin.spec.js | 42 ++++++++-------- ...n-mongodb-analyzer.mongoose.plugin.spec.js | 1 + ...ion-mongodb-analyzer.mquery.plugin.spec.js | 48 +++++++++---------- 3 files changed, 44 insertions(+), 47 deletions(-) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js index d7623bffc0b..f1042142100 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.express-mongo-sanitize.plugin.spec.js @@ -9,6 +9,7 @@ const { prepareTestServerForIastInExpress } = require('../utils') const agent = require('../../../plugins/agent') describe('nosql injection detection in mongodb - whole feature', () => { + // https://github.com/fiznool/express-mongo-sanitize/issues/200 withVersions('mongodb', 'express', '>4.18.0 <5.0.0', expressVersion => { withVersions('mongodb', 'mongodb', mongodbVersion => { const mongodb = require(`../../../../../../versions/mongodb@${mongodbVersion}`) @@ -155,30 +156,27 @@ describe('nosql injection detection in mongodb - whole feature', () => { redactionEnabled: false }) - // https://github.com/fiznool/express-mongo-sanitize/issues/200 - if (semver.intersects(expressVersion, '<5.0.0')) { - withVersions('express-mongo-sanitize', 'express-mongo-sanitize', expressMongoSanitizeVersion => { - prepareTestServerForIastInExpress('Test with sanitization middleware', expressVersion, (expressApp) => { - const mongoSanitize = - require(`../../../../../../versions/express-mongo-sanitize@${expressMongoSanitizeVersion}`).get() - expressApp.use(mongoSanitize()) - }, (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { - testThatRequestHasNoVulnerability({ - fn: async (req, res) => { - await collection.find({ - key: req.query.key - }) - - res.end() - }, - vulnerability: 'NOSQL_MONGODB_INJECTION', - makeRequest: (done, config) => { - axios.get(`http://localhost:${config.port}/?key=value`).catch(done) - } - }) + withVersions('express-mongo-sanitize', 'express-mongo-sanitize', expressMongoSanitizeVersion => { + prepareTestServerForIastInExpress('Test with sanitization middleware', expressVersion, (expressApp) => { + const mongoSanitize = + require(`../../../../../../versions/express-mongo-sanitize@${expressMongoSanitizeVersion}`).get() + expressApp.use(mongoSanitize()) + }, (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { + testThatRequestHasNoVulnerability({ + fn: async (req, res) => { + await collection.find({ + key: req.query.key + }) + + res.end() + }, + vulnerability: 'NOSQL_MONGODB_INJECTION', + makeRequest: (done, config) => { + axios.get(`http://localhost:${config.port}/?key=value`).catch(done) + } }) }) - } + }) }) }) }) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js index 4df740cd890..f8b96557a16 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js @@ -10,6 +10,7 @@ const fs = require('fs') const { NODE_MAJOR } = require('../../../../../../version') describe('nosql injection detection in mongodb - whole feature', () => { + // https://github.com/fiznool/express-mongo-sanitize/issues/200 withVersions('mongoose', 'express', '>4.18.0 <5.0.0', expressVersion => { withVersions('mongoose', 'mongoose', '>4.0.0', mongooseVersion => { const specificMongooseVersion = require(`../../../../../../versions/mongoose@${mongooseVersion}`).version() diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js index f4b1ee05b5c..a91b428211c 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mquery.plugin.spec.js @@ -9,6 +9,7 @@ const semver = require('semver') const fs = require('fs') describe('nosql injection detection with mquery', () => { + // https://github.com/fiznool/express-mongo-sanitize/issues/200 withVersions('mongodb', 'express', '>4.18.0 <5.0.0', expressVersion => { withVersions('mongodb', 'mongodb', mongodbVersion => { const mongodb = require(`../../../../../../versions/mongodb@${mongodbVersion}`) @@ -313,34 +314,31 @@ describe('nosql injection detection with mquery', () => { }, 'NOSQL_MONGODB_INJECTION') }) - // https://github.com/fiznool/express-mongo-sanitize/issues/200 - if (semver.intersects(expressVersion, '<5.0.0')) { - withVersions('express-mongo-sanitize', 'express-mongo-sanitize', expressMongoSanitizeVersion => { - prepareTestServerForIastInExpress('Test with sanitization middleware', expressVersion, (expressApp) => { - const mongoSanitize = - require(`../../../../../../versions/express-mongo-sanitize@${expressMongoSanitizeVersion}`).get() - expressApp.use(mongoSanitize()) - }, (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { - testThatRequestHasNoVulnerability({ - fn: async (req, res) => { - const filter = { - name: req.query.key - } - try { - await require(tmpFilePath).vulnerableFindOne(collection, filter) - } catch (e) { - // do nothing - } - res.end() - }, - vulnerability: 'NOSQL_MONGODB_INJECTION', - makeRequest: (done, config) => { - axios.get(`http://localhost:${config.port}/?key=value`).catch(done) + withVersions('express-mongo-sanitize', 'express-mongo-sanitize', expressMongoSanitizeVersion => { + prepareTestServerForIastInExpress('Test with sanitization middleware', expressVersion, (expressApp) => { + const mongoSanitize = + require(`../../../../../../versions/express-mongo-sanitize@${expressMongoSanitizeVersion}`).get() + expressApp.use(mongoSanitize()) + }, (testThatRequestHasVulnerability, testThatRequestHasNoVulnerability) => { + testThatRequestHasNoVulnerability({ + fn: async (req, res) => { + const filter = { + name: req.query.key + } + try { + await require(tmpFilePath).vulnerableFindOne(collection, filter) + } catch (e) { + // do nothing } - }) + res.end() + }, + vulnerability: 'NOSQL_MONGODB_INJECTION', + makeRequest: (done, config) => { + axios.get(`http://localhost:${config.port}/?key=value`).catch(done) + } }) }) - } + }) }) }) }) From ec073868cbec5e3731be8989fb98ea0faae93858 Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 27 Nov 2024 09:20:11 +0100 Subject: [PATCH 21/31] remove querystring instrumentation --- .../datadog-instrumentations/src/express.js | 60 +++++-------- .../src/helpers/hooks.js | 2 - .../src/querystring.js | 24 ----- .../datadog-instrumentations/src/router.js | 43 ++++----- .../test/querystring.spec.js | 90 ------------------- .../src/appsec/iast/taint-tracking/plugin.js | 8 +- .../appsec/iast/taint-tracking/plugin.spec.js | 8 +- 7 files changed, 55 insertions(+), 180 deletions(-) delete mode 100644 packages/datadog-instrumentations/src/querystring.js delete mode 100644 packages/datadog-instrumentations/test/querystring.spec.js diff --git a/packages/datadog-instrumentations/src/express.js b/packages/datadog-instrumentations/src/express.js index be38ee78102..a4de43e3f22 100644 --- a/packages/datadog-instrumentations/src/express.js +++ b/packages/datadog-instrumentations/src/express.js @@ -57,15 +57,19 @@ function wrapResponseRender (render) { } } +addHook({ name: 'express', versions: ['>=4'] }, express => { + shimmer.wrap(express.response, 'json', wrapResponseJson) + shimmer.wrap(express.response, 'jsonp', wrapResponseJson) + shimmer.wrap(express.response, 'render', wrapResponseRender) + + return express +}) + addHook({ name: 'express', versions: ['>=4.0.0 <5.0.0'] }, express => { shimmer.wrap(express.application, 'handle', wrapHandle) shimmer.wrap(express.Router, 'use', wrapRouterMethod) shimmer.wrap(express.Router, 'route', wrapRouterMethod) - shimmer.wrap(express.response, 'json', wrapResponseJson) - shimmer.wrap(express.response, 'jsonp', wrapResponseJson) - shimmer.wrap(express.response, 'render', wrapResponseRender) - return express }) @@ -77,23 +81,15 @@ addHook({ name: 'express', versions: ['>=5.0.0'] }, express => { return express }) -addHook({ name: 'express', file: ['lib/response.js'], versions: ['>=5.0.0'] }, response => { - shimmer.wrap(response, 'json', wrapResponseJson) - shimmer.wrap(response, 'jsonp', wrapResponseJson) - shimmer.wrap(response, 'render', wrapResponseRender) - - return response -}) - -const queryParserReadCh = channel('datadog:query:read:finish') +const queryReaderReadCh = channel('datadog:query:read:finish') function publishQueryParsedAndNext (req, res, next) { return shimmer.wrapFunction(next, next => function () { - if (queryParserReadCh.hasSubscribers && req) { + if (queryReaderReadCh.hasSubscribers && req) { const abortController = new AbortController() const query = req.query - queryParserReadCh.publish({ req, res, query, abortController }) + queryReaderReadCh.publish({ req, res, query, abortController }) if (abortController.signal.aborted) return } @@ -150,32 +146,24 @@ addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'] }, express => { return express }) -addHook({ name: 'express', file: ['lib/express.js'], versions: ['>=5.0.0'] }, express => { - shimmer.wrapFunction(express, function (original) { - const app = original.call(this, arguments) - const requestProto = Object.getPrototypeOf(app.request) - const requestDescriptor = Object.getOwnPropertyDescriptor(requestProto, 'query') - - shimmer.wrap(requestDescriptor, 'get', function (originalGet) { - return function wrappedGet () { - const query = originalGet.apply(this, arguments) - - if (queryParserReadCh.hasSubscribers) { - const abortController = new AbortController() +const queryParserReadCh = channel('datadog:query:parse:finish') - queryParserReadCh.publish({ req: this, res: this.res, query, abortController }) +addHook({ name: 'express', file: ['lib/request.js'], versions: ['>=5.0.0'] }, request => { + const requestDescriptor = Object.getOwnPropertyDescriptor(request, 'query') - if (abortController.signal.aborted) return - } + shimmer.wrap(requestDescriptor, 'get', function (originalGet) { + return function wrappedGet () { + const query = originalGet.apply(this, arguments) - return query + if (queryParserReadCh.hasSubscribers && query) { + queryParserReadCh.publish({ query }) } - }) - - Object.defineProperty(requestProto, 'query', requestDescriptor) - return original + return query + } }) - return express + Object.defineProperty(request, 'query', requestDescriptor) + + return request }) diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js index 572f106dc0b..4261d4dae44 100644 --- a/packages/datadog-instrumentations/src/helpers/hooks.js +++ b/packages/datadog-instrumentations/src/helpers/hooks.js @@ -95,7 +95,6 @@ module.exports = { 'node:http2': () => require('../http2'), 'node:https': () => require('../http'), 'node:net': () => require('../net'), - 'node:querystring': () => require('../querystring'), 'node:url': () => require('../url'), nyc: () => require('../nyc'), oracledb: () => require('../oracledb'), @@ -113,7 +112,6 @@ module.exports = { pug: () => require('../pug'), q: () => require('../q'), qs: () => require('../qs'), - querystring: () => require('../querystring'), redis: () => require('../redis'), restify: () => require('../restify'), rhea: () => require('../rhea'), diff --git a/packages/datadog-instrumentations/src/querystring.js b/packages/datadog-instrumentations/src/querystring.js deleted file mode 100644 index a95f5c4b480..00000000000 --- a/packages/datadog-instrumentations/src/querystring.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const { addHook, channel } = require('./helpers/instrument') -const shimmer = require('../../datadog-shimmer') -const names = ['querystring', 'node:querystring'] - -const querystringParseCh = channel('datadog:querystring:parse:finish') - -addHook({ name: names }, function (querystring) { - shimmer.wrap(querystring, 'parse', function (parse) { - function wrappedParse () { - const qs = parse.apply(this, arguments) - if (querystringParseCh.hasSubscribers && qs) { - querystringParseCh.publish({ qs }) - } - - return qs - } - - return wrappedParse - }) - - return querystring -}) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index a476bbc44e8..5491034a7c5 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -176,30 +176,33 @@ addHook({ name: 'router', versions: ['>=1 <2'] }, Router => { return Router }) +const queryParserReadCh = channel('datadog:query:read:finish') + addHook({ name: 'router', versions: ['>=2'] }, Router => { - const originalRouter = Router - - function WrappedRouter (options) { - const router = originalRouter.call(this, options) - - if (!router._queryParsingWrapped) { - router._queryParsingWrapped = true - const originalHandle = router.handle - router.handle = function (req, res, next) { - req.query - // If query parsing was aborted, don't continue request handling - if (res.writableEnded) { - return - } + const WrappedRouter = shimmer.wrapFunction(Router, function (originalRouter) { + return function wrappedMethod (options) { + const router = originalRouter.call(this, options) - return originalHandle.apply(this, arguments) - } - } + if (!router._queryParsingWrapped) { + router._queryParsingWrapped = true + const originalHandle = router.handle - return router - } + router.handle = function (req, res, next) { + const abortController = new AbortController() + + if (queryParserReadCh.hasSubscribers && req) { + queryParserReadCh.publish({ req, res, query: req.query, abortController }) - WrappedRouter.prototype = originalRouter.prototype + if (abortController.signal.aborted) return + } + + return originalHandle.apply(this, arguments) + } + } + + return router + } + }) shimmer.wrap(WrappedRouter.prototype, 'use', wrapRouterMethod) shimmer.wrap(WrappedRouter.prototype, 'route', wrapRouterMethod) diff --git a/packages/datadog-instrumentations/test/querystring.spec.js b/packages/datadog-instrumentations/test/querystring.spec.js deleted file mode 100644 index b7f51e67e5b..00000000000 --- a/packages/datadog-instrumentations/test/querystring.spec.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict' - -const agent = require('../../dd-trace/test/plugins/agent') -const { channel } = require('../src/helpers/instrument') -const names = ['querystring', 'node:querystring'] - -names.forEach(name => { - describe(name, () => { - const querystring = require(name) - const querystringParseCh = channel('datadog:querystring:parse:finish') - let querystringParseChCb - - before(async () => { - await agent.load('querystring') - }) - - after(() => { - return agent.close() - }) - - beforeEach(() => { - querystringParseChCb = sinon.stub() - querystringParseCh.subscribe(querystringParseChCb) - }) - - afterEach(() => { - querystringParseCh.unsubscribe(querystringParseChCb) - }) - - describe('querystring.parse', () => { - it('should publish parsed empty query string', () => { - const result = querystring.parse('') - - sinon.assert.calledOnceWithExactly(querystringParseChCb, { - qs: result - }, sinon.match.any) - }) - - it('should publish parsed query string with single parameter', () => { - const result = querystring.parse('foo=bar') - - sinon.assert.calledOnceWithExactly(querystringParseChCb, { - qs: result - }, sinon.match.any) - - expect(result).to.deep.equal({ foo: 'bar' }) - }) - - it('should publish parsed query string with multiple parameters', () => { - const result = querystring.parse('foo=bar&baz=qux') - - sinon.assert.calledOnceWithExactly(querystringParseChCb, { - qs: result - }, sinon.match.any) - - expect(result).to.deep.equal({ foo: 'bar', baz: 'qux' }) - }) - - it('should publish parsed query string with encoded values', () => { - const result = querystring.parse('message=hello%20world') - - sinon.assert.calledOnceWithExactly(querystringParseChCb, { - qs: result - }, sinon.match.any) - - expect(result).to.deep.equal({ message: 'hello world' }) - }) - - it('should publish parsed query string with array parameter', () => { - const result = querystring.parse('items=1&items=2') - - sinon.assert.calledOnceWithExactly(querystringParseChCb, { - qs: result - }, sinon.match.any) - - expect(result).to.have.property('items') - }) - - it('should handle null or undefined query string', () => { - const result = querystring.parse(null) - - sinon.assert.calledOnceWithExactly(querystringParseChCb, { - qs: result - }, sinon.match.any) - - expect(result).to.deep.equal({}) - }) - }) - }) -}) diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js index cce3de7c800..b4b8a01f590 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js @@ -46,13 +46,13 @@ class TaintTrackingPlugin extends SourceIastPlugin { ) this.addSub( - { channelName: 'datadog:qs:parse:finish', tag: HTTP_REQUEST_PARAMETER }, - ({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs) + { channelName: 'datadog:query:read:finish', tag: HTTP_REQUEST_PARAMETER }, + ({ query }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, query) ) this.addSub( - { channelName: 'datadog:querystring:parse:finish', tag: HTTP_REQUEST_PARAMETER }, - ({ qs }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, qs) + { channelName: 'datadog:query:parse:finish', tag: HTTP_REQUEST_PARAMETER }, + ({ query }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, query) ) this.addSub( diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js index 5ac1df8c5b1..d6ee9ed567a 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js @@ -12,7 +12,7 @@ const { } = require('../../../../src/appsec/iast/taint-tracking/source-types') const middlewareNextChannel = dc.channel('apm:express:middleware:next') -const queryParseFinishChannel = dc.channel('datadog:qs:parse:finish') +const queryReadFinishChannel = dc.channel('datadog:query:read:finish') const bodyParserFinishChannel = dc.channel('datadog:body-parser:read:finish') const cookieParseFinishCh = dc.channel('datadog:cookie:parse:finish') const processParamsStartCh = dc.channel('datadog:express:process_params:start') @@ -46,8 +46,8 @@ describe('IAST Taint tracking plugin', () => { expect(taintTrackingPlugin._subscriptions).to.have.lengthOf(11) expect(taintTrackingPlugin._subscriptions[0]._channel.name).to.equals('datadog:body-parser:read:finish') expect(taintTrackingPlugin._subscriptions[1]._channel.name).to.equals('datadog:multer:read:finish') - expect(taintTrackingPlugin._subscriptions[2]._channel.name).to.equals('datadog:qs:parse:finish') - expect(taintTrackingPlugin._subscriptions[3]._channel.name).to.equals('datadog:querystring:parse:finish') + expect(taintTrackingPlugin._subscriptions[2]._channel.name).to.equals('datadog:query:read:finish') + expect(taintTrackingPlugin._subscriptions[3]._channel.name).to.equals('datadog:query:parse:finish') expect(taintTrackingPlugin._subscriptions[4]._channel.name).to.equals('apm:express:middleware:next') expect(taintTrackingPlugin._subscriptions[5]._channel.name).to.equals('datadog:cookie:parse:finish') expect(taintTrackingPlugin._subscriptions[6]._channel.name).to.equals('datadog:express:process_params:start') @@ -139,7 +139,7 @@ describe('IAST Taint tracking plugin', () => { } } - queryParseFinishChannel.publish({ qs: req.query }) + queryReadFinishChannel.publish({ query: req.query }) expect(taintTrackingOperations.taintObject).to.be.calledOnceWith( iastContext, From ef6a82294e59f0400acaef996ed905db014481a8 Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 27 Nov 2024 10:32:10 +0100 Subject: [PATCH 22/31] remove qs instrumentation --- .../src/helpers/hooks.js | 1 - packages/datadog-instrumentations/src/qs.js | 24 ------------------- 2 files changed, 25 deletions(-) delete mode 100644 packages/datadog-instrumentations/src/qs.js diff --git a/packages/datadog-instrumentations/src/helpers/hooks.js b/packages/datadog-instrumentations/src/helpers/hooks.js index 4261d4dae44..4ea35f50218 100644 --- a/packages/datadog-instrumentations/src/helpers/hooks.js +++ b/packages/datadog-instrumentations/src/helpers/hooks.js @@ -111,7 +111,6 @@ module.exports = { protobufjs: () => require('../protobufjs'), pug: () => require('../pug'), q: () => require('../q'), - qs: () => require('../qs'), redis: () => require('../redis'), restify: () => require('../restify'), rhea: () => require('../rhea'), diff --git a/packages/datadog-instrumentations/src/qs.js b/packages/datadog-instrumentations/src/qs.js deleted file mode 100644 index 3901f61b169..00000000000 --- a/packages/datadog-instrumentations/src/qs.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict' - -const { addHook, channel } = require('./helpers/instrument') -const shimmer = require('../../datadog-shimmer') - -const qsParseCh = channel('datadog:qs:parse:finish') - -function wrapParse (originalParse) { - return function () { - const qsParsedObj = originalParse.apply(this, arguments) - if (qsParseCh.hasSubscribers && qsParsedObj) { - qsParseCh.publish({ qs: qsParsedObj }) - } - return qsParsedObj - } -} - -addHook({ - name: 'qs', - versions: ['>=1'] -}, qs => { - shimmer.wrap(qs, 'parse', wrapParse) - return qs -}) From cee91575aba1d1cece584381fee87a33f423491d Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 27 Nov 2024 15:13:26 +0100 Subject: [PATCH 23/31] clean router handle instrumentation --- packages/datadog-instrumentations/src/express.js | 16 ++++++++-------- packages/datadog-instrumentations/src/router.js | 9 +++------ .../src/appsec/iast/taint-tracking/plugin.js | 2 +- .../appsec/iast/taint-tracking/plugin.spec.js | 2 +- .../taint-tracking.express.plugin.spec.js | 6 ++---- packages/dd-trace/test/plugins/externals.json | 2 +- 6 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/datadog-instrumentations/src/express.js b/packages/datadog-instrumentations/src/express.js index a4de43e3f22..a74d3c2e54d 100644 --- a/packages/datadog-instrumentations/src/express.js +++ b/packages/datadog-instrumentations/src/express.js @@ -58,6 +58,8 @@ function wrapResponseRender (render) { } addHook({ name: 'express', versions: ['>=4'] }, express => { + shimmer.wrap(express.application, 'handle', wrapHandle) + shimmer.wrap(express.response, 'json', wrapResponseJson) shimmer.wrap(express.response, 'jsonp', wrapResponseJson) shimmer.wrap(express.response, 'render', wrapResponseRender) @@ -66,7 +68,6 @@ addHook({ name: 'express', versions: ['>=4'] }, express => { }) addHook({ name: 'express', versions: ['>=4.0.0 <5.0.0'] }, express => { - shimmer.wrap(express.application, 'handle', wrapHandle) shimmer.wrap(express.Router, 'use', wrapRouterMethod) shimmer.wrap(express.Router, 'route', wrapRouterMethod) @@ -74,22 +75,21 @@ addHook({ name: 'express', versions: ['>=4.0.0 <5.0.0'] }, express => { }) addHook({ name: 'express', versions: ['>=5.0.0'] }, express => { - shimmer.wrap(express.application, 'handle', wrapHandle) shimmer.wrap(express.Router.prototype, 'use', wrapRouterMethod) shimmer.wrap(express.Router.prototype, 'route', wrapRouterMethod) return express }) -const queryReaderReadCh = channel('datadog:query:read:finish') +const queryParserReadCh = channel('datadog:query:read:finish') function publishQueryParsedAndNext (req, res, next) { return shimmer.wrapFunction(next, next => function () { - if (queryReaderReadCh.hasSubscribers && req) { + if (queryParserReadCh.hasSubscribers && req) { const abortController = new AbortController() const query = req.query - queryReaderReadCh.publish({ req, res, query, abortController }) + queryParserReadCh.publish({ req, res, query, abortController }) if (abortController.signal.aborted) return } @@ -146,7 +146,7 @@ addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'] }, express => { return express }) -const queryParserReadCh = channel('datadog:query:parse:finish') +const queryReadCh = channel('datadog:express:query:finish') addHook({ name: 'express', file: ['lib/request.js'], versions: ['>=5.0.0'] }, request => { const requestDescriptor = Object.getOwnPropertyDescriptor(request, 'query') @@ -155,8 +155,8 @@ addHook({ name: 'express', file: ['lib/request.js'], versions: ['>=5.0.0'] }, re return function wrappedGet () { const query = originalGet.apply(this, arguments) - if (queryParserReadCh.hasSubscribers && query) { - queryParserReadCh.publish({ query }) + if (queryReadCh.hasSubscribers && query) { + queryReadCh.publish({ query }) } return query diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 5491034a7c5..b6f3a583c7b 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -183,11 +183,8 @@ addHook({ name: 'router', versions: ['>=2'] }, Router => { return function wrappedMethod (options) { const router = originalRouter.call(this, options) - if (!router._queryParsingWrapped) { - router._queryParsingWrapped = true - const originalHandle = router.handle - - router.handle = function (req, res, next) { + shimmer.wrap(router, 'handle', function wrapHandle (originalHandle) { + return function wrappedHandle (req, res, next) { const abortController = new AbortController() if (queryParserReadCh.hasSubscribers && req) { @@ -198,7 +195,7 @@ addHook({ name: 'router', versions: ['>=2'] }, Router => { return originalHandle.apply(this, arguments) } - } + }) return router } diff --git a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js index b4b8a01f590..62fdd46d027 100644 --- a/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +++ b/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js @@ -51,7 +51,7 @@ class TaintTrackingPlugin extends SourceIastPlugin { ) this.addSub( - { channelName: 'datadog:query:parse:finish', tag: HTTP_REQUEST_PARAMETER }, + { channelName: 'datadog:express:query:finish', tag: HTTP_REQUEST_PARAMETER }, ({ query }) => this._taintTrackingHandler(HTTP_REQUEST_PARAMETER, query) ) diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js index d6ee9ed567a..5f9c4f4860f 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/plugin.spec.js @@ -47,7 +47,7 @@ describe('IAST Taint tracking plugin', () => { expect(taintTrackingPlugin._subscriptions[0]._channel.name).to.equals('datadog:body-parser:read:finish') expect(taintTrackingPlugin._subscriptions[1]._channel.name).to.equals('datadog:multer:read:finish') expect(taintTrackingPlugin._subscriptions[2]._channel.name).to.equals('datadog:query:read:finish') - expect(taintTrackingPlugin._subscriptions[3]._channel.name).to.equals('datadog:query:parse:finish') + expect(taintTrackingPlugin._subscriptions[3]._channel.name).to.equals('datadog:express:query:finish') expect(taintTrackingPlugin._subscriptions[4]._channel.name).to.equals('apm:express:middleware:next') expect(taintTrackingPlugin._subscriptions[5]._channel.name).to.equals('datadog:cookie:parse:finish') expect(taintTrackingPlugin._subscriptions[6]._channel.name).to.equals('datadog:express:process_params:start') diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index e8bb7e497ca..ebc35bfabac 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -189,8 +189,7 @@ describe('Path params sourcing with express', () => { res.status(200).send() }) - app.param('parameter1', checkParamIsTaintedAndNext) - app.param('parameter2', checkParamIsTaintedAndNext) + app.param(['parameter1', 'parameter2'], checkParamIsTaintedAndNext) appListener = app.listen(0, 'localhost', () => { const port = appListener.address().port @@ -209,8 +208,7 @@ describe('Path params sourcing with express', () => { res.status(200).send() }) - app.param('parameter1', checkParamIsTaintedAndNext) - app.param('parameter2', checkParamIsTaintedAndNext) + app.param(['parameter1', 'parameter2'], checkParamIsTaintedAndNext) appListener = app.listen(0, 'localhost', () => { const port = appListener.address().port diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index d045e07e1dc..7e1c0f9163e 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -332,7 +332,7 @@ }, { "name": "express", - "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.0.0 <5.0.0", ">=4.3.0 <5.0.0", ">=5.0.0"] + "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.0.0 <5.0.0", ">=4.3.0 <5.0.0", ">=5.0.0"] }, { "name": "body-parser", From 14bc0fbd92a87aeb9db1f132420c2fd64e813486 Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 27 Nov 2024 15:28:12 +0100 Subject: [PATCH 24/31] add prefix matching test --- .../datadog-plugin-express/test/index.spec.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 9d39222bf81..131f01c28a4 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -1228,6 +1228,37 @@ describe('Plugin', () => { }) it('should support capturing groups in routes', done => { + if (semver.intersects(version, '>=5.0.0')) { + this.skip && this.skip() // mocha allows dynamic skipping, tap does not + return done() + } + + const app = express() + + app.get('/:path(*)', (req, res) => { + res.status(200).send() + }) + + appListener = app.listen(0, 'localhost', () => { + const port = appListener.address().port + + agent + .use(traces => { + const spans = sort(traces[0]) + + expect(spans[0]).to.have.property('resource', 'GET /:path(*)') + expect(spans[0].meta).to.have.property('http.url', `http://localhost:${port}/user`) + }) + .then(done) + .catch(done) + + axios + .get(`http://localhost:${port}/user`) + .catch(done) + }) + }) + + it('should support wildcard path prefix matching in routes', done => { const app = express() app.get('/*user', (req, res) => { From e99c1804e82b65d9f6ea5063218b100c090760f5 Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 27 Nov 2024 15:47:49 +0100 Subject: [PATCH 25/31] fix mongoose tests --- ...ion-mongodb-analyzer.mongoose.plugin.spec.js | 17 +++++++++++++---- packages/dd-trace/test/plugins/externals.json | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js index f8b96557a16..75337c63b3f 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.mongoose.plugin.spec.js @@ -10,8 +10,7 @@ const fs = require('fs') const { NODE_MAJOR } = require('../../../../../../version') describe('nosql injection detection in mongodb - whole feature', () => { - // https://github.com/fiznool/express-mongo-sanitize/issues/200 - withVersions('mongoose', 'express', '>4.18.0 <5.0.0', expressVersion => { + withVersions('mongoose', 'express', expressVersion => { withVersions('mongoose', 'mongoose', '>4.0.0', mongooseVersion => { const specificMongooseVersion = require(`../../../../../../versions/mongoose@${mongooseVersion}`).version() if (NODE_MAJOR === 14 && semver.satisfies(specificMongooseVersion, '>=8')) return @@ -28,11 +27,16 @@ describe('nosql injection detection in mongodb - whole feature', () => { const dbName = id().toString() mongoose = require(`../../../../../../versions/mongoose@${mongooseVersion}`).get() - mongoose.connect(`mongodb://localhost:27017/${dbName}`, { + await mongoose.connect(`mongodb://localhost:27017/${dbName}`, { useNewUrlParser: true, useUnifiedTopology: true }) + if (mongoose.models.Test) { + delete mongoose.models?.Test + delete mongoose.modelSchemas?.Test + } + Test = mongoose.model('Test', { name: String }) const src = path.join(__dirname, 'resources', vulnerableMethodFilename) @@ -47,7 +51,12 @@ describe('nosql injection detection in mongodb - whole feature', () => { }) after(() => { - fs.unlinkSync(tmpFilePath) + try { + fs.unlinkSync(tmpFilePath) + } catch (e) { + // ignore the error + } + return mongoose.disconnect() }) diff --git a/packages/dd-trace/test/plugins/externals.json b/packages/dd-trace/test/plugins/externals.json index 7e1c0f9163e..288fb9350c6 100644 --- a/packages/dd-trace/test/plugins/externals.json +++ b/packages/dd-trace/test/plugins/externals.json @@ -332,7 +332,7 @@ }, { "name": "express", - "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.0.0 <5.0.0", ">=4.3.0 <5.0.0", ">=5.0.0"] + "versions": [">=4", ">=4.0.0 <4.3.0", ">=4.0.0 <5.0.0", ">=4.3.0 <5.0.0"] }, { "name": "body-parser", From e12f4479128c757624eace1949fea6dacf58272d Mon Sep 17 00:00:00 2001 From: ishabi Date: Tue, 3 Dec 2024 09:20:50 +0100 Subject: [PATCH 26/31] fix express versions --- packages/datadog-instrumentations/src/express.js | 4 ++-- packages/datadog-plugin-express/test/index.spec.js | 5 ++--- .../test/integration-test/client.spec.js | 4 ++-- .../sources/taint-tracking.express.plugin.spec.js | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/datadog-instrumentations/src/express.js b/packages/datadog-instrumentations/src/express.js index a74d3c2e54d..1b328ba4c13 100644 --- a/packages/datadog-instrumentations/src/express.js +++ b/packages/datadog-instrumentations/src/express.js @@ -67,7 +67,7 @@ addHook({ name: 'express', versions: ['>=4'] }, express => { return express }) -addHook({ name: 'express', versions: ['>=4.0.0 <5.0.0'] }, express => { +addHook({ name: 'express', versions: ['4'] }, express => { shimmer.wrap(express.Router, 'use', wrapRouterMethod) shimmer.wrap(express.Router, 'route', wrapRouterMethod) @@ -100,7 +100,7 @@ function publishQueryParsedAndNext (req, res, next) { addHook({ name: 'express', - versions: ['>=4.0.0 <5.0.0'], + versions: ['4'], file: 'lib/middleware/query.js' }, query => { return shimmer.wrapFunction(query, query => function () { diff --git a/packages/datadog-plugin-express/test/index.spec.js b/packages/datadog-plugin-express/test/index.spec.js index 131f01c28a4..8899c34ecb3 100644 --- a/packages/datadog-plugin-express/test/index.spec.js +++ b/packages/datadog-plugin-express/test/index.spec.js @@ -249,11 +249,10 @@ describe('Plugin', () => { expect(spans[index].meta).to.have.property('component', 'express') index++ - const handleResource = isExpress4 ? /^bound\s.*$/ : 'handle' if (isExpress4) { - expect(spans[index].resource).to.match(handleResource) + expect(spans[index].resource).to.match(/^bound\s.*$/) } else { - expect(spans[index]).to.have.property('resource', handleResource) + expect(spans[index]).to.have.property('resource', 'handle') } expect(spans[index]).to.have.property('name', 'express.middleware') expect(spans[index].parent_id.toString()).to.equal(spans[index - 1].span_id.toString()) diff --git a/packages/datadog-plugin-express/test/integration-test/client.spec.js b/packages/datadog-plugin-express/test/integration-test/client.spec.js index 1f884442019..c13a4249892 100644 --- a/packages/datadog-plugin-express/test/integration-test/client.spec.js +++ b/packages/datadog-plugin-express/test/integration-test/client.spec.js @@ -37,14 +37,14 @@ describe('esm', () => { it('is instrumented', async () => { proc = await spawnPluginIntegrationTestProc(sandbox.folder, 'server.mjs', agent.port) - const numberofSpans = semver.intersects(version, '<5.0.0') ? 4 : 3 + const numberOfSpans = semver.intersects(version, '<5.0.0') ? 4 : 3 return curlAndAssertMessage(agent, proc, ({ headers, payload }) => { assert.propertyVal(headers, 'host', `127.0.0.1:${agent.port}`) assert.isArray(payload) assert.strictEqual(payload.length, 1) assert.isArray(payload[0]) - assert.strictEqual(payload[0].length, numberofSpans) + assert.strictEqual(payload[0].length, numberOfSpans) assert.propertyVal(payload[0][0], 'name', 'express.request') assert.propertyVal(payload[0][1], 'name', 'express.middleware') }) diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index ebc35bfabac..947e0fc0b8a 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -46,7 +46,7 @@ describe('URI sourcing with express', () => { iast.disable() }) - it('should taint uri', function (done) { + it('should taint uri', (done) => { const app = express() const pathPattern = semver.intersects(version, '>=5.0.0') ? '/path/*splat' : '/path/*' app.get(pathPattern, (req, res) => { From 1fe57b5e6ed91a497aea4018aa7fed6d545fde22 Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 4 Dec 2024 14:14:29 +0100 Subject: [PATCH 27/31] use weekSet to not process same params twice --- packages/datadog-instrumentations/src/router.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index b6f3a583c7b..0f0795ece59 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -208,10 +208,13 @@ addHook({ name: 'router', versions: ['>=2'] }, Router => { }) const routerParamStartCh = channel('datadog:router:param:start') +const visitedParams = new WeakSet() function wrapHandleRequest (original) { return function wrappedHandleRequest (req, res, next) { - if (routerParamStartCh.hasSubscribers) { + if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) { + visitedParams.add(req.params) + const abortController = new AbortController() routerParamStartCh.publish({ @@ -238,7 +241,9 @@ addHook({ function wrapParam (original) { return function wrappedProcessParams (name, fn) { const wrappedFn = function wrappedParamCallback (req, res, next, param) { - if (routerParamStartCh.hasSubscribers) { + if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) { + visitedParams.add(req.params) + const abortController = new AbortController() routerParamStartCh.publish({ From e12eb42b74869119038ed6308e504e2e1accf999 Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 4 Dec 2024 15:36:02 +0100 Subject: [PATCH 28/31] use arguments instead of explicit params --- packages/datadog-instrumentations/src/router.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 0f0795ece59..6b9dc8c76f1 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -180,8 +180,8 @@ const queryParserReadCh = channel('datadog:query:read:finish') addHook({ name: 'router', versions: ['>=2'] }, Router => { const WrappedRouter = shimmer.wrapFunction(Router, function (originalRouter) { - return function wrappedMethod (options) { - const router = originalRouter.call(this, options) + return function wrappedMethod () { + const router = originalRouter.apply(this, arguments) shimmer.wrap(router, 'handle', function wrapHandle (originalHandle) { return function wrappedHandle (req, res, next) { @@ -240,7 +240,7 @@ addHook({ function wrapParam (original) { return function wrappedProcessParams (name, fn) { - const wrappedFn = function wrappedParamCallback (req, res, next, param) { + const wrappedFn = function wrappedParamCallback (req, res) { if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) { visitedParams.add(req.params) @@ -256,7 +256,7 @@ function wrapParam (original) { if (abortController.signal.aborted) return } - return fn(req, res, next, param, name) + return fn.apply(this, arguments) } return original.call(this, name, wrappedFn) From 7f71fc70249e1dfb7cfa379cf81ee3eeb191594e Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 4 Dec 2024 15:56:34 +0100 Subject: [PATCH 29/31] fix wrapped fn scope --- packages/datadog-instrumentations/src/router.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 6b9dc8c76f1..415d774ca17 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -240,7 +240,9 @@ addHook({ function wrapParam (original) { return function wrappedProcessParams (name, fn) { - const wrappedFn = function wrappedParamCallback (req, res) { + const wrappedFn = (...args) => { + const [req, res] = args + if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) { visitedParams.add(req.params) @@ -256,7 +258,7 @@ function wrapParam (original) { if (abortController.signal.aborted) return } - return fn.apply(this, arguments) + return fn.apply(this, args) } return original.call(this, name, wrappedFn) From eff2153aaa670a826236e1a91bd2a92aa1c69e6d Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 4 Dec 2024 17:49:29 +0100 Subject: [PATCH 30/31] fix wrap param shimmer --- .../datadog-instrumentations/src/router.js | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index 415d774ca17..fb94c6f986c 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -239,29 +239,31 @@ addHook({ }) function wrapParam (original) { - return function wrappedProcessParams (name, fn) { - const wrappedFn = (...args) => { - const [req, res] = args + return function wrappedProcessParams () { + arguments[1] = shimmer.wrapFunction(arguments[1], (originalFn) => { + return (...args) => { + const [req, res] = args - if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) { - visitedParams.add(req.params) + if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) { + visitedParams.add(req.params) - const abortController = new AbortController() + const abortController = new AbortController() - routerParamStartCh.publish({ - req, - res, - params: req?.params, - abortController - }) + routerParamStartCh.publish({ + req, + res, + params: req?.params, + abortController + }) - if (abortController.signal.aborted) return - } + if (abortController.signal.aborted) return + } - return fn.apply(this, args) - } + return originalFn.apply(this, args) + } + }) - return original.call(this, name, wrappedFn) + return original.apply(this, arguments) } } From d764b89efdd79afe41ded233893a0eaca0921559 Mon Sep 17 00:00:00 2001 From: ishabi Date: Wed, 4 Dec 2024 21:03:27 +0100 Subject: [PATCH 31/31] use named function --- packages/datadog-instrumentations/src/router.js | 6 ++---- .../sources/taint-tracking.express.plugin.spec.js | 10 +++++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/datadog-instrumentations/src/router.js b/packages/datadog-instrumentations/src/router.js index fb94c6f986c..bc9ff6152e5 100644 --- a/packages/datadog-instrumentations/src/router.js +++ b/packages/datadog-instrumentations/src/router.js @@ -241,9 +241,7 @@ addHook({ function wrapParam (original) { return function wrappedProcessParams () { arguments[1] = shimmer.wrapFunction(arguments[1], (originalFn) => { - return (...args) => { - const [req, res] = args - + return function wrappedFn (req, res) { if (routerParamStartCh.hasSubscribers && Object.keys(req.params).length && !visitedParams.has(req.params)) { visitedParams.add(req.params) @@ -259,7 +257,7 @@ function wrapParam (original) { if (abortController.signal.aborted) return } - return originalFn.apply(this, args) + return originalFn.apply(this, arguments) } }) diff --git a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js index 947e0fc0b8a..8fc32f1c03a 100644 --- a/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/iast/taint-tracking/sources/taint-tracking.express.plugin.spec.js @@ -202,13 +202,21 @@ describe('Path params sourcing with express', () => { }) it('should taint path param on router.params callback with custom implementation', function (done) { + if (!semver.satisfies(expressVersion, '4')) { + this.skip() + } const app = express() app.use('/:parameter1/:parameter2', (req, res) => { res.status(200).send() }) - app.param(['parameter1', 'parameter2'], checkParamIsTaintedAndNext) + app.param((param, option) => { + return checkParamIsTaintedAndNext + }) + + app.param('parameter1') + app.param('parameter2') appListener = app.listen(0, 'localhost', () => { const port = appListener.address().port