Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 50 additions & 78 deletions integration-tests/appsec/graphql.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const assert = require('node:assert/strict')
const path = require('path')
const { inspect } = require('node:util')
const axios = require('axios')

const {
FakeAgent,
Expand Down Expand Up @@ -38,6 +37,19 @@ describe('graphql', () => {
await agent.stop()
})

/**
* @param {object|object[]} body
*/
async function request (body) {
const response = await fetch(`${proc.url}/graphql`, {
method: 'post',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(body),
})
await response.arrayBuffer()
return response
}

it('should not report any attack', async () => {
const agentPromise = agent.assertMessageReceived(({ headers, payload }) => {
assert.strictEqual(headers.host, `127.0.0.1:${agent.port}`)
Expand All @@ -54,22 +66,15 @@ describe('graphql', () => {
assert.ok(!('_dd.appsec.json' in payload[1][0].meta))
})

const requestPromise = axios({
url: `${proc.url}/graphql`,
method: 'post',
headers: {
'Content-type': 'application/json',
},
data: {
query: 'query getSingleImage($imageId: Int!) { image(imageId: $imageId) { title owner category url }}',
variables: {
imageId: 1,
},
operationName: 'getSingleImage',
const requestPromise = request({
query: 'query getSingleImage($imageId: Int!) { image(imageId: $imageId) { title owner category url }}',
variables: {
imageId: 1,
},
operationName: 'getSingleImage',
})

await Promise.all([agentPromise, requestPromise])
const [, response] = await Promise.all([agentPromise, requestPromise])
assert.strictEqual(response.status, 200)
})

it('should report an attack', async () => {
Expand Down Expand Up @@ -123,22 +128,15 @@ describe('graphql', () => {
assert.deepStrictEqual(JSON.parse(payload[1][0].meta['_dd.appsec.json']), result)
})

const requestPromise = axios({
url: `${proc.url}/graphql`,
method: 'post',
headers: {
'Content-type': 'application/json',
},
data: {
query: 'query getImagesByCategory($category: String) { images(category: $category) { title owner url }}',
variables: {
category: 'testattack',
},
operationName: 'getImagesByCategory',
const requestPromise = request({
query: 'query getImagesByCategory($category: String) { images(category: $category) { title owner url }}',
variables: {
category: 'testattack',
},
operationName: 'getImagesByCategory',
})

await Promise.all([agentPromise, requestPromise])
const [, response] = await Promise.all([agentPromise, requestPromise])
assert.strictEqual(response.status, 200)
})

it('should block an attack', async () => {
Expand All @@ -153,28 +151,15 @@ describe('graphql', () => {
assert.strictEqual(payload[1][0].meta['appsec.event'], 'true')
})

const requestPromise = assert.rejects(
axios({
url: `${proc.url}/graphql`,
method: 'post',
headers: {
'Content-type': 'application/json',
},
data: {
query: 'query getImagesByCategory($category: String) { images(category: $category) { title owner url }}',
variables: {
category: 'blockattack',
},
operationName: 'getImagesByCategory',
},
}),
(err) => {
assert.strictEqual(err.response.status, 403)
return true
}
)

await Promise.all([agentPromise, requestPromise])
const requestPromise = request({
query: 'query getImagesByCategory($category: String) { images(category: $category) { title owner url }}',
variables: {
category: 'blockattack',
},
operationName: 'getImagesByCategory',
})
const [, response] = await Promise.all([agentPromise, requestPromise])
assert.strictEqual(response.status, 403)
})

it('should block an attack in a batched request', async () => {
Expand All @@ -189,32 +174,19 @@ describe('graphql', () => {
assert.strictEqual(payload[1][0].meta['appsec.event'], 'true')
})

const requestPromise = assert.rejects(
axios({
url: `${proc.url}/graphql`,
method: 'post',
headers: {
'Content-type': 'application/json',
},
data: [
{
query: 'query getSingleImage($imageId: Int!) { image(imageId: $imageId) { title }}',
variables: { imageId: 1 },
operationName: 'getSingleImage',
},
{
query: 'query getImagesByCategory($category: String) { images(category: $category) { title }}',
variables: { category: 'blockattack' },
operationName: 'getImagesByCategory',
},
],
}),
(err) => {
assert.strictEqual(err.response.status, 403)
return true
}
)

await Promise.all([agentPromise, requestPromise])
const requestPromise = request([
{
query: 'query getSingleImage($imageId: Int!) { image(imageId: $imageId) { title }}',
variables: { imageId: 1 },
operationName: 'getSingleImage',
},
{
query: 'query getImagesByCategory($category: String) { images(category: $category) { title }}',
variables: { category: 'blockattack' },
operationName: 'getImagesByCategory',
},
])
const [, response] = await Promise.all([agentPromise, requestPromise])
assert.strictEqual(response.status, 403)
})
})
50 changes: 30 additions & 20 deletions integration-tests/appsec/headers-collection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
const assert = require('node:assert/strict')
const path = require('path')
const { inspect } = require('node:util')
const Axios = require('axios')

const {
sandboxCwd,
useSandbox,
Expand All @@ -13,8 +11,20 @@ const {
stopProc,
} = require('../helpers')

/**
* @param {string} baseUrl
* @param {string} url
* @param {object} [options]
*/
async function request (baseUrl, url, options) {
const response = await fetch(new URL(url, baseUrl), options)
await response.arrayBuffer()
assert.strictEqual(response.ok, true)
return response
}

describe('AppSec headers collection - Express', () => {
let axios, cwd, appFile, agent, proc
let cwd, appFile, agent, proc

useSandbox(['express'])

Expand All @@ -40,7 +50,6 @@ describe('AppSec headers collection - Express', () => {
}

proc = await spawnProc(appFile, { cwd, env, execArgv: [] })
axios = Axios.create({ baseURL: proc.url })
})

afterEach(async () => {
Expand Down Expand Up @@ -81,10 +90,10 @@ describe('AppSec headers collection - Express', () => {
startServer('appsec/data-collection/index.js')

it('should collect event headers when a WAF event is triggered', async () => {
const expectedRequestHeaders = ['user-agent', 'accept', 'host', 'accept-encoding']
const expectedRequestHeaders = ['user-agent', 'accept', 'host', 'accept-encoding', 'accept-language']
const expectedResponseHeaders = ['content-type', 'content-language']

await axios.get('/', { headers: { 'User-Agent': 'Arachni/v1' } })
await request(proc.url, '/', { headers: { 'User-Agent': 'Arachni/v1' } })
await assertHeadersReported(expectedRequestHeaders, expectedResponseHeaders)
})
})
Expand All @@ -93,7 +102,9 @@ describe('AppSec headers collection - Express', () => {
startServer('appsec/data-collection/index.js', { extendedDataCollection: true })

it('should collect extended headers when a WAF event is triggered', async () => {
const expectedRequestHeaders = ['user-agent', 'accept', 'host', 'accept-encoding', 'connection']
const expectedRequestHeaders = [
'user-agent', 'accept', 'host', 'accept-encoding', 'accept-language', 'sec-fetch-mode', 'connection',
]

// DD_APPSEC_MAX_COLLECTED_HEADERS is set to 25, so it is expected to collect
// 22 x-datadog-res-XX headers + x-powered-by, content-type and content-language, for a total of 25.
Expand All @@ -104,7 +115,7 @@ describe('AppSec headers collection - Express', () => {
'content-language',
]

await axios.get('/', { headers: { 'User-Agent': 'Arachni/v1' } })
await request(proc.url, '/', { headers: { 'User-Agent': 'Arachni/v1' } })
await assertHeadersReported(expectedRequestHeaders, expectedResponseHeaders)
})
})
Expand All @@ -113,26 +124,26 @@ describe('AppSec headers collection - Express', () => {
startServer('appsec/response-headers/express.js')

it('should always collect content-type and content-length response headers when AppSec is enabled', async () => {
const response = await axios.get('/', { headers: { 'User-Agent': 'Mozilla/5.0' } })
const response = await request(proc.url, '/', { headers: { 'User-Agent': 'Mozilla/5.0' } })

assert.equal(response.status, 200)
assert.ok(response.headers['content-type'])
assert.ok(response.headers['content-length'])
assert.ok(response.headers.get('content-type'))
assert.ok(response.headers.get('content-length'))

await agent.assertMessageReceived(({ payload }) => {
const span = payload[0]?.find(s => s.type === 'web')
if (!span) throw new Error('web-type span not yet received')

assert.equal(span.meta['http.response.headers.content-type'], response.headers['content-type'])
assert.equal(span.meta['http.response.headers.content-length'], response.headers['content-length'])
assert.equal(span.meta['http.response.headers.content-type'], response.headers.get('content-type'))
assert.equal(span.meta['http.response.headers.content-length'], response.headers.get('content-length'))
assert.equal(span.meta['appsec.event'], undefined)
})
})
})
})

describe('AppSec headers collection - Fastify', () => {
let axios, cwd, appFile, agent, proc
let cwd, appFile, agent, proc

useSandbox(['fastify'])

Expand All @@ -151,7 +162,6 @@ describe('AppSec headers collection - Fastify', () => {
},
execArgv: [],
})
axios = Axios.create({ baseURL: proc.url })
})

afterEach(async () => {
Expand All @@ -161,18 +171,18 @@ describe('AppSec headers collection - Fastify', () => {

describe('No security event', () => {
it('should always emit content-type and content-length response headers when AppSec is enabled', async () => {
const response = await axios.get('/', { headers: { 'User-Agent': 'Mozilla/5.0' } })
const response = await request(proc.url, '/', { headers: { 'User-Agent': 'Mozilla/5.0' } })

assert.equal(response.status, 200)
assert.ok(response.headers['content-type'])
assert.ok(response.headers['content-length'])
assert.ok(response.headers.get('content-type'))
assert.ok(response.headers.get('content-length'))

await agent.assertMessageReceived(({ payload }) => {
const span = payload[0]?.find(s => s.type === 'web')
if (!span) throw new Error('web-type span not yet received')

assert.equal(span.meta['http.response.headers.content-type'], response.headers['content-type'])
assert.equal(span.meta['http.response.headers.content-length'], response.headers['content-length'])
assert.equal(span.meta['http.response.headers.content-type'], response.headers.get('content-type'))
assert.equal(span.meta['http.response.headers.content-length'], response.headers.get('content-length'))
assert.equal(span.meta['appsec.event'], undefined)
})
})
Expand Down
Loading
Loading