From e42853a0463cdd490aad66f2f3db932e8171c06e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 10:28:37 +0000 Subject: [PATCH] Allow self-signed TLS for Proxmox and fix critical fast-jwt vuln Proxmox ships with a self-signed cert by default, which Node's fetch rejected outright; route Proxmox requests through an undici Agent with rejectUnauthorized disabled so real Proxmox hosts can be connected. Also bump @fastify/jwt to v10, which pulls in a patched fast-jwt and resolves the critical advisories (crit-header bypass, algorithm confusion, cache collision, ReDoS, empty-HMAC-secret auth bypass) that npm audit flagged on the old v9/fast-jwt<=6.2.3 pairing. Verified auth still works end-to-end (setup, valid token, rejected bad token) after the upgrade; npm audit now reports 0 vulnerabilities. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF --- backend/package-lock.json | 33 +++++++++++++++++++---------- backend/package.json | 3 ++- backend/src/integrations/proxmox.ts | 14 ++++++++++-- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index b76b1d8..4ca5241 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -11,13 +11,14 @@ "@aws-sdk/client-ec2": "^3.1072.0", "@aws-sdk/client-sts": "^3.1072.0", "@fastify/cors": "^10.0.1", - "@fastify/jwt": "^9.0.4", + "@fastify/jwt": "^10.1.0", "@types/ssh2": "^1.15.5", "bcryptjs": "^2.4.3", "better-sqlite3": "^11.8.1", "dotenv": "^16.6.1", "fastify": "^5.2.1", "ssh2": "^1.17.0", + "undici": "^8.5.0", "zod": "^3.24.1" }, "devDependencies": { @@ -955,9 +956,9 @@ "license": "MIT" }, "node_modules/@fastify/jwt": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@fastify/jwt/-/jwt-9.1.0.tgz", - "integrity": "sha512-CiGHCnS5cPMdb004c70sUWhQTfzrJHAeTywt7nVw6dAiI0z1o4WRvU94xfijhkaId4bIxTCOjFgn4sU+Gvk43w==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@fastify/jwt/-/jwt-10.1.0.tgz", + "integrity": "sha512-U1y8ZbxoH1Pjon3euzPJmbCkuYBM+hrQlFWLQWvKmJGCNT6mVsAolnVJdEWfXeQOKpgmuRVCIsPll5RLZxj10A==", "funding": [ { "type": "github", @@ -970,10 +971,10 @@ ], "license": "MIT", "dependencies": { - "@fastify/error": "^4.0.0", + "@fastify/error": "^4.2.0", "@lukeed/ms": "^2.0.2", - "fast-jwt": "^5.0.0", - "fastify-plugin": "^5.0.0", + "fast-jwt": "^6.2.0", + "fastify-plugin": "^5.0.1", "steed": "^1.1.3" } }, @@ -1619,15 +1620,16 @@ } }, "node_modules/fast-jwt": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-5.0.6.tgz", - "integrity": "sha512-LPE7OCGUl11q3ZgW681cEU2d0d2JZ37hhJAmetCgNyW8waVaJVZXhyFF6U2so1Iim58Yc7pfxJe2P7MNetQH2g==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-6.2.4.tgz", + "integrity": "sha512-IoQa53wI6TbARU2yelb0L44ggFQnP2qVcwswCSYHbCAWuwpr70icDb3QjG0v01I8Tt01rVGDkN/rRvpk0lKFTA==", "license": "Apache-2.0", "dependencies": { "@lukeed/ms": "^2.0.2", "asn1.js": "^5.4.1", "ecdsa-sig-formatter": "^1.0.11", - "mnemonist": "^0.40.0" + "mnemonist": "^0.40.0", + "safe-regex2": "^5.1.0" }, "engines": { "node": ">=20" @@ -2538,6 +2540,15 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", + "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/backend/package.json b/backend/package.json index 6a61b30..3c8f9c4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,13 +12,14 @@ "@aws-sdk/client-ec2": "^3.1072.0", "@aws-sdk/client-sts": "^3.1072.0", "@fastify/cors": "^10.0.1", - "@fastify/jwt": "^9.0.4", + "@fastify/jwt": "^10.1.0", "@types/ssh2": "^1.15.5", "bcryptjs": "^2.4.3", "better-sqlite3": "^11.8.1", "dotenv": "^16.6.1", "fastify": "^5.2.1", "ssh2": "^1.17.0", + "undici": "^8.5.0", "zod": "^3.24.1" }, "devDependencies": { diff --git a/backend/src/integrations/proxmox.ts b/backend/src/integrations/proxmox.ts index e67b3b3..8ccaf10 100644 --- a/backend/src/integrations/proxmox.ts +++ b/backend/src/integrations/proxmox.ts @@ -1,3 +1,4 @@ +import { Agent } from 'undici' import type { IntegrationAdapter, Resource } from './types.js' interface ProxmoxResourceEntry { @@ -12,6 +13,9 @@ function authHeader(apiKey: string): Record { return { Authorization: `PVEAPIToken=${apiKey}` } } +// Proxmox ships with a self-signed cert by default, which Node's fetch rejects out of the box. +const insecureDispatcher = new Agent({ connect: { rejectUnauthorized: false } }) + export const proxmox: IntegrationAdapter = { async testConnection(config, secrets) { const baseUrl = config.baseUrl?.replace(/\/$/, '') @@ -19,7 +23,10 @@ export const proxmox: IntegrationAdapter = { if (!baseUrl) return { ok: false, message: 'Missing baseUrl' } if (!apiKey) return { ok: false, message: 'Missing API token' } try { - const res = await fetch(`${baseUrl}/api2/json/version`, { headers: authHeader(apiKey) }) + const res = await fetch(`${baseUrl}/api2/json/version`, { + headers: authHeader(apiKey), + dispatcher: insecureDispatcher, + } as RequestInit) if (!res.ok) return { ok: false, message: `HTTP ${res.status}` } return { ok: true, message: 'Connected' } } catch (err) { @@ -31,7 +38,10 @@ export const proxmox: IntegrationAdapter = { const baseUrl = config.baseUrl?.replace(/\/$/, '') const apiKey = secrets.apiKey if (!baseUrl || !apiKey) return [] - const res = await fetch(`${baseUrl}/api2/json/cluster/resources?type=vm`, { headers: authHeader(apiKey) }) + const res = await fetch(`${baseUrl}/api2/json/cluster/resources?type=vm`, { + headers: authHeader(apiKey), + dispatcher: insecureDispatcher, + } as RequestInit) if (!res.ok) return [] const body = (await res.json()) as { data: ProxmoxResourceEntry[] } return body.data.map((entry) => ({