Skip to content

feat: self-instrument with the ClickStack browser and Node SDKs - #3575

Merged
kodiakhq[bot] merged 6 commits into
mainfrom
sdk-upgrade
Nov 6, 2026
Merged

kodiakhq[bot] merged 6 commits into
mainfrom
sdk-upgrade

Conversation

@SpencerTorres

@SpencerTorres SpencerTorres commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

Closes HDX-5951

Summary

HyperDX now instruments itself with the new ClickStack SDKs (@clickhouse/clickstack-browser, @clickhouse/clickstack-node), the same path users take when they migrate from @hyperdx/browser and @hyperdx/node-opentelemetry. Existing env vars keep working: HYPERDX_API_KEY, per-signal browser endpoints and NEXT_PUBLIC_OTEL_RESOURCE_ATTRIBUTES.

Changes

  • API (packages/api/src/telemetry.ts, bin/hyperdx, utils/logger.ts, tasks/usageStats.ts): a -r preload replaces the old one and still starts nothing without a key. Pino and Express come from the bundled instrumentations. Usage stats get their own exporter, so HyperDX's ingestion key never reaches them.
  • Browser (packages/app/instrumentation-client.ts, AppNav): setUser and setAttributes({ teamName }) replace setGlobalAttributes. Custom actions stay custom-action spans for the Sessions page, and logout starts a new session.
  • Next.js server (packages/app/instrumentation.ts, Dockerfiles): moved to the package root so production builds load it, which they never did before. It reports as <service>-next and skips static assets. HDX_EXPORTER_ENABLED=false turns it off.
  • Shared (packages/common-utils/src/selfTelemetry.ts): one tested helper decides whether the API and the Next server start the SDK.

Screenshots

Example self-instrumented data on ClickStack SDK
Example self-instrumented data on ClickStack SDK

@vercel

vercel Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Nov 6, 2026 2:51pm UTC
hyperdx-storybook Ready Ready Preview Nov 6, 2026 2:51pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8739f80

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/api Minor
@hyperdx/app Minor
@hyperdx/otel-collector Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Duplicate code analysis

No new duplicate code introduced by this PR.


jscpd compared this branch against origin/main
across app, api and common-utils. Only new clones are listed; the codebase
currently holds 110 in total (1.21%).
Run yarn dupes locally to reproduce, or yarn dupes:all for the full picture.

@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

E2E Test Results

✅ All tests passed • 463 passed • 1 skipped • 1389s

Status Count
✅ Passed 463
❌ Failed 0
⚠️ Flaky 1
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

@SpencerTorres
SpencerTorres marked this pull request as ready for review November 6, 2026 14:46
@SpencerTorres
SpencerTorres requested a review from a team as a code owner November 6, 2026 14:46
@SpencerTorres
SpencerTorres requested a review from knudtty November 6, 2026 14:46
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

🔴 Tier 4 — Critical

Touches authentication, tenancy data models, the public API or shipped database config — or substantially changes the query rendering engine, background tasks, the OTel pipeline, image build, or release CI.

Why this tier:

  • Background tasks or delivery pipeline substantially modified — 246 lines (bar: 30):
    • docker/hyperdx/Dockerfile
    • docker/otel-collector/config.standalone.yaml
    • packages/api/src/tasks/index.ts
    • packages/api/src/tasks/usageStats.ts
  • Cross-layer change: touches frontend (packages/app) + backend (packages/api) + shared utils (packages/common-utils)

Review process: Deep review from a domain expert. Synchronous walkthrough may be required.
SLA: Schedule synchronous review within 2 business days.

Stats
  • Production files changed: 35
  • Production lines changed: 983 (+ 897 in test files, excluded from tier calculation)
  • Critical-path lines changed: 246
  • Branch: sdk-upgrade
  • Author: SpencerTorres

To override this classification, remove the review/tier-4 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

Comment thread packages/api/src/tasks/usageStats.ts Outdated
),
processors: [
new BatchLogRecordProcessor({
exporter: new OTLPLogExporter({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Usage-stats exporter still sends the operator's OTLP headers to in-otel.hyperdx.io

OTLPLogExporter merges OTEL_EXPORTER_OTLP_HEADERS and OTEL_EXPORTER_OTLP_LOGS_HEADERS into the explicit headers. The explicit authorization replaces only that one key. Any other header the operator set for their own collector (for example x-honeycomb-team=<secret>, x-scope-orgid, or a vendor API-key header) is still sent to HyperDX's usage endpoint. The old SDK did the same, but this PR adds a dedicated exporter precisely to keep this path isolated. Fix it by not using the env-aware exporter: serialize the record with @opentelemetry/otlp-transformer (JsonLogsSerializer) and POST it with only the usage-stats key. The test mocks OTLPLogExporter, so it can't see the env merge. Add a test with OTEL_EXPORTER_OTLP_HEADERS set that checks the outgoing request headers.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

// process.exit() skips the SDK's exit-time flush, so a one-shot task would
// drop its own span and duration metric. Before init shutdown() resolves at
// once.
const exitAfterShutdown = (code: number) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — exitAfterShutdown isn't run-once or time-bounded, and the in-app cron keeps ticking while it waits

Before, process.exit ran right away. Now an uncaughtException or unhandledRejection leaves the process running until shutdown() settles, which can be up to the exporter/BSP timeouts against a black-holed collector. Meanwhile the CronJob started at line 80 can fire more ticks, and every further exception or rejection starts another shutdown(). A late rejection can also race exitAfterShutdown(0) from the one-shot path. Fix: guard with a module-level exiting flag, stop the cron job, and Promise.race the shutdown against a short timeout (for example 5 s) before calling process.exit.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

Comment thread packages/api/src/server.ts Outdated
// forceExit calls process.exit(), which skips the SDK's own
// exit-time flush. Before init (no key set) this resolves at
// once.
await shutdownTelemetry();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — SDK shutdown runs once per HTTP server, so the first server to finish stops telemetry while the other is still draining

gracefulShutdown is registered separately for appServer and opampServer (line 75), and each onShutdown now awaits shutdownTelemetry(). Whichever finishes first shuts down the SDK while the other server may still be draining requests (up to the 10 s timeout). Those requests' spans and logs are dropped, along with that server's own 'Server gracefully shut down...' line. Fix: shut telemetry down once, after both servers' shutdowns settle, for example with a shared promise built from Promise.allSettled over both, or a run-once wrapper.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

2 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 2 minor

1 posted as inline comment(s) on the changed lines. 1 listed below.

Findings outside the changed lines

1 minor
  • 🔵 packages/api/src/index.ts:403 — Second exit-after-telemetry helper with different semantics from tasks/index.ts → exitAfterFlush here and exitAfterShutdown in packages/api/src/tasks/index.ts solve the same problem (process.exit() skips the SDK's exit-time flush), but they behave differently. This one calls flush() and has no exiting guard, so repeated uncaught exceptions in dev start several flushes and several process.exit calls. The task version calls shutdown(), runs once, and keeps the first exit code. Move one guarded helper to packages/api/src/utils/ and use it in both entry points.

Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found. This SDK-migration PR (@hyperdx/* → @clickhouse/clickstack-* self-instrumentation) is well-tested and carefully commented. No P0/P1 issues survived re-grading: the reliability concerns all depend on unverified SDK internals (OTel exporters normally bound their own export timeout) and degrade telemetry data rather than app correctness, auth, or data integrity. Secret redaction, telemetry start-gating, and usage-stats key isolation were all verified intact. The items below are recommended hardening before or shortly after merge.

🟡 P2 — recommended

  • packages/api/src/index.ts:13 — The start-failure and uncaughtException exit paths changed from a guaranteed process.exit(1) to void flush().finally(() => process.exit(code)), so a flush() that never settles (unreachable collector, no bounded timeout at the call site) weakens the deliberate "exit so the orchestrator restarts" guarantee the #2966 comment protects; the task runner's shutdown() at tasks/index.ts:76 has the same shape.
    • Fix: Race flush()/shutdown() against an unref'd timeout that forces process.exit(code) so a stalled exporter cannot defer exit.
    • adversarial, reliability
  • packages/api/src/server.ts:80 — runOnceAfterAll(servers.length, shutdownTelemetry) only runs the telemetry shutdown after both servers' onShutdown fire, so if one server's onShutdown stalls the drained server waits on it and the SDK flush is skipped when the 10s force-timeout fires, dropping buffered telemetry.
    • Fix: Decouple per-server exit or bound the shared barrier with an independent timer so one stalled server cannot block the other's telemetry flush.
    • adversarial, reliability
  • packages/app/src/components/AppNav/AppNav.tsx:223 — setUser/setAttributes fire when meData resolves, but browser SDK init() is gated behind the async fetch('/api/config') in instrumentation-client.ts; if identity wins that race and the SDK does not buffer pre-init identity calls, the user stays unattributed for the whole RUM session since the effect only re-runs on meData change.
    • Fix: Confirm the SDK buffers pre-init setUser/setAttributes, or re-apply identity once init() signals ready.
    • frontend-races
  • packages/api/src/tasks/index.ts:117 — The in-app cron task process installs uncaughtException/unhandledRejection handlers and exitAfterShutdown, but no SIGTERM/SIGINT handler, so a normal container stop terminates it without running the new flush-on-exit path and drops the task's own spans and duration metric.
    • Fix: Add SIGTERM/SIGINT handlers that route through exitAfterShutdown(0), unless the SDK already registers its own signal-time flush (verify).
    • correctness
  • packages/app/src/utils/nextServerTelemetry.ts:36 — serviceNameFromResourceAttributes is a second, intentionally stricter parser of the same OTEL_RESOURCE_ATTRIBUTES format already parsed by parseResourceAttributes in config.ts; the two are documented as "twins" but can silently drift, and a mismatch makes the Next server pick a different service.name than the SDK.
    • Fix: Extract one shared parser with strict/lenient modes so service.name selection cannot diverge from the SDK's EnvDetector.
    • maintainability, adversarial
  • packages/app/src/utils/nextServerTelemetry.ts:13 — The static-asset span suppression relies on a hand-maintained STATIC_PATH_PREFIXES/STATIC_PATHS/STATIC_PATH_SUFFIXES allowlist whose own comment notes "a new file in public/ needs an entry here," so a forgotten entry silently doubles ingested span volume with nothing in CI to catch it.
    • Fix: Derive the list from public/ contents at build time, or add a regression test asserting known static paths classify as static.
    • maintainability
  • packages/api/src/index.ts — The new exitAfterFlush flush-before-exit behavior (the point of this change) has no test, while the sibling exitAfterShutdown in tasks/index.ts got a full test file; there is no test module for this entry point at all.
    • Fix: Add a test that mocks flush and asserts the start-failure and dev uncaughtException paths await flush() then process.exit(1).
    • testing
  • .changeset/clickstack-sdks.md:1 — The changeset bumps @hyperdx/api and @hyperdx/app but not @hyperdx/common-utils, which now ships a new runtime module (dist/selfTelemetry) that @hyperdx/api imports at runtime; an external install resolving @hyperdx/common-utils@^0.33.0 would get a version lacking that module.
    • Fix: Add a @hyperdx/common-utils bump so the published api/app can resolve dist/selfTelemetry, after verifying whether api is consumed outside the Docker/workspace build.
    • project-standards
🔵 P3 nitpicks (8)
  • packages/common-utils/src/selfTelemetry.ts:32 — hasAuthorization matches a valueless authorization entry (e.g. a bare OTEL_EXPORTER_OTLP_HEADERS=authorization), so decideSelfTelemetry skips appending the key and telemetry starts but every export is rejected unauthenticated with no error surfaced.
    • Fix: Treat an authorization entry with an empty value after the first = as absent.
  • packages/api/src/tasks/usageStats.ts:121 — The per-report LoggerProvider/BatchLogRecordProcessor is created lazily but never shut down, leaving its scheduled-delay timer running for the process lifetime (benign in the long-lived API; no leak across ticks since it is a module singleton).
    • Fix: Shut the provider down on server shutdown, or use a processor without a background timer.
  • packages/api/src/middleware/error.ts:95 — Removing recordException means errors from the non-last handlers of a multi-handler router.use() (which the HTTP instrumentation does not wrap, per the code's own comment) are still logged but no longer recorded as span exception events.
    • Fix: Re-record the exception on the active span for the uninstrumented multi-handler case, or confirm losing those from error tracking is acceptable.
  • packages/app/src/utils/browserTelemetry.ts:14 — recordAction uses the global trace API, so spans emitted before init() registers a provider become no-ops; the auto-run saved-search search executed emit in DBSearchPage.tsx can fire within the config-fetch window and be lost.
    • Fix: Defer or guard recordAction until init() has resolved if early actions must be captured.
  • packages/app/src/components/AppNav/AppNav.components.tsx:182 — Logout runs setUser(null)/setAttributes(null)/startNewSession() synchronously in the onClick immediately before a full-page <Link> navigation, so any async flush/teardown those calls start races the unload and may not complete.
    • Fix: Verify the SDK flushes synchronously (or on pagehide); the startNewSession() here is also partly redundant since the full navigation re-inits the SDK.
  • packages/api/src/telemetry.ts:23 — The if (telemetry.otlpHeaders !== undefined) process.env.OTEL_EXPORTER_OTLP_HEADERS = ... block is duplicated verbatim in telemetry.ts and nextServerTelemetry.ts:112.
    • Fix: Fold the env mutation into a shared applySelfTelemetryDecision helper next to decideSelfTelemetry.
  • packages/api/src/tasks/index.ts:65 — exitAfterShutdown (task) and exitAfterFlush (index.ts:13) implement the same drain-then-exit concern with inconsistent robustness — only the task variant has the exiting re-entrancy guard.
    • Fix: Extract a shared exitAfter(drain, code) helper with the guard built in so both entry points get once-only exit semantics.
  • packages/api/src/utils/runOnceAfterAll.ts:1 — The count=0 degenerate case (where fn would run on the first arrival) is untested; currently safe because callers always pass servers.length >= 1.
    • Fix: Add a count=0 test case or guard against count <= 0 in the implementation.

Reviewers (13): correctness, security, adversarial, reliability, testing, maintainability, kieran-typescript, frontend-races, project-standards, performance, previous-comments, learnings-researcher, agent-native.

Testing gaps:

  • No end-to-end test asserts that log records exported through the SDK's pino instrumentation (not just pino's local stdout) have Authorization/cookie redacted — the off-box export path is the security-critical sink and is currently unverified.
  • No test confirms the browser Resource ends with service.version = NEXT_PUBLIC_APP_VERSION when NEXT_PUBLIC_OTEL_RESOURCE_ATTRIBUTES also sets service.version (precedence now depends on SDK ordering).
  • No test proves a hung or unreachable collector cannot wedge process.exit on the flush()/shutdown() exit paths.

Pre-existing (not introduced, not counted): the hardcoded usage-stats ingestion key in tasks/usageStats.ts was moved, not added; decideSelfTelemetry appending the API key to operator-set OTLP headers preserves prior SDK behavior with operator-controlled inputs.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

// telemetry (it hides the browser key in /api/config) never got Next server
// telemetry either, because production builds did not load
// instrumentation.ts before.
if (!HDX_EXPORTER_ENABLED) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — HDX_EXPORTER_ENABLED=false stops only the Next server, so the API and alert task keep exporting

The shared decideSelfTelemetry is described as the one place that decides whether the SDK starts, but the HDX_EXPORTER_ENABLED check sits outside it, in the Next-only path. In the single-container hyperdx/all-in-one image (docker/hyperdx/entry.prod.sh), setting HDX_EXPORTER_ENABLED=false together with HYPERDX_API_KEY turns off browser and Next telemetry, but packages/api/src/telemetry.ts still starts the SDK for the API and check-alerts. Fix: add an HDX_EXPORTER_ENABLED field to SelfTelemetryEnv and return { start: false } for it inside decideSelfTelemetry, so both entry points share one gate. If the API is meant to ignore the flag, say that explicitly in the changeset.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@kodiakhq
kodiakhq Bot merged commit 5b0d074 into main Nov 6, 2026
26 checks passed
@kodiakhq
kodiakhq Bot deleted the sdk-upgrade branch November 6, 2026 20:10
BilalAtique added a commit to BilalAtique/hyperdx that referenced this pull request Nov 7, 2026
Since ClickHouse#3575 the API installs systeminformation through
@opentelemetry/instrumentation-host-metrics, not @opentelemetry/host-metrics.
That package asks for ^5.31.6, but the old ^5.24.0 pin held it at 5.30.7.
kodiakhq Bot pushed a commit that referenced this pull request Nov 7, 2026
## Summary

The root `resolutions` block pins `systeminformation` to `^5.24.0`, and `yarn.lock` resolves it to 5.30.7. `@hyperdx/api` installs it through `@clickhouse/clickstack-node` → `@opentelemetry/auto-instrumentations-node` → `@opentelemetry/instrumentation-host-metrics` 0.6.0 (since #3575), so it is in the API image. That package asks for `^5.31.6`, but the old pin holds it at 5.30.7. `npm audit` reports 7 advisories for 5.30.7, 5 of them high. Two of them are the command injections in `networkInterfaces()` on Linux from the issue: GHSA-5xpp-75jx-m839 (fixed in 5.31.7) and GHSA-hvx9-hwr7-wjj9 (fixed in 5.31.6).

- The resolution is now `^5.33.19`, the newest 5.x release. It fixes all 7 advisories. The last fix (GHSA-3f7j-7jwc-fxfj, Windows) is in 5.33.16.
- `yarn.lock` changes only the `systeminformation` entry, from 5.30.7 to 5.33.19.
- A changeset bumps `@hyperdx/api` at `patch`.

Things to be aware of:

- I did not find a path from HyperDX code to the vulnerable functions. `@opentelemetry/auto-instrumentations-node` leaves host metrics off by default (`defaultExcludedInstrumentations`). `packages/api/src/telemetry.ts` turns them on only when `NODE_ENV=development` and an API key is set, and the Docker images set `NODE_ENV=production`. An operator can still turn them on with `OTEL_NODE_ENABLED_INSTRUMENTATIONS`. The instrumentation calls `networkStats()` with no argument. On Linux, that reads only the default interface (`ip route`, then `/sys/class/net/<iface>/...`) and does not call `networkInterfaces()`. It calls it only on Windows, or for the `'*'` argument. I measured this on Linux (see Tests). So this change removes a vulnerable version from the image and from scanner reports. I could not show an exploit through HyperDX.
- #1803 raised this resolution to `^5.31.0` in March. It was stacked on #1740 and merged into the #1740 branch after #1740 had landed, so the bump never reached `main`.
- 5.33.19 was published on 2026-10-30, so it passes the repo's 7-day `npmMinimalAgeGate`.
- The systeminformation changelog from 5.30.7 to 5.33.19 lists fixes and hardening, and no API removals.

### Tests

`yarn install` with the repo's Yarn (4.13.0) changes only the `systeminformation` entry in `yarn.lock`. `yarn why systeminformation` shows `5.30.7 (via npm:^5.24.0)` on `main` and `5.33.19 (via npm:^5.33.19)` on this branch, both under `@opentelemetry/instrumentation-host-metrics@npm:0.6.0` (before #3575: `@opentelemetry/host-metrics@npm:0.35.5`). `node_modules` has one copy, 5.33.19.

The API starts with the new version. On this branch after #3575, I started `packages/api/src/index.ts` with `NODE_ENV=development` and the `src/telemetry.ts` preload (the `yarn dev` command, without nodemon and dotenv), a dummy `HYPERDX_API_KEY`, and the OTLP exports sent to a local HTTP listener. The first metrics export had `system.cpu.time`, `system.memory.usage`, `system.network.io` and `system.network.errors`. The log had no uncaught errors or unhandled rejections. Before #3575 I ran the same check with `@opentelemetry/host-metrics` 0.35.5, with the same result.

For the before/after checks I used two small installs with the pre-#3575 `yarn.lock` versions of `@opentelemetry/host-metrics` (0.35.5), `@opentelemetry/sdk-metrics` (1.30.1) and `@opentelemetry/api` (1.9.0). The `systeminformation` call in `@opentelemetry/instrumentation-host-metrics` 0.6.0 (`build/src/stats/si.js`) is the same code as in `host-metrics` 0.35.5, so these results apply to the current dependent too. An npm override sets `systeminformation` to 5.30.7 (`main`) or 5.33.19 (this branch). The Linux runs use `node:22.23.4-alpine`, the base of the API image, in Docker (arm64). `HostMetrics` runs as in `packages/api/src/index.ts`, with an in-memory reader, and is collected twice.

| Check | `main` (5.30.7) | This branch (5.33.19) |
| :-- | :-- | :-- |
| `npm audit`, `systeminformation` advisories | 7 (5 high, 1 moderate, 1 low) | 0 |
| Linux: `networkInterfaces()` with `source /tmp/x;touch${IFS}/tmp/pwned-interfaces` in `/etc/network/interfaces` (GHSA-5xpp-75jx-m839) | runs `touch` | does not run it |
| Linux: `HostMetrics` with the same file | 10 metrics, 0 errors, `system.network.io` for `eth0`, `touch` not run | same |
| macOS: `HostMetrics` | 10 metrics, 0 errors | same |
| Result fields of `networkInterfaces()` and `networkStats()` (Linux and macOS) | | same as `main` |

I also logged every child process. On Linux, on both versions, `HostMetrics` runs only `ip route 2> /dev/null | grep default` and `cat /sys/class/net/eth0/...`. Prettier passes on `package.json` and the changeset.

Not run: Windows, the Docker image build, ESLint, `tsc` and `knip` (no source file or workspace dependency changed).

### How to reproduce

**Starting point:** a clone of this repo. Use the repo's Yarn (4.13.0).

**On `main`:**

1. Run `yarn why systeminformation`. It shows `systeminformation@npm:5.30.7 (via npm:^5.24.0)`. 5.30.7 is in the vulnerable range of GHSA-5xpp-75jx-m839 (`<= 5.31.6`) and GHSA-hvx9-hwr7-wjj9 (`>= 4.17.0, <= 5.31.5`).
2. To see the injection itself: in a `node:22-alpine` container with `systeminformation@5.30.7` installed, write `source /tmp/x;touch${IFS}/tmp/pwned-interfaces` to `/etc/network/interfaces`, then run `node -e "require('systeminformation/lib/network').networkInterfaces()"`. `/tmp/pwned-interfaces` is created.

**On this branch:**

1. Run `yarn why systeminformation`. It shows `systeminformation@npm:5.33.19 (via npm:^5.33.19)`.
2. Repeat step 2 with `systeminformation@5.33.19`. `/tmp/pwned-interfaces` is not created.

### How to test on Vercel preview

N/A — non-UI change.

### References

- Closes #3306
- Related: #1740 (added the resolution), #1803 (its `^5.31.0` bump did not reach `main`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge review/tier-4 Critical — deep review + domain expert sign-off

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants