feat(capabilities): inject container-id/entity-id/external-env headers#166
Conversation
libdatadog's automatic entity detection (libdd-common's entity_id module) is gated #[cfg(unix)] and therefore inert on the wasm32 target, and DD_EXTERNAL_ENV is unreachable from wasm, so the native exporter never sent Datadog-Container-ID / Datadog-Entity-ID / Datadog-External-Env. Node can read /proc and process.env, so detect them in the HTTP transport and rewrite the Rust-rendered request head to carry them — the headers native libdatadog adds via set_standard_headers. Detection mirrors dd-trace-js's exporters/common/docker.js (the proven legacy exporter path) and libdd-common's compute_entity_id (ci-<container_id> else in-<cgroup_inode>). The empty datadog-container-id libdatadog emits on wasm is replaced rather than duplicated, and external-env is rejected if it carries CR/LF or non-ASCII to avoid header injection.
Overall package sizeSelf size: 30.08 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------|🤖 This report was automatically generated by heaviest-objects-in-the-universe |
There was a problem hiding this comment.
Pull request overview
Adds WASM-compatible injection of Datadog entity headers by detecting container/entity/external-env values in the Node HTTP transport layer (which can read /proc and process.env) and rewriting the Rust-rendered HTTP/1.1 request head to include them, with accompanying unit and E2E tests to satisfy system-test expectations.
Changes:
- Implement
detectEntityHeaders()(container-id/entity-id via cgroup parsing; external-env viaDD_EXTERNAL_ENVwith CR/LF + non-ASCII rejection) plus memoized caching. - Implement
applyEntityHeaders()to drop/replace any existing lines for injected header names and append detected headers while keeping the rest of the head intact. - Add tests covering detection, head rewriting behavior (including dedup of empty container-id), and a real-transport E2E asserting headers appear on the wire.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
crates/capabilities/src/http_transport.js |
Adds entity header detection + caching and rewrites the Rust-rendered request head to inject/replace these headers during httpRequest. |
test/http_transport.js |
Adds unit tests for detection and head rewriting, plus an end-to-end transport test validating header injection on the wire. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
rochdev
left a comment
There was a problem hiding this comment.
This should be an event that is handled by the binary instead of altering the headers from the outside. It's fine for now to unblock, but this should be addressed as a follow-up, and ideally a TODO should be added.
#166) libdatadog's automatic entity detection (libdd-common's entity_id module) is gated #[cfg(unix)] and therefore inert on the wasm32 target, and DD_EXTERNAL_ENV is unreachable from wasm, so the native exporter never sent Datadog-Container-ID / Datadog-Entity-ID / Datadog-External-Env. Node can read /proc and process.env, so detect them in the HTTP transport and rewrite the Rust-rendered request head to carry them — the headers native libdatadog adds via set_standard_headers. Detection mirrors dd-trace-js's exporters/common/docker.js (the proven legacy exporter path) and libdd-common's compute_entity_id (ci-<container_id> else in-<cgroup_inode>). The empty datadog-container-id libdatadog emits on wasm is replaced rather than duplicated, and external-env is rejected if it carries CR/LF or non-ASCII to avoid header injection.
Native (wasm) trace export never sent
Datadog-Container-ID/Datadog-Entity-ID/Datadog-External-Env: libdatadog's entity detection (libdd-commonentity_id) is gated#[cfg(unix)]and inert onwasm32, andDD_EXTERNAL_ENVis unreachable from wasm. System-tests expect these headers.What
Detect the entity headers in the Node HTTP transport (
crates/capabilities/src/http_transport.js) — Node can read/procandprocess.env— and rewrite the Rust-rendered HTTP/1.1 request head to carry them. This is the wasm analog of the headers native libdatadog adds viaEndpoint::set_standard_headers.detectEntityHeaders()mirrors dd-trace-js'sexporters/common/docker.js(the proven legacy-exporter path) andlibdd-common'scompute_entity_id(ci-<container_id>elsein-<cgroup_inode>); external-env fromDD_EXTERNAL_ENV. Cached (values are process-stable).applyEntityHeaders()rewrites the pre-rendered head: it drops any existing line for a name it sets — so libdatadog's emptydatadog-container-idis replaced, not duplicated — appends the detected headers, and preserves the request line + framing (Host,Content-Length) byte-for-byte (latin1 round-trip). Returns the head untouched when nothing is detected or the head is malformed.DD_EXTERNAL_ENVis rejected if it contains CR/LF or non-ASCII, to avoid header injection (native libdatadog rejects invalid bytes viaHeaderValue).Why here (not Rust)
The head is rendered in Rust but wasm cannot read
/proc/env; the Node transport is the only layer with both the rendered head and OS access. No upstream-libdatadog or dd-trace-js change is required — consumers pick this up on the next release.Tests
test/http_transport.js(pre-existing observer + UDS tests preserved): detection (docker id,in-<inode>fallback, external-env, CR/LF rejection, empty case), head rewrite (append + framing preservation, empty-container-id dedup, no-op, malformed passthrough), and a real-transport E2E assertingdatadog-external-envlands on the wire through the rewritten head.node --test test/http_transport.js→ 16 passingnode --test test/pipeline.js(rebuilt wasm) → 49 passingeslintclean