Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/actions/install/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ runs:
using: composite
steps:
- id: yarn-cache
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
key: yarn-cache-${{ github.workflow }}-${{ github.job }}-${{ hashFiles('yarn.lock') }}-v2
path: node_modules.tar
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/install/branch-diff/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ inputs:
runs:
using: composite
steps:
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.npm
key: ${{ github.workflow }}-branch-diff-3.1.1
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/node/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ runs:
id: cache-key
shell: bash
run: echo "block=$(( $(date -u +%s) / 1200 ))" >> "$GITHUB_OUTPUT"
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: node-version-cache
with:
path: /tmp/.node-resolved-version-${{ steps.node-version.outputs.version }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
languages: ${{ matrix.language }}
config-file: .github/codeql_config.yml
Expand All @@ -48,7 +48,7 @@ jobs:
# queries: ./path/to/local/query, your-org/your-repo/queries@main

- name: Autobuild
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
6 changes: 3 additions & 3 deletions .github/workflows/test-optimization.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
echo "version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
echo "Playwright version: $PLAYWRIGHT_VERSION"
- name: Cache Playwright browsers
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:
echo "dd-trace major version: $MAJOR"
- name: Cache Playwright browsers
if: matrix.playwright-version == 'oldest'
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: /github/home/.cache/ms-playwright
key: playwright-browsers-oldest-dd${{ steps.dd-version.outputs.major }}
Expand Down Expand Up @@ -252,7 +252,7 @@ jobs:
# as that changes frequently and would have a low cache hit rate
- name: Cache Cypress binary
if: matrix.cypress-version != 'latest'
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.cache/Cypress
key: cypress-binary-${{ matrix.cypress-version }}
Expand Down
5 changes: 4 additions & 1 deletion .gitlab/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,12 @@ benchmark-serverless-trigger:
- if: $CI_COMMIT_BRANCH == 'master'
interruptible: false

# dont run on merges to release branches (vN.x where N is any integer)
# don't run on merges to release branches ("vN.x" where N is any integer)
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^v\d+\.x$/'
when: never
# don't run on pushes to release branches
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH =~ /^v\d+\.x$/'
when: never
- interruptible: true
trigger:
project: DataDog/serverless-tools
Expand Down
1 change: 1 addition & 0 deletions ext/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const tags = {
HTTP_RESPONSE_HEADERS: 'http.response.headers',
HTTP_USERAGENT: 'http.useragent',
HTTP_CLIENT_IP: 'http.client_ip',
NETWORK_CLIENT_IP: 'network.client.ip',

// Messaging

Expand Down
38 changes: 38 additions & 0 deletions integration-tests/aiguard/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('AIGuard SDK integration tests', () => {
DD_SERVICE: 'ai_guard_integration_test',
DD_ENV: 'test',
DD_TRACE_ENABLED: 'true',
DD_TRACE_CLIENT_IP_ENABLED: 'false',
DD_TRACE_AGENT_PORT: String(agent.port),
DD_AI_GUARD_ENABLED: 'true',
DD_AI_GUARD_BLOCK: 'true',
Expand Down Expand Up @@ -69,6 +70,43 @@ describe('AIGuard SDK integration tests', () => {
})
})

it('adds client ip tags to the request root span when AI Guard runs', async () => {
const response = await executeRequest(`${url}/allow`, 'GET', {
'x-forwarded-for': '203.0.113.10, 10.0.0.1',
})

assert.strictEqual(response.status, 200)

await agent.assertMessageReceived(({ payload }) => {
const requestSpan = payload[0].find(span => span.name === 'express.request')
const guardSpan = payload[0].find(span => span.name === 'ai_guard')

assert.notStrictEqual(requestSpan, undefined)
assert.notStrictEqual(guardSpan, undefined)
assert.strictEqual(requestSpan.meta['http.client_ip'], '203.0.113.10')
assert.ok(requestSpan.meta['network.client.ip'])
})
})

it('does not add client ip tags when no AI Guard span is created', async () => {
const response = await executeRequest(`${url}/no-aiguard`, 'GET', {
'x-forwarded-for': '203.0.113.10, 10.0.0.1',
})

assert.strictEqual(response.status, 200)
assert.deepStrictEqual(response.body, { ok: true })

await agent.assertMessageReceived(({ payload }) => {
const requestSpan = payload[0].find(span => span.name === 'express.request')
const guardSpan = payload[0].find(span => span.name === 'ai_guard')

assert.notStrictEqual(requestSpan, undefined)
assert.strictEqual(guardSpan, undefined)
assert.strictEqual(requestSpan.meta['http.client_ip'], undefined)
assert.strictEqual(requestSpan.meta['network.client.ip'], undefined)
})
})

const directApiSuite = [
{ endpoint: '/allow', action: 'ALLOW', reason: 'The prompt looks harmless' },
{ endpoint: '/deny', action: 'DENY', reason: 'I am feeling suspicious today' },
Expand Down
4 changes: 4 additions & 0 deletions integration-tests/aiguard/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const express = require('express')

const app = express()

app.get('/no-aiguard', (req, res) => {
res.status(200).json({ ok: true })
})

app.get('/allow', async (req, res) => {
const evaluation = await tracer.aiguard.evaluate([
{ role: 'system', content: 'You are a beautiful AI' },
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dd-trace",
"version": "5.98.0",
"version": "5.98.1",
"description": "Datadog APM tracing client for JavaScript",
"main": "index.js",
"typings": "index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
'use strict'

/**
* This file is meant to be only thin wrappers over core
* parsing/traversing/generating functionality with the goal to eventually move
* them out of the project. No other code should be added to this file such as
* helpers etc, and the API should be kept exactly as an external API would be
* expected to be.
*/

const log = require('../../../../dd-trace/src/log')

// eslint-disable-next-line camelcase, no-undef
Expand All @@ -24,7 +32,6 @@ const compiler = {
} catch (e) {
log.error(e)

// Fallback for when OXC is not available.
const meriyah = require('../../../../../vendor/dist/meriyah')

compiler.parse = (sourceText, { range, sourceType } = {}) => {
Expand Down
5 changes: 2 additions & 3 deletions packages/datadog-plugin-http/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,8 @@ class HttpServerPlugin extends ServerPlugin {
context.parentStore = store
}

// Only AppSec needs the request scope to be active for any async work that
// may be scheduled after the synchronous `request` event returns (e.g.
// Fastify).
// AppSec, IAST, and AI Guard need req/res on the store so downstream
// subscribers can access them from the async context.
if (incomingHttpRequestStart.hasSubscribers) {
store = { ...store, req, res }
}
Expand Down
8 changes: 8 additions & 0 deletions packages/dd-trace/src/aiguard/channels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict'

const dc = require('dc-polyfill')

module.exports = {
aiguardChannel: dc.channel('dd-trace:ai:aiguard'),
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
}
10 changes: 7 additions & 3 deletions packages/dd-trace/src/aiguard/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
'use strict'

const { channel } = require('dc-polyfill')
const log = require('../log')
const { incomingHttpRequestStart, aiguardChannel } = require('./channels')
const AIGuard = require('./sdk')

const aiguardChannel = channel('dd-trace:ai:aiguard')

let isEnabled = false
let aiguard
let block

function onIncomingHttpRequestStart () {
// No-op: subscribing ensures the HTTP plugin spreads req onto the store
}

function enable (tracer, config) {
if (isEnabled) return

try {
aiguard = new AIGuard(tracer, config)
block = config.experimental?.aiguard?.block !== false

incomingHttpRequestStart.subscribe(onIncomingHttpRequestStart)
aiguardChannel.subscribe(onEvaluate)

isEnabled = true
Expand All @@ -29,6 +32,7 @@ function enable (tracer, config) {
function disable () {
if (!isEnabled) return

incomingHttpRequestStart.unsubscribe(onIncomingHttpRequestStart)
aiguardChannel.unsubscribe(onEvaluate)

aiguard = undefined
Expand Down
42 changes: 42 additions & 0 deletions packages/dd-trace/src/aiguard/sdk.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'use strict'

const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
const { HTTP_CLIENT_IP, NETWORK_CLIENT_IP } = require('../../../../ext/tags')
const { storage } = require('../../../datadog-core')
const log = require('../log')
const { extractIp } = require('../plugins/util/ip_extractor')
const telemetryMetrics = require('../telemetry/metrics')
const tracerVersion = require('../../../../package.json').version
const { keepTrace } = require('../priority_sampler')
Expand Down Expand Up @@ -57,6 +60,7 @@ class AIGuard extends NoopAIGuard {
#maxMessagesLength
#maxContentSize
#meta
#config

/**
* @param {import('../tracer')} tracer - Tracer instance
Expand Down Expand Up @@ -84,6 +88,7 @@ class AIGuard extends NoopAIGuard {
this.#maxMessagesLength = config.experimental.aiguard.maxMessagesLength
this.#maxContentSize = config.experimental.aiguard.maxContentSize
this.#meta = { service: config.service, env: config.env }
this.#config = config
this.#initialized = true
}

Expand Down Expand Up @@ -139,6 +144,42 @@ class AIGuard extends NoopAIGuard {
return null
}

#setRootSpanClientIpTags (rootSpan) {
if (!rootSpan) return

const currentTags = rootSpan.context()._tags
const needsHttpClientIp = !Object.hasOwn(currentTags, HTTP_CLIENT_IP)
const needsNetworkClientIp = !Object.hasOwn(currentTags, NETWORK_CLIENT_IP)

if (!needsHttpClientIp && !needsNetworkClientIp) return

const req = storage('legacy').getStore()?.req

if (!req) return

const newTags = {}

if (needsHttpClientIp) {
const clientIp = extractIp(this.#config, req)

if (clientIp) {
newTags[HTTP_CLIENT_IP] = clientIp
}
}

if (needsNetworkClientIp) {
const networkClientIp = req.socket?.remoteAddress

if (networkClientIp) {
newTags[NETWORK_CLIENT_IP] = networkClientIp
}
}

if (Object.keys(newTags).length > 0) {
rootSpan.addTags(newTags)
}
}

evaluate (messages, opts) {
if (!this.#initialized) {
return super.evaluate(messages, opts)
Expand All @@ -162,6 +203,7 @@ class AIGuard extends NoopAIGuard {
}
const rootSpan = span.context()?._trace?.started?.[0]
if (rootSpan) {
this.#setRootSpanClientIpTags(rootSpan)
// keepTrace must be called before executeRequest so the sampling decision
// is propagated correctly to outgoing HTTP client calls.
keepTrace(rootSpan, AI_GUARD)
Expand Down
3 changes: 2 additions & 1 deletion packages/dd-trace/src/appsec/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const zlib = require('zlib')
const dc = require('dc-polyfill')

const { NETWORK_CLIENT_IP } = require('../../../../ext/tags')
const { storage } = require('../../../datadog-core')
const web = require('../plugins/util/web')
const { ipHeaderList } = require('../plugins/util/ip_extractor')
Expand Down Expand Up @@ -363,7 +364,7 @@ function reportAttack ({ events: attackData, actions }, req) {
: '{"triggers":' + attackDataStr + '}'

if (req.socket) {
newTags['network.client.ip'] = req.socket.remoteAddress
newTags[NETWORK_CLIENT_IP] = req.socket.remoteAddress
}

rootSpan.addTags(newTags)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const agent = require('../../../plugins/agent')
const { withVersions } = require('../../../setup/mocha')
const { prepareTestServerForIastInExpress } = require('../utils')

describe('nosql injection detection with mquery', () => {
// TODO(APPSEC-62431): re-enable once duplicate NOSQL_MONGODB_INJECTION
// detection (N+1 !== N) is fixed
describe.skip('nosql injection detection with mquery', () => {
// https://github.com/fiznool/express-mongo-sanitize/issues/200
withVersions('mquery', 'express', '>4.18.0 <5.0.0', expressVersion => {
withVersions('mquery', 'mongodb', mongodbVersion => {
Expand Down
Loading