From 2f32ef9dc6895340f0aba9b40b2f7193c62f3cce Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Thu, 9 Oct 2025 13:21:08 +0200 Subject: [PATCH 1/9] Add fastify support and test it --- packages/dd-trace/src/appsec/bodies.js | 21 ++ packages/dd-trace/src/appsec/index.js | 7 + packages/dd-trace/src/appsec/reporter.js | 8 +- ...ded-data-collection.fastify.plugin.spec.js | 293 ++++++++++++++++++ 4 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 packages/dd-trace/src/appsec/bodies.js create mode 100644 packages/dd-trace/test/appsec/extended-data-collection.fastify.plugin.spec.js diff --git a/packages/dd-trace/src/appsec/bodies.js b/packages/dd-trace/src/appsec/bodies.js new file mode 100644 index 00000000000..6317e8ebfb3 --- /dev/null +++ b/packages/dd-trace/src/appsec/bodies.js @@ -0,0 +1,21 @@ +'use strict' + +const bodies = new WeakMap() + +function setRequestBody (req, body) { + bodies.set(req, body) +} + +function getRequestBody (req) { + return bodies.get(req) +} + +function deleteRequestBody (req) { + bodies.delete(req) +} + +module.exports = { + getRequestBody, + setRequestBody, + deleteRequestBody +} diff --git a/packages/dd-trace/src/appsec/index.js b/packages/dd-trace/src/appsec/index.js index a4505584562..bde536060b9 100644 --- a/packages/dd-trace/src/appsec/index.js +++ b/packages/dd-trace/src/appsec/index.js @@ -40,6 +40,7 @@ const { storage } = require('../../../datadog-core') const graphql = require('./graphql') const rasp = require('./rasp') const { isInServerlessEnvironment } = require('../serverless') +const { setRequestBody, deleteRequestBody } = require('./bodies') const responseAnalyzedSet = new WeakSet() const storedResponseHeaders = new WeakMap() @@ -114,6 +115,11 @@ function onRequestBodyParsed ({ req, res, body, abortController }) { const rootSpan = web.root(req) if (!rootSpan) return + if (!req.body) { + // do not store body if it is in req.body + setRequestBody(req, body) + } + const results = waf.run({ persistent: { [addresses.HTTP_INCOMING_BODY]: body @@ -205,6 +211,7 @@ function incomingHttpEndTranslator ({ req, res }) { if (storedHeaders) { storedResponseHeaders.delete(req) } + deleteRequestBody(req) } function onPassportVerify ({ framework, login, user, success, abortController }) { diff --git a/packages/dd-trace/src/appsec/reporter.js b/packages/dd-trace/src/appsec/reporter.js index d9604e7eded..b5f4c432704 100644 --- a/packages/dd-trace/src/appsec/reporter.js +++ b/packages/dd-trace/src/appsec/reporter.js @@ -19,6 +19,7 @@ const { const { keepTrace } = require('../priority_sampler') const { ASM } = require('../standalone/product') const { DIAGNOSTIC_KEYS } = require('./waf/diagnostics') +const { getRequestBody } = require('./bodies') const REQUEST_HEADER_TAG_PREFIX = 'http.request.headers.' const RESPONSE_HEADER_TAG_PREFIX = 'http.response.headers.' @@ -543,8 +544,11 @@ function finishRequest (req, res, storedResponseHeaders) { ) if (extendedDataCollection) { - // TODO add support for fastify, req.body is not available in fastify - reportRequestBody(rootSpan, req.body) + let body = req.body + if (!body) { + body = getRequestBody(req) + } + reportRequestBody(rootSpan, body) } if (tags['appsec.event'] === 'true' && typeof req.route?.path === 'string') { diff --git a/packages/dd-trace/test/appsec/extended-data-collection.fastify.plugin.spec.js b/packages/dd-trace/test/appsec/extended-data-collection.fastify.plugin.spec.js new file mode 100644 index 00000000000..02588a5c0e9 --- /dev/null +++ b/packages/dd-trace/test/appsec/extended-data-collection.fastify.plugin.spec.js @@ -0,0 +1,293 @@ +'use strict' + +const Config = require('../../src/config') +const path = require('path') +const { withVersions } = require('../setup/mocha') +const agent = require('../plugins/agent') +const appsec = require('../../src/appsec') +const axios = require('axios') +const assert = require('assert') +const msgpack = require('@msgpack/msgpack') + +function createDeepObject (sheetValue, currentLevel = 1, max = 20) { + if (currentLevel === max) { + return { + [`s-${currentLevel}`]: `s-${currentLevel}`, + [`o-${currentLevel}`]: sheetValue + } + } + + return { + [`s-${currentLevel}`]: `s-${currentLevel}`, + [`o-${currentLevel}`]: createDeepObject(sheetValue, currentLevel + 1, max) + } +} + +describe('extended data collection', () => { + before(() => { + return agent.load(['fastify', 'http'], { client: false }) + }) + + after(() => { + return agent.close({ ritmReset: false }) + }) + + withVersions('fastify', 'fastify', '>=2', fastifyVersion => { + let port, app + + before((done) => { + const fastify = require(`../../../../versions/fastify@${fastifyVersion}`).get() + + app = fastify() + + app.post('/', (request, reply) => { + reply.header('custom-response-header-1', 'custom-response-header-value-1') + reply.header('custom-response-header-2', 'custom-response-header-value-2') + reply.header('custom-response-header-3', 'custom-response-header-value-3') + reply.header('custom-response-header-4', 'custom-response-header-value-4') + reply.header('custom-response-header-5', 'custom-response-header-value-5') + reply.header('custom-response-header-6', 'custom-response-header-value-6') + reply.header('custom-response-header-7', 'custom-response-header-value-7') + reply.header('custom-response-header-8', 'custom-response-header-value-8') + reply.header('custom-response-header-9', 'custom-response-header-value-9') + reply.header('custom-response-header-10', 'custom-response-header-value-10') + + reply.send('DONE') + }) + + app.post('/redacted-headers', (request, reply) => { + reply.header('authorization', 'header-value-1') + reply.header('proxy-authorization', 'header-value-2') + reply.header('www-authenticate', 'header-value-4') + reply.header('proxy-authenticate', 'header-value-5') + reply.header('authentication-info', 'header-value-6') + reply.header('proxy-authentication-info', 'header-value-7') + reply.header('cookie', 'header-value-8') + reply.header('set-cookie', 'header-value-9') + + reply.send('DONE') + }) + + app.listen(port, (err, address) => { + if (err) { + throw err + } + port = app.server.address().port + done() + }) + }) + + after(() => { + app.close() + }) + + beforeEach(() => { + appsec.enable(new Config( + { + appsec: { + enabled: true, + rules: path.join(__dirname, './extended-data-collection.rules.json') + } + } + )) + }) + + afterEach(() => { + appsec.disable() + }) + + it('Should collect nothing when no extended_data_collection is triggered', async () => { + const requestBody = { + other: 'other', + chained: { + child: 'one', + child2: 2 + } + } + await axios.post( + `http://localhost:${port}/`, + requestBody, + { + headers: { + 'custom-header-key-1': 'custom-header-value-1', + 'custom-header-key-2': 'custom-header-value-2', + 'custom-header-key-3': 'custom-header-value-3' + } + } + ) + + await agent.assertSomeTraces((traces) => { + const span = traces[0][0] + assert.strictEqual(span.type, 'web') + + assert.strictEqual(span.meta['http.request.headers.custom-request-header-1'], undefined) + assert.strictEqual(span.meta['http.request.headers.custom-request-header-2'], undefined) + assert.strictEqual(span.meta['http.request.headers.custom-request-header-3'], undefined) + + assert.strictEqual(span.meta['http.response.headers.custom-response-header-1'], undefined) + assert.strictEqual(span.meta['http.response.headers.custom-response-header-2'], undefined) + assert.strictEqual(span.meta['http.response.headers.custom-response-header-3'], undefined) + + const rawMetaStructBody = span.meta_struct?.['http.request.body'] + assert.strictEqual(rawMetaStructBody, undefined) + }) + }) + + it('Should redact request/response headers', async () => { + const requestBody = { + bodyParam: 'collect-standard' + } + await axios.post( + `http://localhost:${port}/redacted-headers`, + requestBody, + { + headers: { + authorization: 'header-value-1', + 'proxy-authorization': 'header-value-2', + 'www-authenticate': 'header-value-3', + 'proxy-authenticate': 'header-value-4', + 'authentication-info': 'header-value-5', + 'proxy-authentication-info': 'header-value-6', + cookie: 'header-value-7', + 'set-cookie': 'header-value-8' + } + } + ) + + await agent.assertSomeTraces((traces) => { + const span = traces[0][0] + assert.strictEqual(span.type, 'web') + assert.strictEqual(span.meta['http.request.headers.authorization'], '') + assert.strictEqual(span.meta['http.request.headers.proxy-authorization'], '') + assert.strictEqual(span.meta['http.request.headers.www-authenticate'], '') + assert.strictEqual(span.meta['http.request.headers.proxy-authenticate'], '') + assert.strictEqual(span.meta['http.request.headers.authentication-info'], '') + assert.strictEqual(span.meta['http.request.headers.proxy-authentication-info'], '') + assert.strictEqual(span.meta['http.request.headers.cookie'], '') + assert.strictEqual(span.meta['http.request.headers.set-cookie'], '') + + assert.strictEqual(span.meta['http.response.headers.authorization'], '') + assert.strictEqual(span.meta['http.response.headers.proxy-authorization'], '') + assert.strictEqual(span.meta['http.response.headers.www-authenticate'], '') + assert.strictEqual(span.meta['http.response.headers.proxy-authenticate'], '') + assert.strictEqual(span.meta['http.response.headers.authentication-info'], '') + assert.strictEqual(span.meta['http.response.headers.proxy-authentication-info'], '') + assert.strictEqual(span.meta['http.response.headers.cookie'], '') + assert.strictEqual(span.meta['http.response.headers.set-cookie'], '') + }) + }) + + it('Should collect request body and request/response with a max of 8 headers', async () => { + const requestBody = { + bodyParam: 'collect-few-headers', + other: 'other', + chained: { + child: 'one', + child2: 2 + } + } + await axios.post( + `http://localhost:${port}/`, + requestBody, + { + headers: { + 'custom-request-header-1': 'custom-request-header-value-1', + 'custom-request-header-2': 'custom-request-header-value-2', + 'custom-request-header-3': 'custom-request-header-value-3', + 'custom-request-header-4': 'custom-request-header-value-4', + 'custom-request-header-5': 'custom-request-header-value-5', + 'custom-request-header-6': 'custom-request-header-value-6', + 'custom-request-header-7': 'custom-request-header-value-7', + 'custom-request-header-8': 'custom-request-header-value-8', + 'custom-request-header-9': 'custom-request-header-value-9', + 'custom-request-header-10': 'custom-request-header-value-10' + } + } + ) + + await agent.assertSomeTraces((traces) => { + const span = traces[0][0] + assert.strictEqual(span.type, 'web') + const collectedRequestHeaders = Object.keys(span.meta) + .filter(metaKey => metaKey.startsWith('http.request.headers.')).length + const collectedResponseHeaders = Object.keys(span.meta) + .filter(metaKey => metaKey.startsWith('http.response.headers.')).length + assert.strictEqual(collectedRequestHeaders, 8) + assert.strictEqual(collectedResponseHeaders, 8) + + assert.ok(span.metrics['_dd.appsec.request.header_collection.discarded'] > 2) + assert.ok(span.metrics['_dd.appsec.response.header_collection.discarded'] > 2) + + const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) + assert.deepEqual(metaStructBody, requestBody) + }) + }) + + it('Should truncate the request body when depth is more than 20 levels', async () => { + const deepObject = createDeepObject('sheet') + + const requestBody = { + bodyParam: 'collect-standard', + deepObject + } + + const expectedDeepTruncatedObject = createDeepObject({ 's-19': 's-19' }, 1, 18) + const expectedRequestBody = { + bodyParam: 'collect-standard', + deepObject: expectedDeepTruncatedObject + } + await axios.post(`http://localhost:${port}/`, requestBody) + + await agent.assertSomeTraces((traces) => { + const span = traces[0][0] + assert.strictEqual(span.type, 'web') + + const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) + assert.deepEqual(metaStructBody, expectedRequestBody) + }) + }) + + it('Should truncate the request body when string length is more than 4096 characters', async () => { + const requestBody = { + bodyParam: 'collect-standard', + longValue: Array(5000).fill('A').join('') + } + + const expectedRequestBody = { + bodyParam: 'collect-standard', + longValue: Array(4096).fill('A').join('') + } + await axios.post(`http://localhost:${port}/`, requestBody) + + await agent.assertSomeTraces((traces) => { + const span = traces[0][0] + assert.strictEqual(span.type, 'web') + + const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) + assert.deepEqual(metaStructBody, expectedRequestBody) + }) + }) + + it('Should truncate the request body when a node has more than 256 elements', async () => { + const children = Array(300).fill('item') + const requestBody = { + bodyParam: 'collect-standard', + children + } + + const expectedRequestBody = { + bodyParam: 'collect-standard', + children: children.slice(0, 256) + } + await axios.post(`http://localhost:${port}/`, requestBody) + + await agent.assertSomeTraces((traces) => { + const span = traces[0][0] + assert.strictEqual(span.type, 'web') + + const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) + assert.deepEqual(metaStructBody, expectedRequestBody) + }) + }) + }) +}) From 68094d45cad0c7a83c9edac9406c94a6217237c3 Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Thu, 9 Oct 2025 14:22:28 +0200 Subject: [PATCH 2/9] Do not use external bodies.js file --- packages/dd-trace/src/appsec/bodies.js | 21 --------------------- packages/dd-trace/src/appsec/index.js | 9 +++++---- packages/dd-trace/src/appsec/reporter.js | 9 ++------- 3 files changed, 7 insertions(+), 32 deletions(-) delete mode 100644 packages/dd-trace/src/appsec/bodies.js diff --git a/packages/dd-trace/src/appsec/bodies.js b/packages/dd-trace/src/appsec/bodies.js deleted file mode 100644 index 6317e8ebfb3..00000000000 --- a/packages/dd-trace/src/appsec/bodies.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const bodies = new WeakMap() - -function setRequestBody (req, body) { - bodies.set(req, body) -} - -function getRequestBody (req) { - return bodies.get(req) -} - -function deleteRequestBody (req) { - bodies.delete(req) -} - -module.exports = { - getRequestBody, - setRequestBody, - deleteRequestBody -} diff --git a/packages/dd-trace/src/appsec/index.js b/packages/dd-trace/src/appsec/index.js index bde536060b9..093e52db8be 100644 --- a/packages/dd-trace/src/appsec/index.js +++ b/packages/dd-trace/src/appsec/index.js @@ -40,10 +40,10 @@ const { storage } = require('../../../datadog-core') const graphql = require('./graphql') const rasp = require('./rasp') const { isInServerlessEnvironment } = require('../serverless') -const { setRequestBody, deleteRequestBody } = require('./bodies') const responseAnalyzedSet = new WeakSet() const storedResponseHeaders = new WeakMap() +const storedBodies = new WeakMap() let isEnabled = false let config @@ -117,7 +117,7 @@ function onRequestBodyParsed ({ req, res, body, abortController }) { if (!req.body) { // do not store body if it is in req.body - setRequestBody(req, body) + storedBodies.set(req, body) } const results = waf.run({ @@ -206,12 +206,13 @@ function incomingHttpEndTranslator ({ req, res }) { const storedHeaders = storedResponseHeaders.get(req) || {} - Reporter.finishRequest(req, res, storedHeaders) + const body = req.body || storedBodies.get(req) + Reporter.finishRequest(req, res, storedHeaders, body) if (storedHeaders) { storedResponseHeaders.delete(req) } - deleteRequestBody(req) + storedBodies.delete(req) } function onPassportVerify ({ framework, login, user, success, abortController }) { diff --git a/packages/dd-trace/src/appsec/reporter.js b/packages/dd-trace/src/appsec/reporter.js index b5f4c432704..c20666038e2 100644 --- a/packages/dd-trace/src/appsec/reporter.js +++ b/packages/dd-trace/src/appsec/reporter.js @@ -19,7 +19,6 @@ const { const { keepTrace } = require('../priority_sampler') const { ASM } = require('../standalone/product') const { DIAGNOSTIC_KEYS } = require('./waf/diagnostics') -const { getRequestBody } = require('./bodies') const REQUEST_HEADER_TAG_PREFIX = 'http.request.headers.' const RESPONSE_HEADER_TAG_PREFIX = 'http.response.headers.' @@ -484,7 +483,7 @@ function reportAttributes (attributes) { rootSpan.addTags(tags) } -function finishRequest (req, res, storedResponseHeaders) { +function finishRequest (req, res, storedResponseHeaders, requestBody) { const rootSpan = web.root(req) if (!rootSpan) return @@ -544,11 +543,7 @@ function finishRequest (req, res, storedResponseHeaders) { ) if (extendedDataCollection) { - let body = req.body - if (!body) { - body = getRequestBody(req) - } - reportRequestBody(rootSpan, body) + reportRequestBody(rootSpan, requestBody) } if (tags['appsec.event'] === 'true' && typeof req.route?.path === 'string') { From 078b351c8f3a5bafa67b6c3c71904fde4eca0e1a Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Thu, 9 Oct 2025 15:26:46 +0200 Subject: [PATCH 3/9] Fix test --- packages/dd-trace/test/appsec/index.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/dd-trace/test/appsec/index.spec.js b/packages/dd-trace/test/appsec/index.spec.js index e349c3bbe76..3a4492a3172 100644 --- a/packages/dd-trace/test/appsec/index.spec.js +++ b/packages/dd-trace/test/appsec/index.spec.js @@ -422,7 +422,7 @@ describe('AppSec Index', function () { expect(waf.run).to.have.not.been.called - expect(Reporter.finishRequest).to.have.been.calledOnceWithExactly(req, res, {}) + expect(Reporter.finishRequest).to.have.been.calledOnceWithExactly(req, res, {}, undefined) }) it('should pass stored response headers to Reporter.finishRequest', () => { @@ -467,7 +467,7 @@ describe('AppSec Index', function () { AppSec.incomingHttpEndTranslator({ req, res }) - expect(Reporter.finishRequest).to.have.been.calledOnceWithExactly(req, res, storedHeaders) + expect(Reporter.finishRequest).to.have.been.calledOnceWithExactly(req, res, storedHeaders, undefined) }) it('should not propagate incoming http end data with invalid framework properties', () => { @@ -505,7 +505,7 @@ describe('AppSec Index', function () { expect(waf.run).to.have.not.been.called - expect(Reporter.finishRequest).to.have.been.calledOnceWithExactly(req, res, {}) + expect(Reporter.finishRequest).to.have.been.calledOnceWithExactly(req, res, {}, undefined) }) it('should propagate incoming http end data with express', () => { @@ -555,7 +555,7 @@ describe('AppSec Index', function () { 'server.request.query': { b: '2' } } }, req) - expect(Reporter.finishRequest).to.have.been.calledOnceWithExactly(req, res, {}) + expect(Reporter.finishRequest).to.have.been.calledOnceWithExactly(req, res, {}, req.body) }) }) From 35ad530e8e323f63900271282cec37b62dde21ec Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Thu, 9 Oct 2025 17:09:43 +0200 Subject: [PATCH 4/9] Add nextjs tests --- ...tended-data-collection.next.plugin.spec.js | 396 ++++++++++++++++++ .../redacted-headers/route.js | 18 + .../app/api/extended-data-collection/route.js | 20 + .../next/app-dir/app/api/test-text/route.js | 7 + .../datadog-extended-data-collection.js | 9 + .../test/appsec/next/app-dir/datadog.js | 1 + .../datadog-extended-data-collection.js | 8 + .../test/appsec/next/pages-dir/datadog.js | 1 + .../api/extended-data-collection/index.js | 15 + .../redacted-headers/index.js | 18 + 10 files changed, 493 insertions(+) create mode 100644 packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js create mode 100644 packages/dd-trace/test/appsec/next/app-dir/app/api/extended-data-collection/redacted-headers/route.js create mode 100644 packages/dd-trace/test/appsec/next/app-dir/app/api/extended-data-collection/route.js create mode 100644 packages/dd-trace/test/appsec/next/app-dir/datadog-extended-data-collection.js create mode 100644 packages/dd-trace/test/appsec/next/pages-dir/datadog-extended-data-collection.js create mode 100644 packages/dd-trace/test/appsec/next/pages-dir/pages/api/extended-data-collection/index.js create mode 100644 packages/dd-trace/test/appsec/next/pages-dir/pages/api/extended-data-collection/redacted-headers/index.js diff --git a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js new file mode 100644 index 00000000000..2240f70e544 --- /dev/null +++ b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js @@ -0,0 +1,396 @@ +'use strict' + +const { spawn, execSync } = require('child_process') +const { cpSync, mkdirSync, rmSync, unlinkSync } = require('fs') +const axios = require('axios') +const { writeFileSync } = require('fs') +const { satisfies } = require('semver') +const path = require('path') +const assert = require('assert') +const msgpack = require('@msgpack/msgpack') + +const agent = require('../plugins/agent') +const { NODE_MAJOR, NODE_MINOR, NODE_PATCH } = require('../../../../version') +const { withVersions } = require('../setup/mocha') + +function findWebSpan (traces) { + for (const trace of traces) { + for (const span of trace) { + if (span.type === 'web') { + return span + } + } + } + throw new Error('web span not found') +} + +function createDeepObject (sheetValue, currentLevel = 1, max = 20) { + if (currentLevel === max) { + return { + [`s-${currentLevel}`]: `s-${currentLevel}`, + [`o-${currentLevel}`]: sheetValue + } + } + + return { + [`s-${currentLevel}`]: `s-${currentLevel}`, + [`o-${currentLevel}`]: createDeepObject(sheetValue, currentLevel + 1, max) + } +} + +describe.only('extended data collection', () => { + let server + let port + + const satisfiesStandalone = version => satisfies(version, '>=12.0.0') + + withVersions('next', 'next', '>=11.1', version => { + if (version === '>=11.0.0 <13' && NODE_MAJOR === 24 && + NODE_MINOR === 0 && NODE_PATCH === 0) { + // node 24.0.0 fails, but 24.0.1 works + } + + const realVersion = require(`../../../../versions/next@${version}`).version() + + function initApp (appName) { + const appDir = path.join(__dirname, 'next', appName) + + before(async function () { + this.timeout(300 * 1000) // Webpack is very slow and builds on every test run + + const cwd = appDir + + const pkg = require(`../../../../versions/next@${version}/package.json`) + + if (realVersion.startsWith('10')) { + return this.skip() // TODO: Figure out why 10.x tests fail. + } + delete pkg.workspaces + + // builds fail for next.js 9.5 using node 14 due to webpack issues + // note that webpack version cannot be set in v9.5 in next.config.js so we do it here instead + // the link below highlights the initial support for webpack 5 (used to fix this issue) in next.js 9.5 + // https://nextjs.org/blog/next-9-5#webpack-5-support-beta + if (realVersion.startsWith('9')) pkg.resolutions = { webpack: '^5.0.0' } + + writeFileSync(`${appDir}/package.json`, JSON.stringify(pkg, null, 2)) + + // installing here for standalone purposes, copying `nodules` above was not generating the server file properly + // if there is a way to re-use nodules from somewhere in the versions folder, this `execSync` will be reverted + try { + execSync('yarn install', { cwd }) + } catch (e) { // retry in case of error from registry + execSync('yarn install', { cwd }) + } + + // building in-process makes tests fail for an unknown reason + execSync('NODE_OPTIONS=--openssl-legacy-provider yarn exec next build', { + cwd, + env: { + ...process.env, + version + }, + stdio: ['pipe', 'ignore', 'pipe'] + }) + + if (satisfiesStandalone(realVersion)) { + // copy public and static files to the `standalone` folder + // const publicOrigin = `${appDir}/public` + const publicDestination = path.join(appDir, '.next/standalone/public') + const rulesFileOrigin = path.join(__dirname, 'extended-data-collection.rules.json') + const rulesFileDestination = path.join(appDir, '.next/standalone/extended-data-collection.rules.json') + + mkdirSync(publicDestination) + cpSync(rulesFileOrigin, rulesFileDestination) + } + }) + + after(function () { + this.timeout(5000) + + const files = [ + 'package.json', + 'yarn.lock' + ] + const filePaths = files.map(file => `${appDir}/${file}`) + filePaths.forEach(path => { + unlinkSync(path) + }) + + const dirs = [ + 'node_modules', + '.next' + ] + const dirPaths = dirs.map(file => `${appDir}/${file}`) + dirPaths.forEach(path => { + rmSync(path, { recursive: true, force: true }) + }) + }) + } + + const startServer = ({ appName, serverPath }, schemaVersion = 'v0', defaultToGlobalService = false) => { + const appDir = path.join(__dirname, 'next', appName) + + before(async () => { + return agent.load('next') + }) + + before(function (done) { + this.timeout(300 * 1000) + const cwd = appDir + + server = spawn('node', [serverPath], { + cwd, + env: { + ...process.env, + VERSION: version, + PORT: 0, + DD_TRACE_AGENT_PORT: agent.server.address().port, + DD_TRACE_SPAN_ATTRIBUTE_SCHEMA: schemaVersion, + DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED: defaultToGlobalService, + NODE_OPTIONS: `--require ${appDir}/datadog-extended-data-collection.js`, + HOSTNAME: '127.0.0.1' + } + }) + + server.once('error', done) + + function waitUntilServerStarted (chunk) { + const chunkStr = chunk.toString() + const match = chunkStr.match(/port:? (\d+)/) || + chunkStr.match(/http:\/\/127\.0\.0\.1:(\d+)/) + + if (match) { + port = Number(match[1]) + server.stdout.off('data', waitUntilServerStarted) + done() + } + } + server.stdout.on('data', waitUntilServerStarted) + + server.stderr.on('data', chunk => process.stderr.write(chunk)) + server.stdout.on('data', chunk => process.stdout.write(chunk)) + }) + + after(async function () { + this.timeout(5000) + + server.kill() + + await agent.close({ ritmReset: false }) + }) + } + + const tests = [ + { + appName: 'pages-dir', + serverPath: 'server' + } + ] + + if (satisfies(realVersion, '>=13.2') && (NODE_MAJOR < 24 || satisfies(realVersion, '!=13.2'))) { + tests.push({ + appName: 'app-dir', + serverPath: '.next/standalone/server.js' + }) + } + + tests.forEach(({ appName, serverPath }) => { + describe(`extended data collection in ${appName}`, () => { + initApp(appName) + + startServer({ appName, serverPath }) + + it('Should collect nothing when no extended_data_collection is triggered', async () => { + const requestBody = { + other: 'other', + chained: { + child: 'one', + child2: 2 + } + } + await axios.post( + `http://localhost:${port}/api/extended-data-collection`, + requestBody, + { + headers: { + 'custom-header-key-1': 'custom-header-value-1', + 'custom-header-key-2': 'custom-header-value-2', + 'custom-header-key-3': 'custom-header-value-3' + } + } + ) + + await agent.assertSomeTraces((traces) => { + const span = findWebSpan(traces) + + assert.strictEqual(span.meta['http.request.headers.custom-request-header-1'], undefined) + assert.strictEqual(span.meta['http.request.headers.custom-request-header-2'], undefined) + assert.strictEqual(span.meta['http.request.headers.custom-request-header-3'], undefined) + + assert.strictEqual(span.meta['http.response.headers.custom-response-header-1'], undefined) + assert.strictEqual(span.meta['http.response.headers.custom-response-header-2'], undefined) + assert.strictEqual(span.meta['http.response.headers.custom-response-header-3'], undefined) + + const rawMetaStructBody = span.meta_struct?.['http.request.body'] + assert.strictEqual(rawMetaStructBody, undefined) + }) + }) + + it('Should redact request/response headers', async () => { + const requestBody = { + bodyParam: 'collect-standard' + } + await axios.post( + `http://localhost:${port}/api/extended-data-collection/redacted-headers`, + requestBody, + { + headers: { + authorization: 'header-value-1', + 'proxy-authorization': 'header-value-2', + 'www-authenticate': 'header-value-3', + 'proxy-authenticate': 'header-value-4', + 'authentication-info': 'header-value-5', + 'proxy-authentication-info': 'header-value-6', + cookie: 'header-value-7', + 'set-cookie': 'header-value-8' + } + } + ) + + await agent.assertSomeTraces((traces) => { + const span = findWebSpan(traces) + + assert.strictEqual(span.meta['http.request.headers.authorization'], '') + assert.strictEqual(span.meta['http.request.headers.proxy-authorization'], '') + assert.strictEqual(span.meta['http.request.headers.www-authenticate'], '') + assert.strictEqual(span.meta['http.request.headers.proxy-authenticate'], '') + assert.strictEqual(span.meta['http.request.headers.authentication-info'], '') + assert.strictEqual(span.meta['http.request.headers.proxy-authentication-info'], '') + assert.strictEqual(span.meta['http.request.headers.cookie'], '') + assert.strictEqual(span.meta['http.request.headers.set-cookie'], '') + + assert.strictEqual(span.meta['http.response.headers.authorization'], '') + assert.strictEqual(span.meta['http.response.headers.proxy-authorization'], '') + assert.strictEqual(span.meta['http.response.headers.www-authenticate'], '') + assert.strictEqual(span.meta['http.response.headers.proxy-authenticate'], '') + assert.strictEqual(span.meta['http.response.headers.authentication-info'], '') + assert.strictEqual(span.meta['http.response.headers.proxy-authentication-info'], '') + assert.strictEqual(span.meta['http.response.headers.cookie'], '') + assert.strictEqual(span.meta['http.response.headers.set-cookie'], '') + }) + }) + + it('Should collect request body and request/response with a max of 8 headers', async () => { + const requestBody = { + bodyParam: 'collect-few-headers', + other: 'other', + chained: { + child: 'one', + child2: 2 + } + } + await axios.post( + `http://localhost:${port}/api/extended-data-collection`, + requestBody, + { + headers: { + 'custom-request-header-1': 'custom-request-header-value-1', + 'custom-request-header-2': 'custom-request-header-value-2', + 'custom-request-header-3': 'custom-request-header-value-3', + 'custom-request-header-4': 'custom-request-header-value-4', + 'custom-request-header-5': 'custom-request-header-value-5', + 'custom-request-header-6': 'custom-request-header-value-6', + 'custom-request-header-7': 'custom-request-header-value-7', + 'custom-request-header-8': 'custom-request-header-value-8', + 'custom-request-header-9': 'custom-request-header-value-9', + 'custom-request-header-10': 'custom-request-header-value-10' + } + } + ) + + await agent.assertSomeTraces((traces) => { + const span = findWebSpan(traces) + + const collectedRequestHeaders = Object.keys(span.meta) + .filter(metaKey => metaKey.startsWith('http.request.headers.')).length + const collectedResponseHeaders = Object.keys(span.meta) + .filter(metaKey => metaKey.startsWith('http.response.headers.')).length + assert.strictEqual(collectedRequestHeaders, 8) + assert.strictEqual(collectedResponseHeaders, 8) + + assert.ok(span.metrics['_dd.appsec.request.header_collection.discarded'] >= 2) + assert.ok(span.metrics['_dd.appsec.response.header_collection.discarded'] >= 2) + + const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) + assert.deepEqual(metaStructBody, requestBody) + }) + }) + + it('Should truncate the request body when depth is more than 20 levels', async () => { + const deepObject = createDeepObject('sheet') + + const requestBody = { + bodyParam: 'collect-standard', + deepObject + } + + const expectedDeepTruncatedObject = createDeepObject({ 's-19': 's-19' }, 1, 18) + const expectedRequestBody = { + bodyParam: 'collect-standard', + deepObject: expectedDeepTruncatedObject + } + await axios.post(`http://localhost:${port}/api/extended-data-collection`, requestBody) + + await agent.assertSomeTraces((traces) => { + const span = findWebSpan(traces) + + const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) + assert.deepEqual(metaStructBody, expectedRequestBody) + }) + }) + + it('Should truncate the request body when string length is more than 4096 characters', async () => { + const requestBody = { + bodyParam: 'collect-standard', + longValue: Array(5000).fill('A').join('') + } + + const expectedRequestBody = { + bodyParam: 'collect-standard', + longValue: Array(4096).fill('A').join('') + } + await axios.post(`http://localhost:${port}/api/extended-data-collection`, requestBody) + + await agent.assertSomeTraces((traces) => { + const span = findWebSpan(traces) + + const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) + assert.deepEqual(metaStructBody, expectedRequestBody) + }) + }) + + it('Should truncate the request body when a node has more than 256 elements', async () => { + const children = Array(300).fill('item') + const requestBody = { + bodyParam: 'collect-standard', + children + } + + const expectedRequestBody = { + bodyParam: 'collect-standard', + children: children.slice(0, 256) + } + await axios.post(`http://localhost:${port}/api/extended-data-collection`, requestBody) + + await agent.assertSomeTraces((traces) => { + const span = findWebSpan(traces) + + const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) + assert.deepEqual(metaStructBody, expectedRequestBody) + }) + }) + }) + }) + }) +}) diff --git a/packages/dd-trace/test/appsec/next/app-dir/app/api/extended-data-collection/redacted-headers/route.js b/packages/dd-trace/test/appsec/next/app-dir/app/api/extended-data-collection/redacted-headers/route.js new file mode 100644 index 00000000000..8a0bb8722c7 --- /dev/null +++ b/packages/dd-trace/test/appsec/next/app-dir/app/api/extended-data-collection/redacted-headers/route.js @@ -0,0 +1,18 @@ +import { NextResponse } from 'next/server' + +export async function POST (request) { + const body = await request.json() + return NextResponse.json({ body }, { + status: 200, + headers: { + authorization: 'header-value-1', + 'proxy-authorization': 'header-value-2', + 'www-authenticate': 'header-value-4', + 'proxy-authenticate': 'header-value-5', + 'authentication-info': 'header-value-6', + 'proxy-authentication-info': 'header-value-7', + cookie: 'header-value-8', + 'set-cookie': 'header-value-9' + } + }) +} diff --git a/packages/dd-trace/test/appsec/next/app-dir/app/api/extended-data-collection/route.js b/packages/dd-trace/test/appsec/next/app-dir/app/api/extended-data-collection/route.js new file mode 100644 index 00000000000..a66ab893cec --- /dev/null +++ b/packages/dd-trace/test/appsec/next/app-dir/app/api/extended-data-collection/route.js @@ -0,0 +1,20 @@ +import { NextResponse } from 'next/server' + +export async function POST (request) { + const body = await request.json() + return NextResponse.json({ body }, { + status: 200, + headers: { + 'custom-response-header-1': 'custom-response-header-value-1', + 'custom-response-header-2': 'custom-response-header-value-2', + 'custom-response-header-3': 'custom-response-header-value-3', + 'custom-response-header-4': 'custom-response-header-value-4', + 'custom-response-header-5': 'custom-response-header-value-5', + 'custom-response-header-6': 'custom-response-header-value-6', + 'custom-response-header-7': 'custom-response-header-value-7', + 'custom-response-header-8': 'custom-response-header-value-8', + 'custom-response-header-9': 'custom-response-header-value-9', + 'custom-response-header-10': 'custom-response-header-value-10' + } + }) +} diff --git a/packages/dd-trace/test/appsec/next/app-dir/app/api/test-text/route.js b/packages/dd-trace/test/appsec/next/app-dir/app/api/test-text/route.js index c5a72eda4d0..21c4c3cdc46 100644 --- a/packages/dd-trace/test/appsec/next/app-dir/app/api/test-text/route.js +++ b/packages/dd-trace/test/appsec/next/app-dir/app/api/test-text/route.js @@ -5,5 +5,12 @@ export async function POST (request) { now: Date.now(), cache: 'no-store', data: body + }, { + status: 200, + headers: { + 'custom-response-header-1': 'custom-response-header-value-1', + 'custom-response-header-2': 'custom-response-header-value-2', + 'Content-Type': 'application/json' + } }) } diff --git a/packages/dd-trace/test/appsec/next/app-dir/datadog-extended-data-collection.js b/packages/dd-trace/test/appsec/next/app-dir/datadog-extended-data-collection.js new file mode 100644 index 00000000000..bfe96b9e5a6 --- /dev/null +++ b/packages/dd-trace/test/appsec/next/app-dir/datadog-extended-data-collection.js @@ -0,0 +1,9 @@ +const path = require('path') + +module.exports = require('../../../..').init({ + flushInterval: 0, + appsec: { + enabled: true, + rules: path.join(__dirname, '..', '..', 'extended-data-collection.rules.json') + } +}) diff --git a/packages/dd-trace/test/appsec/next/app-dir/datadog.js b/packages/dd-trace/test/appsec/next/app-dir/datadog.js index 5e5978ba197..fe5067ab0dc 100644 --- a/packages/dd-trace/test/appsec/next/app-dir/datadog.js +++ b/packages/dd-trace/test/appsec/next/app-dir/datadog.js @@ -1,5 +1,6 @@ const path = require('path') module.exports = require('../../../..').init({ + flushInterval: 0, appsec: { enabled: true, rules: path.join(__dirname, 'appsec-rules.json') diff --git a/packages/dd-trace/test/appsec/next/pages-dir/datadog-extended-data-collection.js b/packages/dd-trace/test/appsec/next/pages-dir/datadog-extended-data-collection.js new file mode 100644 index 00000000000..0511e3ae759 --- /dev/null +++ b/packages/dd-trace/test/appsec/next/pages-dir/datadog-extended-data-collection.js @@ -0,0 +1,8 @@ +const path = require('path') +module.exports = require('../../../..').init({ + flushInterval: 0, + appsec: { + enabled: true, + rules: path.join(__dirname, '..', '..', 'extended-data-collection.rules.json') + } +}) diff --git a/packages/dd-trace/test/appsec/next/pages-dir/datadog.js b/packages/dd-trace/test/appsec/next/pages-dir/datadog.js index 5e5978ba197..fe5067ab0dc 100644 --- a/packages/dd-trace/test/appsec/next/pages-dir/datadog.js +++ b/packages/dd-trace/test/appsec/next/pages-dir/datadog.js @@ -1,5 +1,6 @@ const path = require('path') module.exports = require('../../../..').init({ + flushInterval: 0, appsec: { enabled: true, rules: path.join(__dirname, 'appsec-rules.json') diff --git a/packages/dd-trace/test/appsec/next/pages-dir/pages/api/extended-data-collection/index.js b/packages/dd-trace/test/appsec/next/pages-dir/pages/api/extended-data-collection/index.js new file mode 100644 index 00000000000..099f58a81df --- /dev/null +++ b/packages/dd-trace/test/appsec/next/pages-dir/pages/api/extended-data-collection/index.js @@ -0,0 +1,15 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction + +export default async function POST (req, res) { + res.setHeader('custom-response-header-1', 'custom-response-header-value-1') + res.setHeader('custom-response-header-2', 'custom-response-header-value-2') + res.setHeader('custom-response-header-3', 'custom-response-header-value-3') + res.setHeader('custom-response-header-4', 'custom-response-header-value-4') + res.setHeader('custom-response-header-5', 'custom-response-header-value-5') + res.setHeader('custom-response-header-6', 'custom-response-header-value-6') + res.setHeader('custom-response-header-7', 'custom-response-header-value-7') + res.setHeader('custom-response-header-8', 'custom-response-header-value-8') + res.setHeader('custom-response-header-9', 'custom-response-header-value-9') + res.setHeader('custom-response-header-10', 'custom-response-header-value-10') + res.end('DONE') +} diff --git a/packages/dd-trace/test/appsec/next/pages-dir/pages/api/extended-data-collection/redacted-headers/index.js b/packages/dd-trace/test/appsec/next/pages-dir/pages/api/extended-data-collection/redacted-headers/index.js new file mode 100644 index 00000000000..3fbb5828fe9 --- /dev/null +++ b/packages/dd-trace/test/appsec/next/pages-dir/pages/api/extended-data-collection/redacted-headers/index.js @@ -0,0 +1,18 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction + +export default async function POST (req, res) { + res.setHeader('authorization', 'header-value-1') + res.setHeader('proxy-authorization', 'header-value-2') + res.setHeader('www-authenticate', 'header-value-4') + res.setHeader('proxy-authenticate', 'header-value-5') + res.setHeader('authentication-info', 'header-value-6') + res.setHeader('proxy-authentication-info', 'header-value-7') + res.setHeader('cookie', 'header-value-8') + res.setHeader('set-cookie', 'header-value-9') + + const body = req.body + res.status(200).json({ + cache: 'no-store', + data: body + }) +} From 232415365508c40982baa4b6513894e692b70dfd Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Thu, 9 Oct 2025 17:14:05 +0200 Subject: [PATCH 5/9] Fix test --- .../test/appsec/extended-data-collection.next.plugin.spec.js | 3 --- packages/dd-trace/test/appsec/reporter.spec.js | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js index 2240f70e544..596cf073975 100644 --- a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js +++ b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js @@ -97,11 +97,8 @@ describe.only('extended data collection', () => { // copy public and static files to the `standalone` folder // const publicOrigin = `${appDir}/public` const publicDestination = path.join(appDir, '.next/standalone/public') - const rulesFileOrigin = path.join(__dirname, 'extended-data-collection.rules.json') - const rulesFileDestination = path.join(appDir, '.next/standalone/extended-data-collection.rules.json') mkdirSync(publicDestination) - cpSync(rulesFileOrigin, rulesFileDestination) } }) diff --git a/packages/dd-trace/test/appsec/reporter.spec.js b/packages/dd-trace/test/appsec/reporter.spec.js index b175869cd12..898f8e533c8 100644 --- a/packages/dd-trace/test/appsec/reporter.spec.js +++ b/packages/dd-trace/test/appsec/reporter.spec.js @@ -683,7 +683,7 @@ describe('reporter', () => { span.context()._tags = { '_dd.appsec.rasp.request_body_size.exceeded': 'true' } const res = {} - Reporter.finishRequest(req, res, {}) + Reporter.finishRequest(req, res, {}, req.body) expect(span.setTag).to.have.been.calledWithExactly('_dd.appsec.request_body_size.exceeded', 'true') }) From f7aadf395e3092c1199123fe315ef2fa4bff0411 Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Fri, 10 Oct 2025 13:23:54 +0200 Subject: [PATCH 6/9] Fix tests --- ...extended-data-collection.next.plugin.spec.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js index 596cf073975..5413c7f5e34 100644 --- a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js +++ b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js @@ -1,7 +1,7 @@ 'use strict' const { spawn, execSync } = require('child_process') -const { cpSync, mkdirSync, rmSync, unlinkSync } = require('fs') +const { mkdirSync, rmSync, unlinkSync } = require('fs') const axios = require('axios') const { writeFileSync } = require('fs') const { satisfies } = require('semver') @@ -38,7 +38,7 @@ function createDeepObject (sheetValue, currentLevel = 1, max = 20) { } } -describe.only('extended data collection', () => { +describe('extended data collection', () => { let server let port @@ -206,8 +206,9 @@ describe.only('extended data collection', () => { child2: 2 } } + await axios.post( - `http://localhost:${port}/api/extended-data-collection`, + `http://127.0.0.1:${port}/api/extended-data-collection`, requestBody, { headers: { @@ -239,7 +240,7 @@ describe.only('extended data collection', () => { bodyParam: 'collect-standard' } await axios.post( - `http://localhost:${port}/api/extended-data-collection/redacted-headers`, + `http://127.0.0.1:${port}/api/extended-data-collection/redacted-headers`, requestBody, { headers: { @@ -288,7 +289,7 @@ describe.only('extended data collection', () => { } } await axios.post( - `http://localhost:${port}/api/extended-data-collection`, + `http://127.0.0.1:${port}/api/extended-data-collection`, requestBody, { headers: { @@ -337,7 +338,7 @@ describe.only('extended data collection', () => { bodyParam: 'collect-standard', deepObject: expectedDeepTruncatedObject } - await axios.post(`http://localhost:${port}/api/extended-data-collection`, requestBody) + await axios.post(`http://127.0.0.1:${port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { const span = findWebSpan(traces) @@ -357,7 +358,7 @@ describe.only('extended data collection', () => { bodyParam: 'collect-standard', longValue: Array(4096).fill('A').join('') } - await axios.post(`http://localhost:${port}/api/extended-data-collection`, requestBody) + await axios.post(`http://127.0.0.1:${port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { const span = findWebSpan(traces) @@ -378,7 +379,7 @@ describe.only('extended data collection', () => { bodyParam: 'collect-standard', children: children.slice(0, 256) } - await axios.post(`http://localhost:${port}/api/extended-data-collection`, requestBody) + await axios.post(`http://127.0.0.1:${port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { const span = findWebSpan(traces) From 42bd4e7b2e1099ef7a5e5d560de1258d53d897a8 Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Fri, 10 Oct 2025 15:17:15 +0200 Subject: [PATCH 7/9] Reusing nextjs initialization code --- ...tended-data-collection.next.plugin.spec.js | 155 ++---------------- .../test/appsec/index.next.plugin.spec.js | 155 +----------------- packages/dd-trace/test/appsec/next.utils.js | 148 +++++++++++++++++ 3 files changed, 168 insertions(+), 290 deletions(-) create mode 100644 packages/dd-trace/test/appsec/next.utils.js diff --git a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js index 5413c7f5e34..65575d6bc03 100644 --- a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js +++ b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js @@ -1,17 +1,15 @@ 'use strict' -const { spawn, execSync } = require('child_process') -const { mkdirSync, rmSync, unlinkSync } = require('fs') +const assert = require('assert') + const axios = require('axios') -const { writeFileSync } = require('fs') const { satisfies } = require('semver') -const path = require('path') -const assert = require('assert') const msgpack = require('@msgpack/msgpack') const agent = require('../plugins/agent') const { NODE_MAJOR, NODE_MINOR, NODE_PATCH } = require('../../../../version') const { withVersions } = require('../setup/mocha') +const { initApp, startServer } = require('./next.utils') function findWebSpan (traces) { for (const trace of traces) { @@ -39,11 +37,6 @@ function createDeepObject (sheetValue, currentLevel = 1, max = 20) { } describe('extended data collection', () => { - let server - let port - - const satisfiesStandalone = version => satisfies(version, '>=12.0.0') - withVersions('next', 'next', '>=11.1', version => { if (version === '>=11.0.0 <13' && NODE_MAJOR === 24 && NODE_MINOR === 0 && NODE_PATCH === 0) { @@ -52,132 +45,6 @@ describe('extended data collection', () => { const realVersion = require(`../../../../versions/next@${version}`).version() - function initApp (appName) { - const appDir = path.join(__dirname, 'next', appName) - - before(async function () { - this.timeout(300 * 1000) // Webpack is very slow and builds on every test run - - const cwd = appDir - - const pkg = require(`../../../../versions/next@${version}/package.json`) - - if (realVersion.startsWith('10')) { - return this.skip() // TODO: Figure out why 10.x tests fail. - } - delete pkg.workspaces - - // builds fail for next.js 9.5 using node 14 due to webpack issues - // note that webpack version cannot be set in v9.5 in next.config.js so we do it here instead - // the link below highlights the initial support for webpack 5 (used to fix this issue) in next.js 9.5 - // https://nextjs.org/blog/next-9-5#webpack-5-support-beta - if (realVersion.startsWith('9')) pkg.resolutions = { webpack: '^5.0.0' } - - writeFileSync(`${appDir}/package.json`, JSON.stringify(pkg, null, 2)) - - // installing here for standalone purposes, copying `nodules` above was not generating the server file properly - // if there is a way to re-use nodules from somewhere in the versions folder, this `execSync` will be reverted - try { - execSync('yarn install', { cwd }) - } catch (e) { // retry in case of error from registry - execSync('yarn install', { cwd }) - } - - // building in-process makes tests fail for an unknown reason - execSync('NODE_OPTIONS=--openssl-legacy-provider yarn exec next build', { - cwd, - env: { - ...process.env, - version - }, - stdio: ['pipe', 'ignore', 'pipe'] - }) - - if (satisfiesStandalone(realVersion)) { - // copy public and static files to the `standalone` folder - // const publicOrigin = `${appDir}/public` - const publicDestination = path.join(appDir, '.next/standalone/public') - - mkdirSync(publicDestination) - } - }) - - after(function () { - this.timeout(5000) - - const files = [ - 'package.json', - 'yarn.lock' - ] - const filePaths = files.map(file => `${appDir}/${file}`) - filePaths.forEach(path => { - unlinkSync(path) - }) - - const dirs = [ - 'node_modules', - '.next' - ] - const dirPaths = dirs.map(file => `${appDir}/${file}`) - dirPaths.forEach(path => { - rmSync(path, { recursive: true, force: true }) - }) - }) - } - - const startServer = ({ appName, serverPath }, schemaVersion = 'v0', defaultToGlobalService = false) => { - const appDir = path.join(__dirname, 'next', appName) - - before(async () => { - return agent.load('next') - }) - - before(function (done) { - this.timeout(300 * 1000) - const cwd = appDir - - server = spawn('node', [serverPath], { - cwd, - env: { - ...process.env, - VERSION: version, - PORT: 0, - DD_TRACE_AGENT_PORT: agent.server.address().port, - DD_TRACE_SPAN_ATTRIBUTE_SCHEMA: schemaVersion, - DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED: defaultToGlobalService, - NODE_OPTIONS: `--require ${appDir}/datadog-extended-data-collection.js`, - HOSTNAME: '127.0.0.1' - } - }) - - server.once('error', done) - - function waitUntilServerStarted (chunk) { - const chunkStr = chunk.toString() - const match = chunkStr.match(/port:? (\d+)/) || - chunkStr.match(/http:\/\/127\.0\.0\.1:(\d+)/) - - if (match) { - port = Number(match[1]) - server.stdout.off('data', waitUntilServerStarted) - done() - } - } - server.stdout.on('data', waitUntilServerStarted) - - server.stderr.on('data', chunk => process.stderr.write(chunk)) - server.stdout.on('data', chunk => process.stdout.write(chunk)) - }) - - after(async function () { - this.timeout(5000) - - server.kill() - - await agent.close({ ritmReset: false }) - }) - } - const tests = [ { appName: 'pages-dir', @@ -194,9 +61,9 @@ describe('extended data collection', () => { tests.forEach(({ appName, serverPath }) => { describe(`extended data collection in ${appName}`, () => { - initApp(appName) + initApp(appName, version, realVersion) - startServer({ appName, serverPath }) + const serverData = startServer(appName, serverPath, version, 'datadog-extended-data-collection.js') it('Should collect nothing when no extended_data_collection is triggered', async () => { const requestBody = { @@ -208,7 +75,7 @@ describe('extended data collection', () => { } await axios.post( - `http://127.0.0.1:${port}/api/extended-data-collection`, + `http://127.0.0.1:${serverData.port}/api/extended-data-collection`, requestBody, { headers: { @@ -240,7 +107,7 @@ describe('extended data collection', () => { bodyParam: 'collect-standard' } await axios.post( - `http://127.0.0.1:${port}/api/extended-data-collection/redacted-headers`, + `http://127.0.0.1:${serverData.port}/api/extended-data-collection/redacted-headers`, requestBody, { headers: { @@ -289,7 +156,7 @@ describe('extended data collection', () => { } } await axios.post( - `http://127.0.0.1:${port}/api/extended-data-collection`, + `http://127.0.0.1:${serverData.port}/api/extended-data-collection`, requestBody, { headers: { @@ -338,7 +205,7 @@ describe('extended data collection', () => { bodyParam: 'collect-standard', deepObject: expectedDeepTruncatedObject } - await axios.post(`http://127.0.0.1:${port}/api/extended-data-collection`, requestBody) + await axios.post(`http://127.0.0.1:${serverData.port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { const span = findWebSpan(traces) @@ -358,7 +225,7 @@ describe('extended data collection', () => { bodyParam: 'collect-standard', longValue: Array(4096).fill('A').join('') } - await axios.post(`http://127.0.0.1:${port}/api/extended-data-collection`, requestBody) + await axios.post(`http://127.0.0.1:${serverData.port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { const span = findWebSpan(traces) @@ -379,7 +246,7 @@ describe('extended data collection', () => { bodyParam: 'collect-standard', children: children.slice(0, 256) } - await axios.post(`http://127.0.0.1:${port}/api/extended-data-collection`, requestBody) + await axios.post(`http://127.0.0.1:${serverData.port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { const span = findWebSpan(traces) diff --git a/packages/dd-trace/test/appsec/index.next.plugin.spec.js b/packages/dd-trace/test/appsec/index.next.plugin.spec.js index fca1bd2732b..1e596ee9df6 100644 --- a/packages/dd-trace/test/appsec/index.next.plugin.spec.js +++ b/packages/dd-trace/test/appsec/index.next.plugin.spec.js @@ -1,22 +1,14 @@ 'use strict' -const { spawn, execSync } = require('child_process') -const { cpSync, mkdirSync, rmdirSync, unlinkSync } = require('fs') const axios = require('axios') -const { writeFileSync } = require('fs') const { satisfies } = require('semver') -const path = require('path') const agent = require('../plugins/agent') const { NODE_MAJOR, NODE_MINOR, NODE_PATCH } = require('../../../../version') const { withVersions } = require('../setup/mocha') +const { initApp, startServer } = require('./next.utils') describe('test suite', () => { - let server - let port - - const satisfiesStandalone = version => satisfies(version, '>=12.0.0') - withVersions('next', 'next', '>=11.1', version => { if (version === '>=11.0.0 <13' && NODE_MAJOR === 24 && NODE_MINOR === 0 && NODE_PATCH === 0) { @@ -25,135 +17,6 @@ describe('test suite', () => { const realVersion = require(`../../../../versions/next@${version}`).version() - function initApp (appName) { - const appDir = path.join(__dirname, 'next', appName) - - before(async function () { - this.timeout(300 * 1000) // Webpack is very slow and builds on every test run - - const cwd = appDir - - const pkg = require(`../../../../versions/next@${version}/package.json`) - - if (realVersion.startsWith('10')) { - return this.skip() // TODO: Figure out why 10.x tests fail. - } - delete pkg.workspaces - - // builds fail for next.js 9.5 using node 14 due to webpack issues - // note that webpack version cannot be set in v9.5 in next.config.js so we do it here instead - // the link below highlights the initial support for webpack 5 (used to fix this issue) in next.js 9.5 - // https://nextjs.org/blog/next-9-5#webpack-5-support-beta - if (realVersion.startsWith('9')) pkg.resolutions = { webpack: '^5.0.0' } - - writeFileSync(`${appDir}/package.json`, JSON.stringify(pkg, null, 2)) - - // installing here for standalone purposes, copying `nodules` above was not generating the server file properly - // if there is a way to re-use nodules from somewhere in the versions folder, this `execSync` will be reverted - try { - execSync('yarn install', { cwd }) - } catch (e) { // retry in case of error from registry - execSync('yarn install', { cwd }) - } - - // building in-process makes tests fail for an unknown reason - execSync('NODE_OPTIONS=--openssl-legacy-provider yarn exec next build', { - cwd, - env: { - ...process.env, - version - }, - stdio: ['pipe', 'ignore', 'pipe'] - }) - - if (satisfiesStandalone(realVersion)) { - // copy public and static files to the `standalone` folder - // const publicOrigin = `${appDir}/public` - const publicDestination = `${appDir}/.next/standalone/public` - const rulesFileOrigin = `${appDir}/appsec-rules.json` - const rulesFileDestination = `${appDir}/.next/standalone/appsec-rules.json` - - mkdirSync(publicDestination) - cpSync(rulesFileOrigin, rulesFileDestination) - } - }) - - after(function () { - this.timeout(5000) - - const files = [ - 'package.json', - 'yarn.lock' - ] - const filePaths = files.map(file => `${appDir}/${file}`) - filePaths.forEach(path => { - unlinkSync(path) - }) - - const dirs = [ - 'node_modules', - '.next' - ] - const dirPaths = dirs.map(file => `${appDir}/${file}`) - dirPaths.forEach(path => { - rmdirSync(path, { recursive: true, force: true }) - }) - }) - } - - const startServer = ({ appName, serverPath }, schemaVersion = 'v0', defaultToGlobalService = false) => { - const appDir = path.join(__dirname, 'next', appName) - - before(async () => { - return agent.load('next') - }) - - before(function (done) { - this.timeout(300 * 1000) - const cwd = appDir - - server = spawn('node', [serverPath], { - cwd, - env: { - ...process.env, - VERSION: version, - PORT: 0, - DD_TRACE_AGENT_PORT: agent.server.address().port, - DD_TRACE_SPAN_ATTRIBUTE_SCHEMA: schemaVersion, - DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED: defaultToGlobalService, - NODE_OPTIONS: `--require ${appDir}/datadog.js`, - HOSTNAME: '127.0.0.1' - } - }) - - server.once('error', done) - - function waitUntilServerStarted (chunk) { - const chunkStr = chunk.toString() - const match = chunkStr.match(/port:? (\d+)/) || - chunkStr.match(/http:\/\/127\.0\.0\.1:(\d+)/) - - if (match) { - port = Number(match[1]) - server.stdout.off('data', waitUntilServerStarted) - done() - } - } - server.stdout.on('data', waitUntilServerStarted) - - server.stderr.on('data', chunk => process.stderr.write(chunk)) - server.stdout.on('data', chunk => process.stdout.write(chunk)) - }) - - after(async function () { - this.timeout(5000) - - server.kill() - - await agent.close({ ritmReset: false }) - }) - } - const tests = [ { appName: 'pages-dir', @@ -189,9 +52,9 @@ describe('test suite', () => { tests.forEach(({ appName, serverPath }) => { describe(`should detect threats in ${appName}`, () => { - initApp(appName) + initApp(appName, version, realVersion) - startServer({ appName, serverPath }) + const serverData = startServer(appName, serverPath, version) it('in request body', function (done) { this.timeout(5000) @@ -200,7 +63,7 @@ describe('test suite', () => { agent.subscribe(findBodyThreat) axios - .post(`http://127.0.0.1:${port}/api/test`, { + .post(`http://127.0.0.1:${serverData.port}/api/test`, { key: 'testattack' }).catch(e => { done(e) }) }) @@ -213,7 +76,7 @@ describe('test suite', () => { agent.subscribe(findBodyThreat) axios - .post(`http://127.0.0.1:${port}/api/test-formdata`, new URLSearchParams({ + .post(`http://127.0.0.1:${serverData.port}/api/test-formdata`, new URLSearchParams({ key: 'testattack' })).catch(e => { done(e) @@ -227,7 +90,7 @@ describe('test suite', () => { const findBodyThreat = getFindBodyThreatMethod(done) agent.subscribe(findBodyThreat) axios - .post(`http://127.0.0.1:${port}/api/test-text`, { + .post(`http://127.0.0.1:${serverData.port}/api/test-text`, { key: 'testattack' }).catch(e => { done(e) @@ -241,7 +104,7 @@ describe('test suite', () => { const findBodyThreat = getFindBodyThreatMethod(done) axios - .get(`http://127.0.0.1:${port}/api/test?param=testattack`) + .get(`http://127.0.0.1:${serverData.port}/api/test?param=testattack`) .catch(e => { done(e) }) agent.subscribe(findBodyThreat) @@ -253,7 +116,7 @@ describe('test suite', () => { const findBodyThreat = getFindBodyThreatMethod(done) axios - .get(`http://127.0.0.1:${port}/api/test?param[]=safe¶m[]=testattack`) + .get(`http://127.0.0.1:${serverData.port}/api/test?param[]=safe¶m[]=testattack`) .catch(e => { done(e) }) agent.subscribe(findBodyThreat) @@ -265,7 +128,7 @@ describe('test suite', () => { const findBodyThreat = getFindBodyThreatMethod(done) axios - .get(`http://127.0.0.1:${port}/api/test?param[]=testattack¶m[]=safe`) + .get(`http://127.0.0.1:${serverData.port}/api/test?param[]=testattack¶m[]=safe`) .catch(e => { done(e) }) agent.subscribe(findBodyThreat) diff --git a/packages/dd-trace/test/appsec/next.utils.js b/packages/dd-trace/test/appsec/next.utils.js new file mode 100644 index 00000000000..9b76474b122 --- /dev/null +++ b/packages/dd-trace/test/appsec/next.utils.js @@ -0,0 +1,148 @@ +'use strict' + +const path = require('node:path') +const { execSync, spawn } = require('node:child_process') +const { mkdirSync, rmdirSync, unlinkSync, writeFileSync } = require('node:fs') + +const { satisfies } = require('semver') + +const agent = require('../plugins/agent') + +function initApp (appName, version, realVersion) { + const satisfiesStandalone = version => satisfies(version, '>=12.0.0') + + const appDir = path.join(__dirname, 'next', appName) + + before(async function () { + this.timeout(300 * 1000) // Webpack is very slow and builds on every test run + + const cwd = appDir + + const pkg = require(`../../../../versions/next@${version}/package.json`) + + if (realVersion.startsWith('10')) { + return this.skip() // TODO: Figure out why 10.x tests fail. + } + delete pkg.workspaces + + // builds fail for next.js 9.5 using node 14 due to webpack issues + // note that webpack version cannot be set in v9.5 in next.config.js so we do it here instead + // the link below highlights the initial support for webpack 5 (used to fix this issue) in next.js 9.5 + // https://nextjs.org/blog/next-9-5#webpack-5-support-beta + if (realVersion.startsWith('9')) pkg.resolutions = { webpack: '^5.0.0' } + + writeFileSync(`${appDir}/package.json`, JSON.stringify(pkg, null, 2)) + + // installing here for standalone purposes, copying `nodules` above was not generating the server file properly + // if there is a way to re-use nodules from somewhere in the versions folder, this `execSync` will be reverted + try { + execSync('yarn install', { cwd }) + } catch (e) { // retry in case of error from registry + execSync('yarn install', { cwd }) + } + + // building in-process makes tests fail for an unknown reason + execSync('NODE_OPTIONS=--openssl-legacy-provider yarn exec next build', { + cwd, + env: { + ...process.env, + version + }, + stdio: ['pipe', 'ignore', 'pipe'] + }) + + if (satisfiesStandalone(realVersion)) { + // copy public and static files to the `standalone` folder + // const publicOrigin = `${appDir}/public` + const publicDestination = `${appDir}/.next/standalone/public` + + mkdirSync(publicDestination) + } + }) + + after(function () { + this.timeout(5000) + + const files = [ + 'package.json', + 'yarn.lock' + ] + const filePaths = files.map(file => `${appDir}/${file}`) + filePaths.forEach(path => { + unlinkSync(path) + }) + + const dirs = [ + 'node_modules', + '.next' + ] + const dirPaths = dirs.map(file => `${appDir}/${file}`) + dirPaths.forEach(path => { + rmdirSync(path, { recursive: true, force: true }) + }) + }) +} + +function startServer (appName, serverPath, version, ddInitFile = 'datadog.js') { + const result = {} + let server + + const appDir = path.join(__dirname, 'next', appName) + const schemaVersion = 'v0' + const defaultToGlobalService = false + + before(async () => { + return agent.load('next') + }) + + before(function (done) { + this.timeout(300 * 1000) + const cwd = appDir + + server = spawn('node', [serverPath], { + cwd, + env: { + ...process.env, + VERSION: version, + PORT: 0, + DD_TRACE_AGENT_PORT: agent.server.address().port, + DD_TRACE_SPAN_ATTRIBUTE_SCHEMA: schemaVersion, + DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED: defaultToGlobalService, + NODE_OPTIONS: `--require ${appDir}/${ddInitFile}`, + HOSTNAME: '127.0.0.1' + } + }) + + server.once('error', done) + + function waitUntilServerStarted (chunk) { + const chunkStr = chunk.toString() + const match = chunkStr.match(/port:? (\d+)/) || + chunkStr.match(/http:\/\/127\.0\.0\.1:(\d+)/) + + if (match) { + result.port = Number(match[1]) + server.stdout.off('data', waitUntilServerStarted) + done() + } + } + server.stdout.on('data', waitUntilServerStarted) + + server.stderr.on('data', chunk => process.stderr.write(chunk)) + server.stdout.on('data', chunk => process.stdout.write(chunk)) + }) + + after(async function () { + this.timeout(5000) + + server.kill() + + await agent.close({ ritmReset: false }) + }) + + return result +} + +module.exports = { + initApp, startServer +} From 07b258152992d00b50224cfa4776b063f8f0b58e Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Thu, 16 Oct 2025 16:59:47 +0200 Subject: [PATCH 8/9] Remove unnecessary change --- .../test/appsec/next/app-dir/app/api/test-text/route.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/dd-trace/test/appsec/next/app-dir/app/api/test-text/route.js b/packages/dd-trace/test/appsec/next/app-dir/app/api/test-text/route.js index 21c4c3cdc46..c5a72eda4d0 100644 --- a/packages/dd-trace/test/appsec/next/app-dir/app/api/test-text/route.js +++ b/packages/dd-trace/test/appsec/next/app-dir/app/api/test-text/route.js @@ -5,12 +5,5 @@ export async function POST (request) { now: Date.now(), cache: 'no-store', data: body - }, { - status: 200, - headers: { - 'custom-response-header-1': 'custom-response-header-value-1', - 'custom-response-header-2': 'custom-response-header-value-2', - 'Content-Type': 'application/json' - } }) } From 67066e009380f24b50ff5dcd8f292e2bac247dd5 Mon Sep 17 00:00:00 2001 From: Ugaitz Urien Date: Thu, 16 Oct 2025 17:28:29 +0200 Subject: [PATCH 9/9] Reuse a bit of code --- ...ded-data-collection.express.plugin.spec.js | 15 +------- ...ded-data-collection.fastify.plugin.spec.js | 15 +------- ...tended-data-collection.next.plugin.spec.js | 38 ++++--------------- packages/dd-trace/test/appsec/utils.js | 18 ++++++++- 4 files changed, 26 insertions(+), 60 deletions(-) diff --git a/packages/dd-trace/test/appsec/extended-data-collection.express.plugin.spec.js b/packages/dd-trace/test/appsec/extended-data-collection.express.plugin.spec.js index a100ce5d82f..cf478e4bc5a 100644 --- a/packages/dd-trace/test/appsec/extended-data-collection.express.plugin.spec.js +++ b/packages/dd-trace/test/appsec/extended-data-collection.express.plugin.spec.js @@ -8,20 +8,7 @@ const appsec = require('../../src/appsec') const axios = require('axios') const assert = require('assert') const msgpack = require('@msgpack/msgpack') - -function createDeepObject (sheetValue, currentLevel = 1, max = 20) { - if (currentLevel === max) { - return { - [`s-${currentLevel}`]: `s-${currentLevel}`, - [`o-${currentLevel}`]: sheetValue - } - } - - return { - [`s-${currentLevel}`]: `s-${currentLevel}`, - [`o-${currentLevel}`]: createDeepObject(sheetValue, currentLevel + 1, max) - } -} +const { createDeepObject } = require('./utils') describe('extended data collection', () => { before(() => { diff --git a/packages/dd-trace/test/appsec/extended-data-collection.fastify.plugin.spec.js b/packages/dd-trace/test/appsec/extended-data-collection.fastify.plugin.spec.js index 02588a5c0e9..33e3d1a6357 100644 --- a/packages/dd-trace/test/appsec/extended-data-collection.fastify.plugin.spec.js +++ b/packages/dd-trace/test/appsec/extended-data-collection.fastify.plugin.spec.js @@ -8,20 +8,7 @@ const appsec = require('../../src/appsec') const axios = require('axios') const assert = require('assert') const msgpack = require('@msgpack/msgpack') - -function createDeepObject (sheetValue, currentLevel = 1, max = 20) { - if (currentLevel === max) { - return { - [`s-${currentLevel}`]: `s-${currentLevel}`, - [`o-${currentLevel}`]: sheetValue - } - } - - return { - [`s-${currentLevel}`]: `s-${currentLevel}`, - [`o-${currentLevel}`]: createDeepObject(sheetValue, currentLevel + 1, max) - } -} +const { createDeepObject } = require('./utils') describe('extended data collection', () => { before(() => { diff --git a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js index 65575d6bc03..d9fd22cfccd 100644 --- a/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js +++ b/packages/dd-trace/test/appsec/extended-data-collection.next.plugin.spec.js @@ -10,31 +10,7 @@ const agent = require('../plugins/agent') const { NODE_MAJOR, NODE_MINOR, NODE_PATCH } = require('../../../../version') const { withVersions } = require('../setup/mocha') const { initApp, startServer } = require('./next.utils') - -function findWebSpan (traces) { - for (const trace of traces) { - for (const span of trace) { - if (span.type === 'web') { - return span - } - } - } - throw new Error('web span not found') -} - -function createDeepObject (sheetValue, currentLevel = 1, max = 20) { - if (currentLevel === max) { - return { - [`s-${currentLevel}`]: `s-${currentLevel}`, - [`o-${currentLevel}`]: sheetValue - } - } - - return { - [`s-${currentLevel}`]: `s-${currentLevel}`, - [`o-${currentLevel}`]: createDeepObject(sheetValue, currentLevel + 1, max) - } -} +const { createDeepObject, getWebSpan } = require('./utils') describe('extended data collection', () => { withVersions('next', 'next', '>=11.1', version => { @@ -87,7 +63,7 @@ describe('extended data collection', () => { ) await agent.assertSomeTraces((traces) => { - const span = findWebSpan(traces) + const span = getWebSpan(traces) assert.strictEqual(span.meta['http.request.headers.custom-request-header-1'], undefined) assert.strictEqual(span.meta['http.request.headers.custom-request-header-2'], undefined) @@ -124,7 +100,7 @@ describe('extended data collection', () => { ) await agent.assertSomeTraces((traces) => { - const span = findWebSpan(traces) + const span = getWebSpan(traces) assert.strictEqual(span.meta['http.request.headers.authorization'], '') assert.strictEqual(span.meta['http.request.headers.proxy-authorization'], '') @@ -175,7 +151,7 @@ describe('extended data collection', () => { ) await agent.assertSomeTraces((traces) => { - const span = findWebSpan(traces) + const span = getWebSpan(traces) const collectedRequestHeaders = Object.keys(span.meta) .filter(metaKey => metaKey.startsWith('http.request.headers.')).length @@ -208,7 +184,7 @@ describe('extended data collection', () => { await axios.post(`http://127.0.0.1:${serverData.port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { - const span = findWebSpan(traces) + const span = getWebSpan(traces) const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) assert.deepEqual(metaStructBody, expectedRequestBody) @@ -228,7 +204,7 @@ describe('extended data collection', () => { await axios.post(`http://127.0.0.1:${serverData.port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { - const span = findWebSpan(traces) + const span = getWebSpan(traces) const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) assert.deepEqual(metaStructBody, expectedRequestBody) @@ -249,7 +225,7 @@ describe('extended data collection', () => { await axios.post(`http://127.0.0.1:${serverData.port}/api/extended-data-collection`, requestBody) await agent.assertSomeTraces((traces) => { - const span = findWebSpan(traces) + const span = getWebSpan(traces) const metaStructBody = msgpack.decode(span.meta_struct['http.request.body']) assert.deepEqual(metaStructBody, expectedRequestBody) diff --git a/packages/dd-trace/test/appsec/utils.js b/packages/dd-trace/test/appsec/utils.js index ec9f22ad283..311ccd0184e 100644 --- a/packages/dd-trace/test/appsec/utils.js +++ b/packages/dd-trace/test/appsec/utils.js @@ -8,9 +8,25 @@ function getWebSpan (traces) { } } } + throw new Error('web span not found') } +function createDeepObject (sheetValue, currentLevel = 1, max = 20) { + if (currentLevel === max) { + return { + [`s-${currentLevel}`]: `s-${currentLevel}`, + [`o-${currentLevel}`]: sheetValue + } + } + + return { + [`s-${currentLevel}`]: `s-${currentLevel}`, + [`o-${currentLevel}`]: createDeepObject(sheetValue, currentLevel + 1, max) + } +} + module.exports = { - getWebSpan + getWebSpan, + createDeepObject }