diff --git a/packages/dd-trace/src/appsec/rasp/fs-plugin.js b/packages/dd-trace/src/appsec/rasp/fs-plugin.js index dbd267b95e2..a632bb2d1e5 100644 --- a/packages/dd-trace/src/appsec/rasp/fs-plugin.js +++ b/packages/dd-trace/src/appsec/rasp/fs-plugin.js @@ -14,25 +14,31 @@ const enabledFor = { let fsPlugin -function enterWith (fsProps, store = storage('legacy').getStore()) { +function getStoreToStart (fsProps, store = storage('legacy').getStore()) { if (store && !store.fs?.opExcluded) { - storage('legacy').enterWith({ + return { ...store, fs: { ...store.fs, ...fsProps, parentStore: store } - }) + } } + + return store } class AppsecFsPlugin extends Plugin { enable () { - this.addSub('apm:fs:operation:start', this._onFsOperationStart) - this.addSub('apm:fs:operation:finish', this._onFsOperationFinishOrRenderEnd) - this.addSub('tracing:datadog:express:response:render:start', this._onResponseRenderStart) - this.addSub('tracing:datadog:express:response:render:end', this._onFsOperationFinishOrRenderEnd) + this.addBind('apm:fs:operation:start', this._onFsOperationStart) + this.addBind('apm:fs:operation:finish', this._onFsOperationFinishOrRenderEnd) + this.addBind('tracing:datadog:express:response:render:start', this._onResponseRenderStart) + this.addBind('tracing:datadog:express:response:render:end', this._onFsOperationFinishOrRenderEnd) + // TODO Remove this when dc-polyfill is fixed&updated + // hack to node 18 and early 20.x + // with dc-polyfill addBind is not enough to force a channel.hasSubscribers === true + this.addSub('tracing:datadog:express:response:render:start', () => {}) super.configure(true) } @@ -44,19 +50,20 @@ class AppsecFsPlugin extends Plugin { _onFsOperationStart () { const store = storage('legacy').getStore() if (store) { - enterWith({ root: store.fs?.root === undefined }, store) + return getStoreToStart({ root: store.fs?.root === undefined }, store) } } _onResponseRenderStart () { - enterWith({ opExcluded: true }) + return getStoreToStart({ opExcluded: true }) } _onFsOperationFinishOrRenderEnd () { const store = storage('legacy').getStore() - if (store?.fs?.parentStore) { - storage('legacy').enterWith(store.fs.parentStore) + if (store?.fs) { + return store.fs.parentStore } + return store } } diff --git a/packages/dd-trace/test/appsec/iast/analyzers/path-traversal-analyzer.spec.js b/packages/dd-trace/test/appsec/iast/analyzers/path-traversal-analyzer.spec.js index f914006b5e0..8e9c7abae4d 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/path-traversal-analyzer.spec.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/path-traversal-analyzer.spec.js @@ -477,6 +477,26 @@ prepareTestServerForIast('integration test', (testThatRequestHasVulnerability, t describe('test stat', () => { runFsMethodTestThreeWay('stat', 0, null, __filename) + + describe('with two calls to async method without waiting to the callback', () => { + const fsAsyncWayMethodPath = path.join(os.tmpdir(), 'fs-async-way-method.js') + + before(() => { + fs.copyFileSync(path.join(__dirname, 'resources', 'fs-async-way-method.js'), fsAsyncWayMethodPath) + }) + + after(() => { + fs.unlinkSync(fsAsyncWayMethodPath) + }) + + testThatRequestHasVulnerability(function () { + const store = storage('legacy').getStore() + const iastCtx = iastContextFunctions.getIastContext(store) + const callArgs = [fsAsyncWayMethodPath] + callArgs[0] = newTaintedString(iastCtx, callArgs[0], 'param', 'Request') + return require(fsAsyncWayMethodPath).doubleCallIgnoringCb('stat', callArgs) + }, 'PATH_TRAVERSAL', { occurrences: 2 }) + }) }) describe('test symlink', () => { diff --git a/packages/dd-trace/test/appsec/iast/analyzers/resources/fs-async-way-method.js b/packages/dd-trace/test/appsec/iast/analyzers/resources/fs-async-way-method.js index 16a599b295c..a574eaf3c75 100644 --- a/packages/dd-trace/test/appsec/iast/analyzers/resources/fs-async-way-method.js +++ b/packages/dd-trace/test/appsec/iast/analyzers/resources/fs-async-way-method.js @@ -2,10 +2,21 @@ const fs = require('fs') -module.exports = function (methodName, args, cb) { +function main (methodName, args, cb) { return new Promise((resolve, reject) => { fs[methodName](...args, (err, res) => { resolve(cb(res)) }) }) } + +main.doubleCallIgnoringCb = function (methodName, args) { + return new Promise((resolve) => { + fs[methodName](...args, () => {}) + fs[methodName](...args, () => { + resolve() + }) + }) +} + +module.exports = main diff --git a/packages/dd-trace/test/appsec/rasp/fs-plugin.spec.js b/packages/dd-trace/test/appsec/rasp/fs-plugin.spec.js index b87c88c20de..b79d05bfabc 100644 --- a/packages/dd-trace/test/appsec/rasp/fs-plugin.spec.js +++ b/packages/dd-trace/test/appsec/rasp/fs-plugin.spec.js @@ -27,6 +27,7 @@ describe('AppsecFsPlugin', () => { beforeEach(() => { configure = sinon.stub() class PluginClass { + addBind (channelName, handler) {} addSub (channelName, handler) {} configure (config) { @@ -93,20 +94,18 @@ describe('AppsecFsPlugin', () => { }) describe('_onFsOperationStart', () => { - it('should mark fs root', () => { + it('should return fs root', () => { const origStore = {} storage('legacy').enterWith(origStore) - appsecFsPlugin._onFsOperationStart() + let store = appsecFsPlugin._onFsOperationStart() - let store = storage('legacy').getStore() assert.property(store, 'fs') assert.propertyVal(store.fs, 'parentStore', origStore) assert.propertyVal(store.fs, 'root', true) - appsecFsPlugin._onFsOperationFinishOrRenderEnd() + store = appsecFsPlugin._onFsOperationFinishOrRenderEnd() - store = storage('legacy').getStore() assert.equal(store, origStore) assert.notProperty(store, 'fs') }) @@ -115,28 +114,30 @@ describe('AppsecFsPlugin', () => { const origStore = { orig: true } storage('legacy').enterWith(origStore) - appsecFsPlugin._onFsOperationStart() + const rootStore = appsecFsPlugin._onFsOperationStart() - const rootStore = storage('legacy').getStore() assert.property(rootStore, 'fs') assert.propertyVal(rootStore.fs, 'parentStore', origStore) assert.propertyVal(rootStore.fs, 'root', true) - appsecFsPlugin._onFsOperationStart() + storage('legacy').enterWith(rootStore) + + let store = appsecFsPlugin._onFsOperationStart() - let store = storage('legacy').getStore() assert.property(store, 'fs') assert.propertyVal(store.fs, 'parentStore', rootStore) assert.propertyVal(store.fs, 'root', false) assert.propertyVal(store, 'orig', true) - appsecFsPlugin._onFsOperationFinishOrRenderEnd() + storage('legacy').enterWith(store) + + store = appsecFsPlugin._onFsOperationFinishOrRenderEnd() - store = storage('legacy').getStore() assert.equal(store, rootStore) - appsecFsPlugin._onFsOperationFinishOrRenderEnd() - store = storage('legacy').getStore() + storage('legacy').enterWith(store) + + store = appsecFsPlugin._onFsOperationFinishOrRenderEnd() assert.equal(store, origStore) }) }) @@ -148,16 +149,16 @@ describe('AppsecFsPlugin', () => { const origStore = {} storage('legacy').enterWith(origStore) - appsecFsPlugin._onResponseRenderStart() + let store = appsecFsPlugin._onResponseRenderStart() - let store = storage('legacy').getStore() assert.property(store, 'fs') assert.propertyVal(store.fs, 'parentStore', origStore) assert.propertyVal(store.fs, 'opExcluded', true) - appsecFsPlugin._onFsOperationFinishOrRenderEnd() + storage('legacy').enterWith(store) + + store = appsecFsPlugin._onFsOperationFinishOrRenderEnd() - store = storage('legacy').getStore() assert.equal(store, origStore) assert.notProperty(store, 'fs') }) @@ -225,6 +226,12 @@ describe('AppsecFsPlugin', () => { it('should clean up store when finishing op', () => { let count = 4 + // TODO Remove this when node 18 is unsupported or dc-polyfill is fixed&updated + // hack to node 18 and early 20.x + // with dc-polyfill addBind is not enough to force a channel.hasSubscribers === true + const onStart = () => {} + opStartCh.subscribe(onStart) + const onFinish = () => { const store = storage('legacy').getStore() count-- @@ -244,6 +251,7 @@ describe('AppsecFsPlugin', () => { assert.strictEqual(count, 0) } finally { opFinishCh.unsubscribe(onFinish) + opStartCh.unsubscribe(onStart) } }) }) 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 210c3849ece..d0a5ad8d3dd 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 @@ -30,376 +30,416 @@ describe('RASP - lfi', () => { } withVersions('express', 'express', expressVersion => { - let app, server + withVersions('express', 'ejs', ejsVersion => { + let app, server - before(() => { - return agent.load(['http', 'express'], { client: false }) - }) + before(() => { + return agent.load(['http', 'express'], { client: false }) + }) - before((done) => { - const express = require(`../../../../../versions/express@${expressVersion}`).get() - const expressApp = express() + before((done) => { + const express = require(`../../../../../versions/express@${expressVersion}`).get() + // const ejs = require(`../../../../../versions/ejs@${ejsVersion}`).get() + const expressApp = express() - expressApp.get('/', (req, res) => { - app(req, res) - }) + expressApp.set('view engine', 'ejs') + expressApp.set('views', path.join(__dirname, 'resources')) - appsec.enable(new Config({ - appsec: { - enabled: true, - rules: path.join(__dirname, 'resources', 'lfi_rasp_rules.json'), - rasp: { enabled: true } - } - })) + expressApp.get('/', (req, res) => { + app(req, res) + }) - server = expressApp.listen(0, () => { - const port = server.address().port - axios = Axios.create({ - baseURL: `http://localhost:${port}` + appsec.enable(new Config({ + appsec: { + enabled: true, + rules: path.join(__dirname, 'resources', 'lfi_rasp_rules.json'), + rasp: { enabled: true } + } + })) + + server = expressApp.listen(0, () => { + const port = server.address().port + axios = Axios.create({ + baseURL: `http://localhost:${port}` + }) + done() }) - done() }) - }) - after(() => { - appsec.disable() - server.close() - return agent.close({ ritmReset: false }) - }) + after(() => { + appsec.disable() + server.close() + return agent.close({ ritmReset: false }) + }) - describe('lfi', () => { - function getApp (fn, args, options) { - return async (req, res) => { - try { - const result = await fn(args) - options.onfinish?.(result) - } catch (e) { - if (e.message === 'DatadogRaspAbortError') { - res.writeHead(418) + describe('lfi', () => { + function getApp (fn, args, options) { + return async (req, res) => { + try { + const result = await fn(args) + options.onfinish?.(result) + } catch (e) { + if (e.message === 'DatadogRaspAbortError') { + res.writeHead(418) + } } + res.end('end') } - res.end('end') } - } - function getAppSync (fn, args, options) { - return (req, res) => { - try { - const result = fn(args) - options.onfinish?.(result) - } catch (e) { - if (e.message === 'DatadogRaspAbortError') { - res.writeHead(418) + function getAppSync (fn, args, options) { + return (req, res) => { + try { + const result = fn(args) + options.onfinish?.(result) + } catch (e) { + if (e.message === 'DatadogRaspAbortError') { + res.writeHead(418) + } } + res.end('end') } - res.end('end') } - } - function runFsMethodTest (description, options, fn, ...args) { - const { vulnerableIndex = 0, ruleEvalCount } = options + function runFsMethodTest (description, options, fn, ...args) { + const { vulnerableIndex = 0, ruleEvalCount } = options - describe(description, () => { - const getAppFn = options.getAppFn ?? getApp + describe(description, () => { + const getAppFn = options.getAppFn ?? getApp - it('should block param from the request', () => { - app = getAppFn(fn, args, options) + it('should block param from the request', () => { + app = getAppFn(fn, args, options) - const file = args[vulnerableIndex] - return testBlockingRequest(`/?file=${file}`, undefined, ruleEvalCount) - .then(span => { - assert(span.meta['_dd.appsec.json'].includes(file)) - }) - }) + const file = args[vulnerableIndex] + return testBlockingRequest(`/?file=${file}`, undefined, ruleEvalCount) + .then(span => { + assert(span.meta['_dd.appsec.json'].includes(file)) + }) + }) - it('should not block if param not found in the request', async () => { - app = getAppFn(fn, args, options) + it('should not block if param not found in the request', async () => { + app = getAppFn(fn, args, options) - await axios.get('/?file=/test.file') + await axios.get('/?file=/test.file') - return checkRaspExecutedAndNotThreat(agent, false) + return checkRaspExecutedAndNotThreat(agent, false) + }) }) - }) - } - - function runFsMethodTestThreeWay (methodName, options = {}, ...args) { - let desc = `test ${methodName} ${options.desc ?? ''}` - const { vulnerableIndex = 0 } = options - if (vulnerableIndex !== 0) { - desc += ` with vulnerable index ${vulnerableIndex}` } - describe(desc, () => { - runFsMethodTest(`test fs.${methodName}Sync method`, { ...options, getAppFn: getAppSync }, (args) => { - return require('fs')[`${methodName}Sync`](...args) - }, ...args) - runFsMethodTest(`test fs.${methodName} method`, options, (args) => { - return new Promise((resolve, reject) => { - require('fs')[methodName](...args, (err, res) => { - if (err) reject(err) - else resolve(res) + function runFsMethodTestThreeWay (methodName, options = {}, ...args) { + let desc = `test ${methodName} ${options.desc ?? ''}` + const { vulnerableIndex = 0 } = options + if (vulnerableIndex !== 0) { + desc += ` with vulnerable index ${vulnerableIndex}` + } + describe(desc, () => { + runFsMethodTest(`test fs.${methodName}Sync method`, { ...options, getAppFn: getAppSync }, (args) => { + return require('fs')[`${methodName}Sync`](...args) + }, ...args) + + runFsMethodTest(`test fs.${methodName} method`, options, (args) => { + return new Promise((resolve, reject) => { + require('fs')[methodName](...args, (err, res) => { + if (err) reject(err) + else resolve(res) + }) }) - }) - }, ...args) - - runFsMethodTest(`test fs.promises.${methodName} method`, options, async (args) => { - return require('fs').promises[methodName](...args) - }, ...args) - }) - } - - function unlink (...args) { - args.forEach(arg => { - try { - fs.unlinkSync(arg) - } catch (e) { + }, ...args) - } - }) - } + runFsMethodTest(`test fs.promises.${methodName} method`, options, async (args) => { + return require('fs').promises[methodName](...args) + }, ...args) + }) + } - describe('test access', () => { - runFsMethodTestThreeWay('access', undefined, __filename) - runFsMethodTestThreeWay('access', { desc: 'Buffer' }, Buffer.from(__filename)) + function unlink (...args) { + args.forEach(arg => { + try { + fs.unlinkSync(arg) + } catch (e) { - // not supported by waf yet - // runFsMethodTestThreeWay('access', { desc: 'URL' }, new URL(`file://${__filename}`)) - }) + } + }) + } - describe('test appendFile', () => { - const filename = path.join(os.tmpdir(), 'test-appendfile') + describe('test access', () => { + runFsMethodTestThreeWay('access', undefined, __filename) + runFsMethodTestThreeWay('access', { desc: 'Buffer' }, Buffer.from(__filename)) - beforeEach(() => { - fs.writeFileSync(filename, '') + // not supported by waf yet + // runFsMethodTestThreeWay('access', { desc: 'URL' }, new URL(`file://${__filename}`)) }) - afterEach(() => { - fs.unlinkSync(filename) - }) + describe('test appendFile', () => { + const filename = path.join(os.tmpdir(), 'test-appendfile') - runFsMethodTestThreeWay('appendFile', undefined, filename, 'test-content') - }) + beforeEach(() => { + fs.writeFileSync(filename, '') + }) - describe('test chmod', () => { - const filename = path.join(os.tmpdir(), 'test-chmod') + afterEach(() => { + fs.unlinkSync(filename) + }) - beforeEach(() => { - fs.writeFileSync(filename, '') + runFsMethodTestThreeWay('appendFile', undefined, filename, 'test-content') }) - afterEach(() => { - fs.unlinkSync(filename) - }) - runFsMethodTestThreeWay('chmod', undefined, filename, '666') - }) + describe('test chmod', () => { + const filename = path.join(os.tmpdir(), 'test-chmod') - describe('test copyFile', () => { - const src = path.join(os.tmpdir(), 'test-copyFile-src') - const dest = path.join(os.tmpdir(), 'test-copyFile-dst') + beforeEach(() => { + fs.writeFileSync(filename, '') + }) - beforeEach(() => { - fs.writeFileSync(src, '') + afterEach(() => { + fs.unlinkSync(filename) + }) + runFsMethodTestThreeWay('chmod', undefined, filename, '666') }) - afterEach(() => unlink(src, dest)) + describe('test copyFile', () => { + const src = path.join(os.tmpdir(), 'test-copyFile-src') + const dest = path.join(os.tmpdir(), 'test-copyFile-dst') - runFsMethodTestThreeWay('copyFile', { vulnerableIndex: 0, ruleEvalCount: 2 }, src, dest) - runFsMethodTestThreeWay('copyFile', { vulnerableIndex: 1, ruleEvalCount: 2 }, src, dest) - }) + beforeEach(() => { + fs.writeFileSync(src, '') + }) - describe('test link', () => { - const src = path.join(os.tmpdir(), 'test-link-src') - const dest = path.join(os.tmpdir(), 'test-link-dst') + afterEach(() => unlink(src, dest)) - beforeEach(() => { - fs.writeFileSync(src, '') + runFsMethodTestThreeWay('copyFile', { vulnerableIndex: 0, ruleEvalCount: 2 }, src, dest) + runFsMethodTestThreeWay('copyFile', { vulnerableIndex: 1, ruleEvalCount: 2 }, src, dest) }) - afterEach(() => unlink(src, dest)) + describe('test link', () => { + const src = path.join(os.tmpdir(), 'test-link-src') + const dest = path.join(os.tmpdir(), 'test-link-dst') - runFsMethodTestThreeWay('copyFile', { vulnerableIndex: 0, ruleEvalCount: 2 }, src, dest) - runFsMethodTestThreeWay('copyFile', { vulnerableIndex: 1, ruleEvalCount: 2 }, src, dest) - }) + beforeEach(() => { + fs.writeFileSync(src, '') + }) - describe('test lstat', () => { - runFsMethodTestThreeWay('lstat', undefined, __filename) - }) + afterEach(() => unlink(src, dest)) - describe('test mkdir', () => { - const dirname = path.join(os.tmpdir(), 'test-mkdir') + runFsMethodTestThreeWay('copyFile', { vulnerableIndex: 0, ruleEvalCount: 2 }, src, dest) + runFsMethodTestThreeWay('copyFile', { vulnerableIndex: 1, ruleEvalCount: 2 }, src, dest) + }) - afterEach(() => { - try { - fs.rmdirSync(dirname) - } catch (e) { - // some ops are blocked - } + describe('test lstat', () => { + runFsMethodTestThreeWay('lstat', undefined, __filename) }) - runFsMethodTestThreeWay('mkdir', undefined, dirname) - }) - describe('test mkdtemp', () => { - const dirname = path.join(os.tmpdir(), 'test-mkdtemp') + describe('test mkdir', () => { + const dirname = path.join(os.tmpdir(), 'test-mkdir') - runFsMethodTestThreeWay('mkdtemp', { - onfinish: (todelete) => { + afterEach(() => { try { - fs.rmdirSync(todelete) + fs.rmdirSync(dirname) } catch (e) { // some ops are blocked } - } - }, dirname) - }) + }) + runFsMethodTestThreeWay('mkdir', undefined, dirname) + }) - describe('test open', () => { - runFsMethodTestThreeWay('open', { - onfinish: (fd) => { - if (fd && fd.close) { - fd.close() - } else { - fs.close(fd, () => {}) - } - } - }, __filename, 'r') - }) + describe('test mkdtemp', () => { + const dirname = path.join(os.tmpdir(), 'test-mkdtemp') - describe('test opendir', () => { - const dirname = path.join(os.tmpdir(), 'test-opendir') + runFsMethodTestThreeWay('mkdtemp', { + onfinish: (todelete) => { + try { + fs.rmdirSync(todelete) + } catch (e) { + // some ops are blocked + } + } + }, dirname) + }) - beforeEach(() => { - fs.mkdirSync(dirname) + describe('test open', () => { + runFsMethodTestThreeWay('open', { + onfinish: (fd) => { + if (fd && fd.close) { + fd.close() + } else { + fs.close(fd, () => { + }) + } + } + }, __filename, 'r') }) - afterEach(() => { - fs.rmdirSync(dirname) + describe('test opendir', () => { + const dirname = path.join(os.tmpdir(), 'test-opendir') + + beforeEach(() => { + fs.mkdirSync(dirname) + }) + + afterEach(() => { + fs.rmdirSync(dirname) + }) + runFsMethodTestThreeWay('opendir', { + onfinish: (dir) => { + dir.close() + } + }, dirname) }) - runFsMethodTestThreeWay('opendir', { - onfinish: (dir) => { - dir.close() - } - }, dirname) - }) - describe('test readdir', () => { - const dirname = path.join(os.tmpdir(), 'test-opendir') + describe('test readdir', () => { + const dirname = path.join(os.tmpdir(), 'test-opendir') + + beforeEach(() => { + fs.mkdirSync(dirname) + }) - beforeEach(() => { - fs.mkdirSync(dirname) + afterEach(() => { + fs.rmdirSync(dirname) + }) + runFsMethodTestThreeWay('readdir', undefined, dirname) }) - afterEach(() => { - fs.rmdirSync(dirname) + describe('test readFile', () => { + runFsMethodTestThreeWay('readFile', undefined, __filename) + + runFsMethodTest('an async operation without callback is executed before', + { getAppFn: getAppSync, ruleEvalCount: 2 }, (args) => { + const fs = require('fs') + fs.readFile(path.join(__dirname, 'utils.js'), () => { + }) // safe and ignored operation + return fs.readFileSync(...args) + }, __filename) }) - runFsMethodTestThreeWay('readdir', undefined, dirname) - }) - describe('test readFile', () => { - runFsMethodTestThreeWay('readFile', undefined, __filename) - }) + describe('test readlink', () => { + const src = path.join(os.tmpdir(), 'test-readlink-src') + const dest = path.join(os.tmpdir(), 'test-readlink-dst') - describe('test readlink', () => { - const src = path.join(os.tmpdir(), 'test-readlink-src') - const dest = path.join(os.tmpdir(), 'test-readlink-dst') + beforeEach(() => { + fs.writeFileSync(src, '') + fs.linkSync(src, dest) + }) - beforeEach(() => { - fs.writeFileSync(src, '') - fs.linkSync(src, dest) + afterEach(() => unlink(src, dest)) + + runFsMethodTestThreeWay('readlink', undefined, dest) }) - afterEach(() => unlink(src, dest)) + describe('test realpath', () => { + runFsMethodTestThreeWay('realpath', undefined, __filename) - runFsMethodTestThreeWay('readlink', undefined, dest) - }) + runFsMethodTest('test fs.realpath.native method', {}, (args) => { + return new Promise((resolve, reject) => { + require('fs').realpath.native(...args, (err, result) => { + if (err) reject(err) + else resolve(result) + }) + }) + }, __filename) + }) - describe('test realpath', () => { - runFsMethodTestThreeWay('realpath', undefined, __filename) + describe('test rename', () => { + const src = path.join(os.tmpdir(), 'test-rename-src') + const dest = path.join(os.tmpdir(), 'test-rename-dst') - runFsMethodTest('test fs.realpath.native method', {}, (args) => { - return new Promise((resolve, reject) => { - require('fs').realpath.native(...args, (err, result) => { - if (err) reject(err) - else resolve(result) - }) + beforeEach(() => { + fs.writeFileSync(src, '') }) - }, __filename) - }) - describe('test rename', () => { - const src = path.join(os.tmpdir(), 'test-rename-src') - const dest = path.join(os.tmpdir(), 'test-rename-dst') + afterEach(() => unlink(dest)) - beforeEach(() => { - fs.writeFileSync(src, '') + runFsMethodTestThreeWay('rename', { vulnerableIndex: 0, ruleEvalCount: 2 }, src, dest) + runFsMethodTestThreeWay('rename', { vulnerableIndex: 1, ruleEvalCount: 2 }, src, dest) }) - afterEach(() => unlink(dest)) + describe('test rmdir', () => { + const dirname = path.join(os.tmpdir(), 'test-rmdir') - runFsMethodTestThreeWay('rename', { vulnerableIndex: 0, ruleEvalCount: 2 }, src, dest) - runFsMethodTestThreeWay('rename', { vulnerableIndex: 1, ruleEvalCount: 2 }, src, dest) - }) + beforeEach(() => { + fs.mkdirSync(dirname) + }) - describe('test rmdir', () => { - const dirname = path.join(os.tmpdir(), 'test-rmdir') + afterEach(() => { + try { + fs.rmdirSync(dirname) + } catch (e) { + } + }) - beforeEach(() => { - fs.mkdirSync(dirname) + runFsMethodTestThreeWay('rmdir', undefined, dirname) }) - afterEach(() => { - try { fs.rmdirSync(dirname) } catch (e) {} + describe('test stat', () => { + runFsMethodTestThreeWay('stat', undefined, __filename) }) - runFsMethodTestThreeWay('rmdir', undefined, dirname) - }) + describe('test symlink', () => { + const src = path.join(os.tmpdir(), 'test-symlink-src') + const dest = path.join(os.tmpdir(), 'test-symlink-dst') - describe('test stat', () => { - runFsMethodTestThreeWay('stat', undefined, __filename) - }) + beforeEach(() => { + fs.writeFileSync(src, '') + }) - describe('test symlink', () => { - const src = path.join(os.tmpdir(), 'test-symlink-src') - const dest = path.join(os.tmpdir(), 'test-symlink-dst') + afterEach(() => { + unlink(src, dest) + }) - beforeEach(() => { - fs.writeFileSync(src, '') + runFsMethodTestThreeWay('symlink', { vulnerableIndex: 0, ruleEvalCount: 2 }, src, dest) + runFsMethodTestThreeWay('symlink', { vulnerableIndex: 1, ruleEvalCount: 2 }, src, dest) }) - afterEach(() => { - unlink(src, dest) - }) + describe('test truncate', () => { + const src = path.join(os.tmpdir(), 'test-truncate-src') - runFsMethodTestThreeWay('symlink', { vulnerableIndex: 0, ruleEvalCount: 2 }, src, dest) - runFsMethodTestThreeWay('symlink', { vulnerableIndex: 1, ruleEvalCount: 2 }, src, dest) - }) + beforeEach(() => { + fs.writeFileSync(src, 'aaaaaa') + }) - describe('test truncate', () => { - const src = path.join(os.tmpdir(), 'test-truncate-src') + afterEach(() => unlink(src)) - beforeEach(() => { - fs.writeFileSync(src, 'aaaaaa') + runFsMethodTestThreeWay('truncate', undefined, src) }) - afterEach(() => unlink(src)) + describe('test unlink', () => { + const src = path.join(os.tmpdir(), 'test-unlink-src') - runFsMethodTestThreeWay('truncate', undefined, src) - }) + beforeEach(() => { + fs.writeFileSync(src, '') + }) + runFsMethodTestThreeWay('unlink', undefined, src) + }) - describe('test unlink', () => { - const src = path.join(os.tmpdir(), 'test-unlink-src') + describe('test writeFile', () => { + const src = path.join(os.tmpdir(), 'test-writeFile-src') - beforeEach(() => { - fs.writeFileSync(src, '') - }) - runFsMethodTestThreeWay('unlink', undefined, src) - }) + afterEach(() => unlink(src)) - describe('test writeFile', () => { - const src = path.join(os.tmpdir(), 'test-writeFile-src') + runFsMethodTestThreeWay('writeFile', undefined, src, 'content') + }) - afterEach(() => unlink(src)) + describe('test with express render', () => { + function getAppFn (fn, args, options) { + return (req, res) => { + try { + const result = fn(args) + options.onfinish?.(result) + } catch (e) { + if (e.message === 'DatadogRaspAbortError') { + res.status(418) + } + } + res.render('template') + } + } - runFsMethodTestThreeWay('writeFile', undefined, src, 'content') + runFsMethodTest('rule is eval only once and rendering file accesses are ignored', + { getAppFn, ruleEvalCount: 1 }, (args) => { + const fs = require('fs') + return fs.readFileSync(...args) + }, __filename) + }) }) }) }) diff --git a/packages/dd-trace/test/appsec/rasp/resources/template.ejs b/packages/dd-trace/test/appsec/rasp/resources/template.ejs new file mode 100644 index 00000000000..a806d0e8cd7 --- /dev/null +++ b/packages/dd-trace/test/appsec/rasp/resources/template.ejs @@ -0,0 +1,5 @@ + +
+