Skip to content

Comments

fix: replace deprecated csurf and native-url packages#4505

Closed
Copilot wants to merge 5 commits intomasterfrom
copilot/update-deprecated-packages
Closed

fix: replace deprecated csurf and native-url packages#4505
Copilot wants to merge 5 commits intomasterfrom
copilot/update-deprecated-packages

Conversation

Copy link
Contributor

Copilot AI commented Dec 29, 2025

Removes npm deprecation warnings for [email protected] (archived) and [email protected] (transitive dependency via native-url).

Changes

CSRF Protection - Replace csurf with csrf-sync

  • Migrates to Synchronizer Token pattern (session-based CSRF protection)
  • Adds /api/csrf-token endpoint to generate CSRF tokens for frontend
  • Updates frontend with axios interceptors to automatically fetch and include CSRF tokens
  • Updates /api/authenticate and /api/password endpoints to use csrfSynchronisedProtection
  • Moves session middleware before history middleware for proper CSRF flow
  • Sets saveUninitialized: true to allow CSRF tokens for unauthenticated users
// Before
import csrf from 'csurf'
const csrfProtection = csrf({ value: (req) => req.csrfToken() })

// After
import { csrfSync } from 'csrf-sync'
const { csrfSynchronisedProtection, generateToken } = csrfSync({
  getTokenFromRequest: (req) => req.body._csrf || req.query._csrf || req.headers['x-csrf-token']
})

// New endpoint for frontend
app.get('/api/csrf-token', (req, res) => {
  const token = generateToken(req)
  res.json({ token })
})

Frontend CSRF Handling - Automatic token management in src/apis/ConfigApis.js

  • Axios interceptor fetches CSRF token before state-changing requests
  • Token automatically included in x-csrf-token header
  • Auto-retry with refreshed token on 403 errors

URL Parsing - Replace native-url with Node.js built-in URL API

  • Updates api/lib/MqttClient.ts and src/views/Settings.vue to use new URL()
  • Removes patches/native-url+0.3.4.patch
// Before
import url from 'native-url'
const parsed = url.parse(config.host || '')

// After
try {
  const parsed = new URL(config.host || '')
  protocol = parsed.protocol.replace(/:$/, '')
  hostname = parsed.hostname
} catch {
  hostname = config.host
}

Cleanup

  • Removed patch-package npm module (no longer needed)
  • Removed postinstall script from package.json

Dependencies

Removed: csurf, @types/csurf, native-url, patch-package
Added: [email protected] (no vulnerabilities)

Testing

  • ✅ All 103 tests pass (51 backend + 52 frontend)
  • ✅ Build succeeds
  • ✅ Linting passes
  • ✅ No deprecation warnings during npm install
  • ✅ CSRF token endpoint returns valid tokens
  • ✅ Login flow works correctly with CSRF protection
Original prompt

This section details on the original issue you should resolve

<issue_title>[npm WARN deprecated] [email protected] and [email protected] during global install</issue_title>
<issue_description>Hi zwave-js-ui team,

when installing the latest zwave-js-ui globally via npm, I see the following warnings:

npm WARN deprecated [email protected]: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.

It seems csurf and querystring are dependencies of your project or one of its sub-dependencies. While the warnings do not prevent installation or operation, it might be good to consider updating or replacing these deprecated packages in a future release.

Thank you for your work and support!</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

- Replace csurf with csrf-csrf for CSRF protection
- Use Node.js built-in URL API instead of native-url
- Remove @types/csurf and native-url+0.3.4.patch
- Update CSRF middleware to use Double Submit Cookie pattern
- Add cookie-parser middleware for CSRF protection
- Update MqttClient.ts to use URL constructor
- Update Settings.vue to use URL constructor

This resolves the npm deprecation warnings during installation.

Co-authored-by: robertsLando <[email protected]>
@robertsLando robertsLando requested a review from Copilot December 29, 2025 16:31
Copilot AI changed the title [WIP] Update deprecated packages csurf and querystring fix: replace deprecated csurf and native-url packages Dec 29, 2025
Copilot AI requested a review from robertsLando December 29, 2025 16:32
@robertsLando robertsLando marked this pull request as ready for review December 29, 2025 16:33
},
) as RequestHandler,
)
app.use(cookieParser())

Check failure

Code scanning / CodeQL

Missing CSRF middleware High

This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.
This cookie middleware is serving a
request handler
without CSRF protection.

Copilot Autofix

AI about 2 months ago

In general, to fix this issue you should ensure that any state‑changing route that relies on cookie‑based authentication is protected by CSRF middleware. This typically means adding a CSRF protection middleware globally for sensitive routes or per‑route for each POST, PUT, and DELETE endpoint that uses session cookies. In this codebase, doubleCsrf from csrf-csrf is already imported, and some routes (/api/authenticate, /api/password) use doubleCsrfProtection, so the best fix is to consistently apply this existing middleware to the remaining state‑changing, authenticated routes.

The single best fix with minimal functional impact is:

  1. Leave the existing cookieParser and body parsers as they are.
  2. Keep using the existing doubleCsrfProtection middleware instance (assumed to be defined earlier in api/app.ts since it’s imported and already used).
  3. Add doubleCsrfProtection into the middleware chain for all authenticated, state‑changing routes that currently lack CSRF protection. That includes the following routes in api/app.ts:
    • POST /api/restart
    • POST /api/statistics
    • POST /api/versions
    • POST /api/importConfig
    • PUT /api/store
    • DELETE /api/store
    • PUT /api/store-multi
    • POST /api/store-multi
    • POST /api/store/upload
    • POST /api/debug/start
    • POST /api/debug/stop
    • POST /api/debug/cancel
  4. Preserve middleware order such that doubleCsrfProtection runs before isAuthenticated (or at least before the route handler). In the existing code, doubleCsrfProtection comes before isAuthenticated on /api/password, so we should follow the same pattern for consistency.

No new imports or helper methods are required; we just add doubleCsrfProtection as an extra middleware argument on the listed routes.


Suggested changeset 1
api/app.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/api/app.ts b/api/app.ts
--- a/api/app.ts
+++ b/api/app.ts
@@ -1367,6 +1367,7 @@
 app.post(
 	'/api/restart',
 	apisLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		try {
@@ -1415,6 +1416,7 @@
 app.post(
 	'/api/statistics',
 	apisLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		try {
@@ -1461,6 +1463,7 @@
 app.post(
 	'/api/versions',
 	apisLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		try {
@@ -1510,6 +1513,7 @@
 app.post(
 	'/api/importConfig',
 	apisLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		let config = req.body.data
@@ -1622,8 +1626,13 @@
 	}
 })
 
-app.put('/api/store', storeLimiter, isAuthenticated, async function (req, res) {
-	try {
+app.put(
+	'/api/store',
+	storeLimiter,
+	doubleCsrfProtection,
+	isAuthenticated,
+	async function (req, res) {
+		try {
 		const reqPath = getSafePath(req)
 
 		const isNew = req.query.isNew === 'true'
@@ -1656,6 +1665,7 @@
 app.delete(
 	'/api/store',
 	storeLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		try {
@@ -1675,6 +1685,7 @@
 app.put(
 	'/api/store-multi',
 	storeLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		try {
@@ -1693,6 +1704,7 @@
 app.post(
 	'/api/store-multi',
 	storeLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		const files = req.body.files || []
@@ -1756,6 +1768,7 @@
 app.post(
 	'/api/store/upload',
 	storeLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		let file: any
@@ -1813,6 +1826,7 @@
 app.post(
 	'/api/debug/start',
 	apisLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		try {
@@ -1846,6 +1860,7 @@
 app.post(
 	'/api/debug/stop',
 	apisLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		try {
@@ -1883,6 +1898,7 @@
 app.post(
 	'/api/debug/cancel',
 	apisLimiter,
+	doubleCsrfProtection,
 	isAuthenticated,
 	async function (req, res) {
 		try {
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
@coveralls
Copy link

Pull Request Test Coverage Report for Build 20577551083

Details

  • 0 of 33 (0.0%) changed or added relevant lines in 2 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.02%) to 19.512%

Changes Missing Coverage Covered Lines Changed/Added Lines %
api/lib/MqttClient.ts 0 14 0.0%
api/app.ts 0 19 0.0%
Totals Coverage Status
Change from base Build 20394770530: -0.02%
Covered Lines: 4051
Relevant Lines: 22030

💛 - Coveralls

Copy link
Member

@robertsLando robertsLando left a comment

Choose a reason for hiding this comment

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

When I try to login from UI I get this error:

ForbiddenError: invalid csrf token
    at doubleCsrf (file:///home/daniel/GitProjects/zwave-js-ui/node_modules/csrf-csrf/dist/index.js:30:33)
    at file:///home/daniel/GitProjects/zwave-js-ui/api/app.ts:467:34
    at ModuleJob.run (node:internal/modules/esm/module_job:345:25)
    at onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:665:26)
    at asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)

Also now that you removed the only patch available you could also remove the patch-package npm module from dependencies (remember to also remove postinstall script).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses deprecation warnings by replacing two deprecated npm packages: csurf (archived CSRF protection) with csrf-csrf, and native-url (which depends on deprecated querystring) with Node.js's built-in URL API. The changes successfully remove deprecation warnings but introduce critical bugs in the CSRF protection implementation that will prevent it from functioning correctly.

Key Changes

  • Replaced csurf with csrf-csrf for CSRF protection using the Double Submit Cookie pattern
  • Migrated from native-url to Node.js built-in URL API for URL parsing in both backend and frontend
  • Removed deprecated package dependencies and associated type definitions

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
api/app.ts Replaced csurf with csrf-csrf middleware, added cookie-parser configuration, updated CSRF protection for authenticate and password endpoints
api/lib/MqttClient.ts Replaced native-url with Node.js URL API for parsing MQTT broker URLs with protocol/hostname extraction
src/views/Settings.vue Replaced native-url with URL API for detecting secure MQTT protocols (mqtts/tls)
package.json Removed csurf and native-url dependencies, added csrf-csrf dependency
package-lock.json Updated lock file reflecting dependency changes and transitive dependency removals
patches/native-url+0.3.4.patch Removed patch file no longer needed after removing native-url dependency

)

const parsed = url.parse(config.host || '')
let parsed: URL | null = null
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The variable parsed is declared on line 353 but is never used after the try-catch block. It's only used to extract protocol and hostname which are then stored in separate variables. Since parsed serves no further purpose after the extraction, this is unnecessary variable declaration.

Consider removing the parsed variable and directly extracting the values within the try block without storing the URL object.

Copilot uses AI. Check for mistakes.
api/app.ts Outdated
sameSite: 'strict',
path: '/',
secure: !!process.env.HTTPS || !!process.env.USE_SECURE_COOKIE,
httpOnly: true,
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The CSRF cookie is configured with httpOnly: true, which prevents JavaScript from reading the cookie value. The csrf-csrf library uses the Double Submit Cookie pattern, which requires the client to read the cookie and send its value in a request header (typically x-csrf-token).

With httpOnly: true, client-side JavaScript cannot access the cookie to extract the token, breaking the CSRF protection mechanism. The cookie should either:

  1. Have httpOnly: false to allow JavaScript to read it, OR
  2. You need to destructure generateToken from the doubleCsrf call and create a GET endpoint (e.g., /api/csrf-token) that calls generateToken(req, res) to provide the token to clients, while keeping cookies httpOnly

The typical pattern with csrf-csrf is:

  • Keep cookies httpOnly: false for double-submit pattern, OR
  • Use signed/encrypted cookies with httpOnly: true and provide a token generation endpoint

Copilot uses AI. Check for mistakes.
api/app.ts Outdated
const { doubleCsrfProtection } = doubleCsrf({
getSecret: () => sessionSecret,
getSessionIdentifier: (req) => req.session?.user?.username || '',
cookieName: '__Host-psifi.x-csrf-token',
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The cookie name __Host-psifi.x-csrf-token uses the __Host- prefix, which requires specific conditions to work:

  1. The cookie must be set with the Secure attribute (only works over HTTPS)
  2. The cookie must be set with Path=/
  3. The cookie must NOT have a Domain attribute

In development environments or HTTP-only deployments where secure is false (line 623), browsers will reject cookies with the __Host- prefix. This will cause CSRF protection to fail silently in non-HTTPS environments.

Consider either:

  1. Using a regular cookie name like x-csrf-token without the __Host- prefix
  2. Using the __Host- prefix only when secure is true, otherwise use a regular name
  3. Documenting that CSRF protection requires HTTPS to function properly

Copilot uses AI. Check for mistakes.
- Replace csrf-csrf with csrf-sync (Synchronizer Token Pattern)
- Add /api/csrf-token endpoint to generate CSRF tokens
- Update frontend to automatically fetch and include CSRF tokens
- Move session middleware before history middleware for proper CSRF flow
- Set saveUninitialized: true to allow CSRF tokens for unauthenticated users
- Remove patch-package dependency and postinstall script (no patches remaining)
- Update axios interceptors to handle CSRF tokens automatically

Co-authored-by: robertsLando <[email protected]>
Copy link
Contributor Author

Copilot AI commented Dec 29, 2025

When I try to login from UI I get this error:

Fixed in c7a9a20.

The issue was that I initially used csrf-csrf which implements the Double Submit Cookie pattern. However, since this app uses session-based authentication (express-session), the Synchronizer Token Pattern is more appropriate.

Changes made:

  • Replaced csrf-csrf with csrf-sync
  • Added /api/csrf-token endpoint that returns CSRF tokens
  • Updated frontend with axios interceptors to automatically fetch and include CSRF tokens in requests
  • Moved session middleware before history middleware to ensure proper CSRF flow
  • Removed patch-package dependency and postinstall script as requested

The login flow now works correctly with CSRF protection.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • 224.0.0.251
    • Triggering command: REDACTED, pid is -1 (packet block)

If you need me to access, download, or install something from one of these locations, you can either:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[npm WARN deprecated] [email protected] and [email protected] during global install

3 participants