dd-trace 6.8.0 → 6.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ci/diagnose.js +27 -25
- package/ci/init.js +6 -6
- package/ci/runbook.md +114 -20
- package/ci/test-optimization-validation/approval-artifacts.js +36 -3
- package/ci/test-optimization-validation/approval.js +78 -22
- package/ci/test-optimization-validation/blocker-category.js +24 -0
- package/ci/test-optimization-validation/ci-discovery.js +23 -17
- package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
- package/ci/test-optimization-validation/ci-remediation.js +1 -1
- package/ci/test-optimization-validation/cli.js +280 -52
- package/ci/test-optimization-validation/command-blocker.js +159 -20
- package/ci/test-optimization-validation/command-output-policy.js +9 -3
- package/ci/test-optimization-validation/command-runner.js +9 -7
- package/ci/test-optimization-validation/environment.js +4 -2
- package/ci/test-optimization-validation/executable.js +15 -13
- package/ci/test-optimization-validation/execution-lock.js +70 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
- package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
- package/ci/test-optimization-validation/generated-files.js +38 -28
- package/ci/test-optimization-validation/generated-test-contract.js +8 -4
- package/ci/test-optimization-validation/generated-verifier.js +24 -17
- package/ci/test-optimization-validation/literal-glob.js +52 -0
- package/ci/test-optimization-validation/manifest-loader.js +3 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
- package/ci/test-optimization-validation/manifest-schema.js +80 -17
- package/ci/test-optimization-validation/offline-fixtures.js +13 -4
- package/ci/test-optimization-validation/package-check.js +94 -0
- package/ci/test-optimization-validation/plan-writer.js +183 -10
- package/ci/test-optimization-validation/preflight-runner.js +110 -31
- package/ci/test-optimization-validation/project-build-artifact.js +31 -0
- package/ci/test-optimization-validation/redaction.js +18 -27
- package/ci/test-optimization-validation/report-writer.js +182 -48
- package/ci/test-optimization-validation/result-semantics.js +20 -3
- package/ci/test-optimization-validation/runner-command.js +40 -20
- package/ci/test-optimization-validation/runner-contract.js +79 -43
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
- package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
- package/ci/test-optimization-validation/static-diagnosis.js +10 -3
- package/ci/test-optimization-validation/test-output.js +2 -1
- package/ci/test-optimization-validation/validation-blocker.js +21 -0
- package/ci/vitest-no-worker-init-setup.mjs +30 -34
- package/index.d.ts +98 -1
- package/init.js +1 -17
- package/initialize.mjs +11 -0
- package/loader-hook.mjs +6 -4
- package/package.json +5 -3
- package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
- package/packages/datadog-esbuild/src/utils.js +16 -1
- package/packages/datadog-instrumentations/src/ai.js +61 -34
- package/packages/datadog-instrumentations/src/bluebird.js +6 -6
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
- package/packages/datadog-instrumentations/src/cucumber.js +124 -47
- package/packages/datadog-instrumentations/src/dns.js +21 -2
- package/packages/datadog-instrumentations/src/electron.js +1 -0
- package/packages/datadog-instrumentations/src/fastify.js +3 -1
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +33 -9
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +118 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +979 -233
- package/packages/datadog-instrumentations/src/mocha/main.js +107 -9
- package/packages/datadog-instrumentations/src/mocha/utils.js +64 -42
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +155 -23
- package/packages/datadog-instrumentations/src/openai.js +14 -0
- package/packages/datadog-instrumentations/src/pg.js +180 -6
- package/packages/datadog-instrumentations/src/playwright.js +173 -79
- package/packages/datadog-instrumentations/src/promise-js.js +4 -4
- package/packages/datadog-instrumentations/src/promise.js +3 -3
- package/packages/datadog-instrumentations/src/router.js +195 -19
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -10
- package/packages/datadog-instrumentations/src/vitest-main.js +360 -56
- package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
- package/packages/datadog-instrumentations/src/webdriverio.js +194 -18
- package/packages/datadog-instrumentations/src/when.js +3 -3
- package/packages/datadog-instrumentations/src/ws.js +5 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
- package/packages/datadog-plugin-cucumber/src/index.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
- package/packages/datadog-plugin-cypress/src/support.js +7 -4
- package/packages/datadog-plugin-electron/src/net.js +10 -4
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +1 -18
- package/packages/datadog-plugin-jest/src/util.js +2 -0
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
- package/packages/datadog-plugin-mocha/src/index.js +318 -1
- package/packages/datadog-plugin-next/src/index.js +57 -9
- package/packages/datadog-plugin-openai/src/tracing.js +4 -0
- package/packages/datadog-plugin-pg/src/index.js +65 -1
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +95 -7
- package/packages/datadog-shimmer/src/shimmer.js +6 -2
- package/packages/dd-trace/src/aiguard/sdk.js +3 -0
- package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
- package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
- package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
- package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
- package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +48 -476
- package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
- package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
- package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/helper.js +2 -0
- package/packages/dd-trace/src/config/index.js +16 -5
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +8 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
- package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
- package/packages/dd-trace/src/guardrails/log.js +1 -10
- package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
- package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
- package/packages/dd-trace/src/llmobs/noop.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +149 -68
- package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
- package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
- package/packages/dd-trace/src/log/channels.js +1 -9
- package/packages/dd-trace/src/log/levels.js +12 -0
- package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span.js +4 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +6 -0
- package/packages/dd-trace/src/plugins/util/git.js +6 -4
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
- package/packages/dd-trace/src/plugins/util/test.js +319 -75
- package/packages/dd-trace/src/plugins/util/url.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +23 -1
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/ritm.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
- package/packages/dd-trace/src/sampling_rule.js +4 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
- package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/version.js +10 -8
- /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
TEST_STATUS,
|
|
8
8
|
TEST_TYPE,
|
|
9
9
|
VITEST_POOL,
|
|
10
|
+
addIntelligentTestRunnerSpanTags,
|
|
10
11
|
finishAllTraceSpans,
|
|
11
12
|
getTestSuitePath,
|
|
12
13
|
getTestSuiteCommonTags,
|
|
@@ -15,7 +16,6 @@ const {
|
|
|
15
16
|
getIsFaultyEarlyFlakeDetection,
|
|
16
17
|
TEST_SOURCE_FILE,
|
|
17
18
|
TEST_IS_RETRY,
|
|
18
|
-
TEST_CODE_COVERAGE_LINES_PCT,
|
|
19
19
|
TEST_CODE_OWNERS,
|
|
20
20
|
TEST_COMMAND,
|
|
21
21
|
TEST_LEVELS_METADATA,
|
|
@@ -41,11 +41,21 @@ const {
|
|
|
41
41
|
TEST_BROWSER_NAME,
|
|
42
42
|
TEST_IS_RUM_ACTIVE,
|
|
43
43
|
TEST_PARAMETERS,
|
|
44
|
+
TEST_ITR_UNSKIPPABLE,
|
|
45
|
+
TEST_ITR_FORCED_RUN,
|
|
46
|
+
ITR_CORRELATION_ID,
|
|
44
47
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
45
48
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
49
|
+
const id = require('../../dd-trace/src/id')
|
|
46
50
|
const {
|
|
47
51
|
TELEMETRY_EVENT_CREATED,
|
|
48
52
|
TELEMETRY_EVENT_FINISHED,
|
|
53
|
+
TELEMETRY_CODE_COVERAGE_STARTED,
|
|
54
|
+
TELEMETRY_CODE_COVERAGE_FINISHED,
|
|
55
|
+
TELEMETRY_CODE_COVERAGE_EMPTY,
|
|
56
|
+
TELEMETRY_CODE_COVERAGE_NUM_FILES,
|
|
57
|
+
TELEMETRY_ITR_FORCED_TO_RUN,
|
|
58
|
+
TELEMETRY_ITR_UNSKIPPABLE,
|
|
49
59
|
TELEMETRY_TEST_SESSION,
|
|
50
60
|
} = require('../../dd-trace/src/ci-visibility/telemetry')
|
|
51
61
|
const { DD_MAJOR } = require('../../../version')
|
|
@@ -343,7 +353,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
343
353
|
testName,
|
|
344
354
|
testSuiteAbsolutePath,
|
|
345
355
|
isNew,
|
|
356
|
+
isAttemptToFix,
|
|
346
357
|
isDisabled,
|
|
358
|
+
isQuarantined,
|
|
347
359
|
isRumActive,
|
|
348
360
|
isTestFrameworkWorker,
|
|
349
361
|
requestErrorTags,
|
|
@@ -362,7 +374,9 @@ class VitestPlugin extends CiPlugin {
|
|
|
362
374
|
[TEST_SOURCE_START]: testStartLine || 1,
|
|
363
375
|
[TEST_STATUS]: 'skip',
|
|
364
376
|
[TEST_FINAL_STATUS]: 'skip',
|
|
377
|
+
...(isAttemptToFix ? { [TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX]: 'true' } : {}),
|
|
365
378
|
...(isDisabled ? { [TEST_MANAGEMENT_IS_DISABLED]: 'true' } : {}),
|
|
379
|
+
...(isQuarantined ? { [TEST_MANAGEMENT_IS_QUARANTINED]: 'true' } : {}),
|
|
366
380
|
...(isNew ? { [TEST_IS_NEW]: 'true' } : {}),
|
|
367
381
|
...(isRumActive ? { [TEST_IS_RUM_ACTIVE]: 'true' } : {}),
|
|
368
382
|
...(isTestFrameworkWorker ? { [TEST_IS_TEST_FRAMEWORK_WORKER]: 'true' } : {}),
|
|
@@ -393,10 +407,16 @@ class VitestPlugin extends CiPlugin {
|
|
|
393
407
|
frameworkVersion,
|
|
394
408
|
isTestFrameworkWorker,
|
|
395
409
|
isVitestNoWorkerInitActive,
|
|
410
|
+
disableTestImpactAnalysis,
|
|
396
411
|
isBrowserMode,
|
|
397
412
|
browserDriver,
|
|
398
413
|
browserName,
|
|
399
414
|
browserProjectName,
|
|
415
|
+
isCodeCoverageEnabled,
|
|
416
|
+
coverageLibrary,
|
|
417
|
+
itrCorrelationId,
|
|
418
|
+
isUnskippable,
|
|
419
|
+
isForcedToRun,
|
|
400
420
|
} = ctx
|
|
401
421
|
|
|
402
422
|
const testCommand = ctx.testCommand || 'vitest run'
|
|
@@ -416,6 +436,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
416
436
|
const testSessionName = getTestSessionName(this.config, trimmedCommand, this.testEnvironmentMetadata)
|
|
417
437
|
if (this.tracer._exporter.addMetadataTags) {
|
|
418
438
|
const libraryCapabilitiesTags = this.getLibraryCapabilitiesTags(frameworkVersion, {
|
|
439
|
+
disableTestImpactAnalysis,
|
|
419
440
|
isVitestNoWorkerInitActive,
|
|
420
441
|
})
|
|
421
442
|
this.tracer._exporter.addMetadataTags({
|
|
@@ -444,6 +465,20 @@ class VitestPlugin extends CiPlugin {
|
|
|
444
465
|
if (isTestFrameworkWorker) {
|
|
445
466
|
testSuiteMetadata[TEST_IS_TEST_FRAMEWORK_WORKER] = 'true'
|
|
446
467
|
}
|
|
468
|
+
if (itrCorrelationId) {
|
|
469
|
+
testSuiteMetadata[ITR_CORRELATION_ID] = itrCorrelationId
|
|
470
|
+
}
|
|
471
|
+
if (isUnskippable) {
|
|
472
|
+
testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
473
|
+
this.telemetry.count(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
|
|
474
|
+
}
|
|
475
|
+
if (isForcedToRun) {
|
|
476
|
+
testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
477
|
+
this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
|
|
478
|
+
}
|
|
479
|
+
if (isCodeCoverageEnabled) {
|
|
480
|
+
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_STARTED, 'suite', { library: coverageLibrary })
|
|
481
|
+
}
|
|
447
482
|
setBrowserTags(testSuiteMetadata, {
|
|
448
483
|
browserDriver,
|
|
449
484
|
browserName,
|
|
@@ -473,9 +508,33 @@ class VitestPlugin extends CiPlugin {
|
|
|
473
508
|
return ctx.currentStore
|
|
474
509
|
})
|
|
475
510
|
|
|
476
|
-
this.addSub('ci:vitest:test-suite:finish', ({
|
|
511
|
+
this.addSub('ci:vitest:test-suite:finish', ({
|
|
512
|
+
testSuiteSpan,
|
|
513
|
+
status,
|
|
514
|
+
coverageFiles,
|
|
515
|
+
coverageLibrary,
|
|
516
|
+
testSuiteAbsolutePath,
|
|
517
|
+
deferFlush,
|
|
518
|
+
onDone,
|
|
519
|
+
}) => {
|
|
477
520
|
if (testSuiteSpan) {
|
|
478
521
|
testSuiteSpan.setTag(TEST_STATUS, status)
|
|
522
|
+
if (coverageFiles) {
|
|
523
|
+
if (!coverageFiles.length) {
|
|
524
|
+
this.telemetry.count(TELEMETRY_CODE_COVERAGE_EMPTY)
|
|
525
|
+
}
|
|
526
|
+
const files = new Set(coverageFiles)
|
|
527
|
+
files.add(testSuiteAbsolutePath)
|
|
528
|
+
const relativeFiles = [...files].map(filename => getTestSuitePath(filename, this.repositoryRoot))
|
|
529
|
+
const { _traceId, _spanId } = testSuiteSpan.context()
|
|
530
|
+
this.tracer._exporter.exportCoverage({
|
|
531
|
+
sessionId: _traceId,
|
|
532
|
+
suiteId: _spanId,
|
|
533
|
+
files: relativeFiles,
|
|
534
|
+
})
|
|
535
|
+
this.telemetry.ciVisEvent(TELEMETRY_CODE_COVERAGE_FINISHED, 'suite', { library: coverageLibrary })
|
|
536
|
+
this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeFiles.length)
|
|
537
|
+
}
|
|
479
538
|
testSuiteSpan.finish()
|
|
480
539
|
finishAllTraceSpans(testSuiteSpan)
|
|
481
540
|
}
|
|
@@ -490,6 +549,17 @@ class VitestPlugin extends CiPlugin {
|
|
|
490
549
|
}
|
|
491
550
|
})
|
|
492
551
|
|
|
552
|
+
this.addSub('ci:vitest:worker-report:coverage', data => {
|
|
553
|
+
const formattedCoverages = JSON.parse(data).map(coverage => ({
|
|
554
|
+
sessionId: id(coverage.sessionId),
|
|
555
|
+
suiteId: id(coverage.suiteId),
|
|
556
|
+
files: coverage.files,
|
|
557
|
+
}))
|
|
558
|
+
for (const formattedCoverage of formattedCoverages) {
|
|
559
|
+
this.tracer._exporter.exportCoverage(formattedCoverage)
|
|
560
|
+
}
|
|
561
|
+
})
|
|
562
|
+
|
|
493
563
|
this.addBind('ci:vitest:test-suite:error', (ctx) => {
|
|
494
564
|
const { error } = ctx
|
|
495
565
|
const testSuiteSpan = ctx.currentStore?.testSuiteSpan
|
|
@@ -512,12 +582,18 @@ class VitestPlugin extends CiPlugin {
|
|
|
512
582
|
isEarlyFlakeDetectionEnabled,
|
|
513
583
|
isEarlyFlakeDetectionFaulty,
|
|
514
584
|
isTestManagementTestsEnabled,
|
|
585
|
+
isCodeCoverageEnabled,
|
|
586
|
+
isSuitesSkippingEnabled,
|
|
587
|
+
isSuitesSkipped,
|
|
588
|
+
numSkippedSuites,
|
|
589
|
+
hasUnskippableSuites,
|
|
590
|
+
hasForcedToRunSuites,
|
|
515
591
|
requestErrorTags,
|
|
516
592
|
vitestPool,
|
|
517
593
|
isVitestNoWorkerInitActive,
|
|
518
594
|
onDone,
|
|
519
595
|
}) => {
|
|
520
|
-
for (const [tag, value] of Object.entries(requestErrorTags
|
|
596
|
+
for (const [tag, value] of Object.entries(requestErrorTags)) {
|
|
521
597
|
this.testSessionSpan.setTag(tag, value)
|
|
522
598
|
this.testModuleSpan.setTag(tag, value)
|
|
523
599
|
}
|
|
@@ -527,10 +603,20 @@ class VitestPlugin extends CiPlugin {
|
|
|
527
603
|
this.testModuleSpan.setTag('error', error)
|
|
528
604
|
this.testSessionSpan.setTag('error', error)
|
|
529
605
|
}
|
|
530
|
-
|
|
531
|
-
this.
|
|
532
|
-
this.
|
|
533
|
-
|
|
606
|
+
addIntelligentTestRunnerSpanTags(
|
|
607
|
+
this.testSessionSpan,
|
|
608
|
+
this.testModuleSpan,
|
|
609
|
+
{
|
|
610
|
+
isSuitesSkipped,
|
|
611
|
+
isSuitesSkippingEnabled,
|
|
612
|
+
isCodeCoverageEnabled,
|
|
613
|
+
testCodeCoverageLinesTotal,
|
|
614
|
+
skippingType: 'suite',
|
|
615
|
+
skippingCount: numSkippedSuites,
|
|
616
|
+
hasUnskippableSuites,
|
|
617
|
+
hasForcedToRunSuites,
|
|
618
|
+
}
|
|
619
|
+
)
|
|
534
620
|
if (isEarlyFlakeDetectionEnabled) {
|
|
535
621
|
this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
|
|
536
622
|
}
|
|
@@ -566,12 +652,14 @@ class VitestPlugin extends CiPlugin {
|
|
|
566
652
|
* Returns Vitest library capability metadata tags.
|
|
567
653
|
* @param {string} frameworkVersion - The Vitest version.
|
|
568
654
|
* @param {object} [ctx] - Diagnostic channel context.
|
|
655
|
+
* @param {boolean} [ctx.disableTestImpactAnalysis] - Whether TIA is unsupported for this run.
|
|
569
656
|
* @param {boolean} [ctx.isVitestNoWorkerInitActive] - Whether no-worker init is active for this run.
|
|
570
657
|
* @returns {Record<string, string|undefined>}
|
|
571
658
|
*/
|
|
572
659
|
getLibraryCapabilitiesTags (frameworkVersion, ctx = {}) {
|
|
573
660
|
return getDefaultLibraryCapabilitiesTags(this.constructor.id, frameworkVersion, {
|
|
574
661
|
omitFailedTestReplay: ctx.isVitestNoWorkerInitActive,
|
|
662
|
+
omitTestImpactAnalysis: ctx.disableTestImpactAnalysis || ctx.isVitestNoWorkerInitActive,
|
|
575
663
|
})
|
|
576
664
|
}
|
|
577
665
|
|
|
@@ -15,6 +15,10 @@ const skipMethodSize = skipMethods.size
|
|
|
15
15
|
|
|
16
16
|
const nonConfigurableModuleExports = new WeakMap()
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {NonNullable<ReturnType<typeof globalThis.Object.getOwnPropertyDescriptor>>} Descriptor
|
|
20
|
+
*/
|
|
21
|
+
|
|
18
22
|
// Reused descriptor scratch space for the `name` and `length` slots that
|
|
19
23
|
// `copyProperties` and `wrapCallback` rewrite per wrap. `Object.defineProperty`
|
|
20
24
|
// reads the descriptor's slots synchronously and does not retain the object,
|
|
@@ -46,7 +50,7 @@ function copyProperties (original, wrapped) {
|
|
|
46
50
|
if (ownKeys.length !== 2) {
|
|
47
51
|
for (const key of ownKeys) {
|
|
48
52
|
if (skipMethods.has(key)) continue
|
|
49
|
-
const descriptor = /** @type {
|
|
53
|
+
const descriptor = /** @type {Descriptor} */ (Object.getOwnPropertyDescriptor(original, key))
|
|
50
54
|
if (descriptor.writable && descriptor.enumerable && descriptor.configurable) {
|
|
51
55
|
wrapped[key] = original[key]
|
|
52
56
|
} else if (descriptor.writable || descriptor.configurable || !Object.hasOwn(wrapped, key)) {
|
|
@@ -65,7 +69,7 @@ function copyObjectProperties (original, wrapped, skipKey) {
|
|
|
65
69
|
const ownKeys = Reflect.ownKeys(original)
|
|
66
70
|
for (const key of ownKeys) {
|
|
67
71
|
if (key === skipKey) continue
|
|
68
|
-
const descriptor = /** @type {
|
|
72
|
+
const descriptor = /** @type {Descriptor} */ (Object.getOwnPropertyDescriptor(original, key))
|
|
69
73
|
if (descriptor.writable && descriptor.enumerable && descriptor.configurable) {
|
|
70
74
|
wrapped[key] = original[key]
|
|
71
75
|
} else if (descriptor.writable || descriptor.configurable || !Object.hasOwn(wrapped, key)) {
|
|
@@ -121,6 +121,9 @@ class AIGuard extends NoopAIGuard {
|
|
|
121
121
|
*
|
|
122
122
|
* - Clones each message so callers cannot mutate the data set in the meta struct.
|
|
123
123
|
* - Truncates the list of messages and `content` fields emitting metrics accordingly.
|
|
124
|
+
*
|
|
125
|
+
* @param {import('../../../../index').aiguard.Message[]} messages
|
|
126
|
+
* @param {{ source: string, integration: string }} telemetryTags
|
|
124
127
|
*/
|
|
125
128
|
#buildMessagesForMetaStruct (messages, telemetryTags) {
|
|
126
129
|
const size = Math.min(messages.length, this.#maxMessagesLength)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { LRUCache } = require('
|
|
4
|
-
const log = require('
|
|
5
|
-
const web = require('
|
|
6
|
-
const
|
|
7
|
-
const
|
|
3
|
+
const { LRUCache } = require('../../../../../vendor/dist/lru-cache')
|
|
4
|
+
const log = require('../../log')
|
|
5
|
+
const web = require('../../plugins/util/web')
|
|
6
|
+
const { updateBlockFailureMetric } = require('../telemetry')
|
|
7
|
+
const blockedTemplates = require('./templates')
|
|
8
8
|
|
|
9
9
|
// Bounded by the LRU as defense-in-depth: getSpecificKey already keys on the
|
|
10
10
|
// resolved route (or the path with the query string stripped) so cardinality
|
|
@@ -182,7 +182,26 @@ function getBlockingAction (actions) {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
|
-
*
|
|
185
|
+
* Turns a WAF result's actions into a blocking response, when one is requested.
|
|
186
|
+
* Single seam from WAF output to {@link block}, shared by every request/response/auth handler.
|
|
187
|
+
*
|
|
188
|
+
* @param {Record<string, unknown>|undefined} actions WAF result actions.
|
|
189
|
+
* @param {import('http').IncomingMessage} req
|
|
190
|
+
* @param {import('http').ServerResponse} res
|
|
191
|
+
* @param {object} rootSpan
|
|
192
|
+
* @param {AbortController} abortController
|
|
193
|
+
*/
|
|
194
|
+
function handleResults (actions, req, res, rootSpan, abortController) {
|
|
195
|
+
if (!actions || !req || !res || !rootSpan || !abortController) return
|
|
196
|
+
|
|
197
|
+
const blockingAction = getBlockingAction(actions)
|
|
198
|
+
if (blockingAction) {
|
|
199
|
+
block(req, res, rootSpan, abortController, blockingAction)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @param {import('../../config/config-base')} [config] - Tracer configuration
|
|
186
205
|
*/
|
|
187
206
|
function setTemplates (config) {
|
|
188
207
|
templates.html.body = config?.appsec?.blockedTemplateHtml
|
|
@@ -233,6 +252,7 @@ module.exports = {
|
|
|
233
252
|
specificBlockingTypes,
|
|
234
253
|
getBlockingData,
|
|
235
254
|
getBlockingAction,
|
|
255
|
+
handleResults,
|
|
236
256
|
setTemplates,
|
|
237
257
|
isBlocked,
|
|
238
258
|
setDefaultBlockingActionParameters,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../log')
|
|
4
|
+
const web = require('../../plugins/util/web')
|
|
5
|
+
const addresses = require('../addresses')
|
|
6
|
+
const { handleResults } = require('../blocking')
|
|
7
|
+
const { getActiveRequest } = require('../store')
|
|
8
|
+
const UserTracking = require('../user_tracking')
|
|
9
|
+
const waf = require('../waf')
|
|
10
|
+
|
|
11
|
+
function onPassportVerify ({ framework, login, user, success, abortController }) {
|
|
12
|
+
const req = getActiveRequest()
|
|
13
|
+
const rootSpan = req && web.root(req)
|
|
14
|
+
|
|
15
|
+
if (!rootSpan) {
|
|
16
|
+
log.warn('[ASM] No rootSpan found in onPassportVerify')
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const results = UserTracking.trackLogin(framework, login, user, success, rootSpan)
|
|
21
|
+
|
|
22
|
+
handleResults(results?.actions, req, web.getContext(req)?.res, rootSpan, abortController)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function onPassportDeserializeUser ({ user, abortController }) {
|
|
26
|
+
const req = getActiveRequest()
|
|
27
|
+
const rootSpan = req && web.root(req)
|
|
28
|
+
|
|
29
|
+
if (!rootSpan) {
|
|
30
|
+
log.warn('[ASM] No rootSpan found in onPassportDeserializeUser')
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const results = UserTracking.trackUser(user, rootSpan)
|
|
35
|
+
|
|
36
|
+
handleResults(results?.actions, req, web.getContext(req)?.res, rootSpan, abortController)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function onExpressSession ({ req, res, sessionId, abortController }) {
|
|
40
|
+
const rootSpan = web.root(req)
|
|
41
|
+
if (!rootSpan) {
|
|
42
|
+
log.warn('[ASM] No rootSpan found in onExpressSession')
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const isSdkCalled = rootSpan.context().getTag('usr.session_id')
|
|
47
|
+
if (isSdkCalled) return
|
|
48
|
+
|
|
49
|
+
const results = waf.run({
|
|
50
|
+
persistent: {
|
|
51
|
+
[addresses.USER_SESSION_ID]: sessionId,
|
|
52
|
+
},
|
|
53
|
+
}, req)
|
|
54
|
+
|
|
55
|
+
handleResults(results?.actions, req, res, rootSpan, abortController)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
onPassportVerify,
|
|
60
|
+
onPassportDeserializeUser,
|
|
61
|
+
onExpressSession,
|
|
62
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { HTTP_CLIENT_IP } = require('../../../../../ext/tags')
|
|
4
|
+
const web = require('../../plugins/util/web')
|
|
5
|
+
const { extractIp } = require('../../plugins/util/ip_extractor')
|
|
6
|
+
const { isEmpty } = require('../../util')
|
|
7
|
+
const addresses = require('../addresses')
|
|
8
|
+
const apiSecurity = require('../api_security')
|
|
9
|
+
const { handleResults } = require('../blocking')
|
|
10
|
+
const Reporter = require('../reporter')
|
|
11
|
+
const { getActiveRequest } = require('../store')
|
|
12
|
+
const waf = require('../waf')
|
|
13
|
+
const { storedResponseHeaders, copyHeadersOmitting } = require('./http-shared')
|
|
14
|
+
|
|
15
|
+
const analyzedBodies = new WeakSet()
|
|
16
|
+
const analyzedCookies = new WeakSet()
|
|
17
|
+
const storedBodies = new WeakMap()
|
|
18
|
+
|
|
19
|
+
let config
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('../../config/config-base')|undefined} _config
|
|
23
|
+
*/
|
|
24
|
+
function setConfig (_config) {
|
|
25
|
+
config = _config
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function onRequestBodyParsed ({ req, res, body, abortController }) {
|
|
29
|
+
if (body === undefined || body === null) return
|
|
30
|
+
|
|
31
|
+
if (!req) {
|
|
32
|
+
req = getActiveRequest()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const rootSpan = web.root(req)
|
|
36
|
+
if (!rootSpan) return
|
|
37
|
+
|
|
38
|
+
if (!req.body) {
|
|
39
|
+
// do not store body if it is in req.body
|
|
40
|
+
storedBodies.set(req, body)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof body === 'object') {
|
|
44
|
+
if (isEmpty(body)) return
|
|
45
|
+
analyzedBodies.add(body)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const results = waf.run({
|
|
49
|
+
persistent: {
|
|
50
|
+
[addresses.HTTP_INCOMING_BODY]: body,
|
|
51
|
+
},
|
|
52
|
+
}, req)
|
|
53
|
+
|
|
54
|
+
handleResults(results?.actions, req, res, rootSpan, abortController)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function onRequestCookieParser ({ req, res, abortController, cookies }) {
|
|
58
|
+
if (!cookies || typeof cookies !== 'object') return
|
|
59
|
+
|
|
60
|
+
const rootSpan = web.root(req)
|
|
61
|
+
if (!rootSpan) return
|
|
62
|
+
|
|
63
|
+
if (isEmpty(cookies)) return
|
|
64
|
+
analyzedCookies.add(cookies)
|
|
65
|
+
|
|
66
|
+
const results = waf.run({
|
|
67
|
+
persistent: {
|
|
68
|
+
[addresses.HTTP_INCOMING_COOKIES]: cookies,
|
|
69
|
+
},
|
|
70
|
+
}, req)
|
|
71
|
+
|
|
72
|
+
handleResults(results?.actions, req, res, rootSpan, abortController)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function incomingHttpStartTranslator ({ req, res, abortController }) {
|
|
76
|
+
const rootSpan = web.root(req)
|
|
77
|
+
if (!rootSpan) return
|
|
78
|
+
|
|
79
|
+
const clientIp = extractIp(config, req)
|
|
80
|
+
|
|
81
|
+
rootSpan.addTags({
|
|
82
|
+
'_dd.appsec.enabled': 1,
|
|
83
|
+
'_dd.runtime_family': 'nodejs',
|
|
84
|
+
[HTTP_CLIENT_IP]: clientIp,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (config.inferredProxyServicesEnabled) {
|
|
88
|
+
const context = web.getContext(req)
|
|
89
|
+
if (context?.inferredProxySpan) {
|
|
90
|
+
context.inferredProxySpan.setTag('_dd.appsec.enabled', 1)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const persistent = {
|
|
95
|
+
[addresses.HTTP_INCOMING_URL]: req.url,
|
|
96
|
+
[addresses.HTTP_INCOMING_HEADERS]: copyHeadersOmitting(req.headers, 'cookie'),
|
|
97
|
+
[addresses.HTTP_INCOMING_METHOD]: req.method,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (clientIp) {
|
|
101
|
+
persistent[addresses.HTTP_CLIENT_IP] = clientIp
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const results = waf.run({ persistent }, req)
|
|
105
|
+
|
|
106
|
+
handleResults(results?.actions, req, res, rootSpan, abortController)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function incomingHttpEndTranslator ({ req, res }) {
|
|
110
|
+
const persistent = {}
|
|
111
|
+
|
|
112
|
+
// we need to keep this to support other body parsers
|
|
113
|
+
if (req.body !== undefined && req.body !== null) {
|
|
114
|
+
if (typeof req.body === 'object') {
|
|
115
|
+
if (!isEmpty(req.body) && !analyzedBodies.has(req.body)) {
|
|
116
|
+
persistent[addresses.HTTP_INCOMING_BODY] = req.body
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
persistent[addresses.HTTP_INCOMING_BODY] = req.body
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// we need to keep this to support other cookie parsers
|
|
124
|
+
if (
|
|
125
|
+
req.cookies !== null &&
|
|
126
|
+
typeof req.cookies === 'object' &&
|
|
127
|
+
!isEmpty(req.cookies) &&
|
|
128
|
+
!analyzedCookies.has(req.cookies)
|
|
129
|
+
) {
|
|
130
|
+
persistent[addresses.HTTP_INCOMING_COOKIES] = req.cookies
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// we need to keep this to support nextjs
|
|
134
|
+
const query = req.query
|
|
135
|
+
if (
|
|
136
|
+
query !== null &&
|
|
137
|
+
typeof query === 'object' &&
|
|
138
|
+
!isEmpty(query)
|
|
139
|
+
) {
|
|
140
|
+
persistent[addresses.HTTP_INCOMING_QUERY] = query
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// This hook runs before span finish, so ensure route/endpoint tags are available before API Security sampling runs.
|
|
144
|
+
web.setRouteOrEndpointTag(req)
|
|
145
|
+
|
|
146
|
+
const apiSecSamplingDecision = apiSecurity.sampleRequest(req, res, true)
|
|
147
|
+
if (apiSecSamplingDecision === apiSecurity.SamplingDecision.SAMPLE) {
|
|
148
|
+
persistent[addresses.WAF_CONTEXT_PROCESSOR] = { 'extract-schema': true }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let wafResult
|
|
152
|
+
if (!isEmpty(persistent)) {
|
|
153
|
+
wafResult = waf.run({ persistent }, req)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
apiSecurity.reportRequest(req, apiSecSamplingDecision, wafResult)
|
|
157
|
+
|
|
158
|
+
waf.disposeContext(req)
|
|
159
|
+
|
|
160
|
+
const storedHeaders = storedResponseHeaders.get(req) || {}
|
|
161
|
+
|
|
162
|
+
const body = req.body || storedBodies.get(req)
|
|
163
|
+
Reporter.finishRequest(req, res, storedHeaders, body)
|
|
164
|
+
|
|
165
|
+
if (storedHeaders) {
|
|
166
|
+
storedResponseHeaders.delete(req)
|
|
167
|
+
}
|
|
168
|
+
storedBodies.delete(req)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function onRequestQueryParsed ({ req, res, query, abortController }) {
|
|
172
|
+
if (!query || typeof query !== 'object') return
|
|
173
|
+
|
|
174
|
+
if (!req) {
|
|
175
|
+
req = getActiveRequest()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const rootSpan = web.root(req)
|
|
179
|
+
if (!rootSpan) return
|
|
180
|
+
|
|
181
|
+
if (isEmpty(query)) return
|
|
182
|
+
|
|
183
|
+
const results = waf.run({
|
|
184
|
+
persistent: {
|
|
185
|
+
[addresses.HTTP_INCOMING_QUERY]: query,
|
|
186
|
+
},
|
|
187
|
+
}, req)
|
|
188
|
+
|
|
189
|
+
handleResults(results?.actions, req, res, rootSpan, abortController)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function onRequestProcessParams ({ req, res, abortController, params }) {
|
|
193
|
+
const rootSpan = web.root(req)
|
|
194
|
+
if (!rootSpan) return
|
|
195
|
+
|
|
196
|
+
if (!params || typeof params !== 'object' || isEmpty(params)) return
|
|
197
|
+
|
|
198
|
+
const results = waf.run({
|
|
199
|
+
persistent: {
|
|
200
|
+
[addresses.HTTP_INCOMING_PARAMS]: params,
|
|
201
|
+
},
|
|
202
|
+
}, req)
|
|
203
|
+
|
|
204
|
+
handleResults(results?.actions, req, res, rootSpan, abortController)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
setConfig,
|
|
209
|
+
onRequestBodyParsed,
|
|
210
|
+
onRequestCookieParser,
|
|
211
|
+
incomingHttpStartTranslator,
|
|
212
|
+
incomingHttpEndTranslator,
|
|
213
|
+
onRequestQueryParsed,
|
|
214
|
+
onRequestProcessParams,
|
|
215
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const web = require('../../plugins/util/web')
|
|
4
|
+
const { isEmpty } = require('../../util')
|
|
5
|
+
const addresses = require('../addresses')
|
|
6
|
+
const apiSecurity = require('../api_security')
|
|
7
|
+
const { isBlocked, callBlockDelegation, handleResults } = require('../blocking')
|
|
8
|
+
const waf = require('../waf')
|
|
9
|
+
const { storedResponseHeaders, copyHeadersOmitting } = require('./http-shared')
|
|
10
|
+
|
|
11
|
+
const responseAnalyzedSet = new WeakSet()
|
|
12
|
+
|
|
13
|
+
function onResponseBody ({ req, res, body }) {
|
|
14
|
+
if (!body || typeof body !== 'object') return
|
|
15
|
+
if (apiSecurity.sampleRequest(req, res) !== apiSecurity.SamplingDecision.SAMPLE) return
|
|
16
|
+
|
|
17
|
+
// we don't support blocking at this point, so no results needed
|
|
18
|
+
waf.run({
|
|
19
|
+
persistent: {
|
|
20
|
+
[addresses.HTTP_INCOMING_RESPONSE_BODY]: body,
|
|
21
|
+
},
|
|
22
|
+
}, req)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function onResponseWriteHead ({ req, res, abortController, statusCode, responseHeaders }) {
|
|
26
|
+
// Normalize header names to lowercase so downstream consumers see the same shape
|
|
27
|
+
// regardless of how the caller wrote them.
|
|
28
|
+
const normalizedResponseHeaders = {}
|
|
29
|
+
for (const [key, value] of Object.entries(responseHeaders)) {
|
|
30
|
+
normalizedResponseHeaders[key.toLowerCase()] = value
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!isEmpty(normalizedResponseHeaders)) {
|
|
34
|
+
storedResponseHeaders.set(req, normalizedResponseHeaders)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// TODO: do not call waf if inside block()
|
|
38
|
+
// if (isBlocking()) {
|
|
39
|
+
// return
|
|
40
|
+
// }
|
|
41
|
+
|
|
42
|
+
// avoid "write after end" error
|
|
43
|
+
if (isBlocked(res) || callBlockDelegation(res)) {
|
|
44
|
+
abortController?.abort()
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// avoid double waf call
|
|
49
|
+
if (responseAnalyzedSet.has(res)) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const rootSpan = web.root(req)
|
|
54
|
+
if (!rootSpan) return
|
|
55
|
+
|
|
56
|
+
const results = waf.run({
|
|
57
|
+
persistent: {
|
|
58
|
+
[addresses.HTTP_INCOMING_RESPONSE_CODE]: String(statusCode),
|
|
59
|
+
[addresses.HTTP_INCOMING_RESPONSE_HEADERS]: copyHeadersOmitting(normalizedResponseHeaders, 'set-cookie'),
|
|
60
|
+
},
|
|
61
|
+
}, req)
|
|
62
|
+
|
|
63
|
+
responseAnalyzedSet.add(res)
|
|
64
|
+
|
|
65
|
+
handleResults(results?.actions, req, res, rootSpan, abortController)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function onResponseOperation ({ res, abortController }) {
|
|
69
|
+
if (isBlocked(res)) {
|
|
70
|
+
abortController?.abort()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
onResponseBody,
|
|
76
|
+
onResponseWriteHead,
|
|
77
|
+
onResponseOperation,
|
|
78
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Response headers captured on `responseWriteHead` (http-response) and later read
|
|
4
|
+
// when the request finishes (http-request end translator). This is the only piece
|
|
5
|
+
// of per-request state shared across the two handler modules.
|
|
6
|
+
const storedResponseHeaders = new WeakMap()
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {Record<string, unknown>} src
|
|
10
|
+
* @param {string} omit
|
|
11
|
+
* @returns {Record<string, unknown>}
|
|
12
|
+
*/
|
|
13
|
+
function copyHeadersOmitting (src, omit) {
|
|
14
|
+
const filtered = {}
|
|
15
|
+
for (const key of Object.keys(src)) {
|
|
16
|
+
if (key !== omit) filtered[key] = src[key]
|
|
17
|
+
}
|
|
18
|
+
return filtered
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
storedResponseHeaders,
|
|
23
|
+
copyHeadersOmitting,
|
|
24
|
+
}
|