Dependency Management
This document describes how Questarr selects, obtains, and tracks its dependencies.Selection
Dependencies are added deliberately as part of normal development, vianpm install, and land in package.json alongside the feature or fix that needs them. Preference is given to actively maintained, widely used packages already common in the Node/React ecosystem. New dependencies go through the same pull request review process as any other code change (see .github/CONTRIBUTING.md) before merging to main.
Obtaining dependencies
- Packages are installed from the public npm registry.
package-lock.jsonis committed to the repository and used for reproducible installs — the exact resolved version of every direct and transitive dependency is pinned.- The
packageManagerfield inpackage.jsonpins the npm version used to install and build the project. - The
allowScriptsfield inpackage.jsonexplicitly allowlists which packages are permitted to run install-time (postinstall) scripts. It’s npm’s own native field (npm ≥ 11.16.0), managed vianpm approve-scripts/npm deny-scripts, not a third-party tool — today it’s advisory (npm flags unreviewed scripts but still runs them), with a future npm release expected to block unapproved scripts by default. Any package added here should have a concrete reason (e.g. a native module that needs to compile a binary during install). - The
overridesfield forces a specific version of a transitive dependency when a direct dependency’s own declared range still permits a vulnerable release:socket.io-parser: 4.2.7patches CVE-2026-33151 (resource exhaustion via unbounded binary attachments, fixed in 4.2.6) and CVE-2026-69185 (zero-attachment memory exhaustion, fixed in 4.2.7). Needed becausesocket.io/socket.io-clientdeclaresocket.io-parser: ~4.2.4, a range that still allows the unpatched 4.2.4–4.2.6.engine.io: ^6.6.10patches GHSA-2gc4-cqfq-p2gv (DoS via an Engine.IO protocol revision mismatch). Needed becausesocket.io@4.8.3declaresengine.io: ~6.6.0, a range that still allows the unpatched 6.6.0–6.6.9.@esbuild-kit/core-utils’sesbuilddependency is bumped to^0.25.0to patch the esbuild dev-server request-forwarding issue (GHSA-67mh-4wv8-2f99). Needed becausedrizzle-kitpulls in@esbuild-kit/esm-loader→@esbuild-kit/core-utils, which pinsesbuild: ~0.18.20.body-parser: 1.20.6patches CVE-2026-12590 (invalidlimitvalues silently disabling size enforcement). Needed becauseexpressbundlesbody-parser: ~1.20.5.qs: ^6.16.0patches CVE-2026-82417 (DoS via attacker-controlledisBuffer, affecting>=2.2.5 <6.16.0) and CVE-2026-82562 (array-limit bypass via bracket-key comma parsing, affecting>=6.14.2 <=6.15.3). Needed becauseexpressandbody-parserboth pinqs: ~6.15.1, which excludes the patched6.16.0— npm’s only other remedy was a semver-major bump toexpress@5. The override also coversopenid,steam-webandsuperagent, whose own ranges likewise still permit a vulnerable release.brace-expansion: ^5.0.12patches GHSA-6j4f-fj2g-mc7p and GHSA-qhr7-859c-m2p7 (stack exhaustion via uncontrolled recursion, fixed in 5.0.10 and 5.0.11) and GHSA-q2hr-2g5m-vwhr (quadratic-time{a},b}rewrite, fixed in 5.0.12). It was previously pinned at^5.0.9, which patched CVE-2026-69152 (DoS via unbounded intermediate arrays, bypassing the CVE-2026-14257 mitigation). Needed becausearchiver→readdir-glob→minimatchwould resolve to a version below5.0.9without this override; the override was previously pinned at^5.0.8, which patched CVE-2026-14257 but was itself within the range vulnerable to CVE-2026-69152 (4.0.0–5.0.8). The same floor also covers CVE-2026-13149 (exponential-time expansion of consecutive{}groups, fixed in 5.0.8).fast-uri: ^3.1.8also patches GHSA-hrr3-gc8f-f4qj (inconsistent host case normalization via percent-encoded octets, fixed in 3.1.8). The earlier^3.1.7floor patched seven HIGH advisories: CVE-2026-16221 (fixed in 3.1.4), CVE-2026-18446 (backslash authority introducer, fixed in 3.1.5), CVE-2026-75931 and CVE-2026-76172 (host confusion via skipped IDN/percent-encoded scheme normalization), CVE-2026-75975 and CVE-2026-75899 (SSRF via malformed IPv6/repeated hostname percent-decoding normalization), and CVE-2026-84292 (authority injection via an unvalidated port inserialize). Needed becauseajv@8.20.0’s own declared range (^3.0.1, then^3.1.5) kept allowing an unpatched release.ajvis a shared transitive dependency of bothsecretlint(dev-only) and the production@hookform/resolvers(as apeerOptionaldependency), so despite earlier notes here,fast-uriis reachable in production — confirmed bynpm audit --omit=devflagging it directly.proxy-addr: ^2.0.8patches CVE-2026-90711 (AIKIDO-2026-101201, critical: accepts undersized IPv4-mapped IPv6 trust subnets, letting unauthenticated clients spoofX-Forwarded-Forand bypass IP-based access controls, rate limiting, and audit logging), affecting>=1.1.0 <=2.0.7. Needed becauseexpresspinsproxy-addr: ~2.0.7, which had not yet bumped its declared range to include the patched2.0.8.ip-address: ^10.7.2patches CVE-2026-101913 and CVE-2026-101910 (affecting<=10.5.0). Needed becauseexpress-rate-limit(^10.2.0) andsocks(^10.1.1) still allow a vulnerable release.node-gyp’sundicidependency is bumped to^6.29.0to patch CVE-2026-85024 (affecting6.25.0–6.28.0). Scoped tonode-gyp(reached via the dev-only@lizenz/checker) because the app’s own directundicidependency already carries the same fix on the 8.x line (8.10.2). Needed becausenode-gypdeclaresundici: ^6.25.0.eslint-plugin-react’seslintdependency is bumped to^10.9.1to allow ESLint v10 support. Needed becauseeslint-plugin-react@7.37.2officially supports only up toeslint@^9.7, but the linting rules in ESLint v10 are stricter and require updating the codebase to comply. This override is temporary — onceeslint-plugin-reactreleases a new major version with official ESLint v10 support, it can be removed.- All of the above should be revisited (and likely removed) once the upstream packages bump their own internal dependency ranges past the vulnerable versions.
- The
check-overridesCI job (npm run check:overrides, seescripts/check-overrides.mjs) checks this automatically on every PR and fails once an override is no longer needed, so there’s no need to track removal manually.
Tracking and updates
Dependabot is configured in.github/dependabot.yml to check for updates weekly (Monday) for both npm dependencies and GitHub Actions used in CI:
- Updates are opened as grouped pull requests (e.g. React-related packages, Radix UI components, dev vs. production dependencies, and all GitHub Actions bumps) to keep the PR volume manageable.
- Semver-major bumps are proposed automatically like any other update rather than excluded, since silently skipping them meant a major-version-only security fix could go unnoticed; they aren’t folded into the minor/patch groups, so they still land as their own PR and get individual review.
- Every dependency-update PR runs through the same CI gate as any other change — lint, type check, the full test suite, and a Docker build (see
.github/workflows/ci.yml) — before it can be merged.
Release-time visibility
Every published Docker image ships with a generated Software Bill of Materials listing the exact versions of every dependency included in that release. See docs/SBOM.md for how to inspect it.Currently blocked updates
Tracked here so a blocked Dependabot PR doesn’t get silently re-proposed and re-investigated from scratch. Remove an entry once its update is unblocked and merged. As of 2026-07-04,release/1.4.0:
@hookform/resolvers3.10.0→5.4.0(PR #756) — blocked. Installs, but the TypeScript check fails in form resolver usage (client/src/pages/downloaders.tsx,client/src/pages/indexers.tsx). The project is on Zod 3 (zod: ^3.25.0); this upgrade likely needs resolver/schema compatibility adjustments first.- React 19
react 18.3.1→19.2.7,@types/react 18.3.11→19.2.17(PR #761) — blocked. Install fails on peer dependency resolution across UI dependencies; needs a broader compatibility pass across the React ecosystem packages first.