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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BbJV5nm8KPVH1oNJYKpnoF
This commit is contained in:
Claude 2026-06-19 10:28:37 +00:00
parent 70b2ef8a69
commit e42853a046
No known key found for this signature in database
3 changed files with 36 additions and 14 deletions

View file

@ -11,13 +11,14 @@
"@aws-sdk/client-ec2": "^3.1072.0", "@aws-sdk/client-ec2": "^3.1072.0",
"@aws-sdk/client-sts": "^3.1072.0", "@aws-sdk/client-sts": "^3.1072.0",
"@fastify/cors": "^10.0.1", "@fastify/cors": "^10.0.1",
"@fastify/jwt": "^9.0.4", "@fastify/jwt": "^10.1.0",
"@types/ssh2": "^1.15.5", "@types/ssh2": "^1.15.5",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"better-sqlite3": "^11.8.1", "better-sqlite3": "^11.8.1",
"dotenv": "^16.6.1", "dotenv": "^16.6.1",
"fastify": "^5.2.1", "fastify": "^5.2.1",
"ssh2": "^1.17.0", "ssh2": "^1.17.0",
"undici": "^8.5.0",
"zod": "^3.24.1" "zod": "^3.24.1"
}, },
"devDependencies": { "devDependencies": {
@ -955,9 +956,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@fastify/jwt": { "node_modules/@fastify/jwt": {
"version": "9.1.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/@fastify/jwt/-/jwt-9.1.0.tgz", "resolved": "https://registry.npmjs.org/@fastify/jwt/-/jwt-10.1.0.tgz",
"integrity": "sha512-CiGHCnS5cPMdb004c70sUWhQTfzrJHAeTywt7nVw6dAiI0z1o4WRvU94xfijhkaId4bIxTCOjFgn4sU+Gvk43w==", "integrity": "sha512-U1y8ZbxoH1Pjon3euzPJmbCkuYBM+hrQlFWLQWvKmJGCNT6mVsAolnVJdEWfXeQOKpgmuRVCIsPll5RLZxj10A==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -970,10 +971,10 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fastify/error": "^4.0.0", "@fastify/error": "^4.2.0",
"@lukeed/ms": "^2.0.2", "@lukeed/ms": "^2.0.2",
"fast-jwt": "^5.0.0", "fast-jwt": "^6.2.0",
"fastify-plugin": "^5.0.0", "fastify-plugin": "^5.0.1",
"steed": "^1.1.3" "steed": "^1.1.3"
} }
}, },
@ -1619,15 +1620,16 @@
} }
}, },
"node_modules/fast-jwt": { "node_modules/fast-jwt": {
"version": "5.0.6", "version": "6.2.4",
"resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-5.0.6.tgz", "resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-6.2.4.tgz",
"integrity": "sha512-LPE7OCGUl11q3ZgW681cEU2d0d2JZ37hhJAmetCgNyW8waVaJVZXhyFF6U2so1Iim58Yc7pfxJe2P7MNetQH2g==", "integrity": "sha512-IoQa53wI6TbARU2yelb0L44ggFQnP2qVcwswCSYHbCAWuwpr70icDb3QjG0v01I8Tt01rVGDkN/rRvpk0lKFTA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@lukeed/ms": "^2.0.2", "@lukeed/ms": "^2.0.2",
"asn1.js": "^5.4.1", "asn1.js": "^5.4.1",
"ecdsa-sig-formatter": "^1.0.11", "ecdsa-sig-formatter": "^1.0.11",
"mnemonist": "^0.40.0" "mnemonist": "^0.40.0",
"safe-regex2": "^5.1.0"
}, },
"engines": { "engines": {
"node": ">=20" "node": ">=20"
@ -2538,6 +2540,15 @@
"node": ">=14.17" "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": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",

View file

@ -12,13 +12,14 @@
"@aws-sdk/client-ec2": "^3.1072.0", "@aws-sdk/client-ec2": "^3.1072.0",
"@aws-sdk/client-sts": "^3.1072.0", "@aws-sdk/client-sts": "^3.1072.0",
"@fastify/cors": "^10.0.1", "@fastify/cors": "^10.0.1",
"@fastify/jwt": "^9.0.4", "@fastify/jwt": "^10.1.0",
"@types/ssh2": "^1.15.5", "@types/ssh2": "^1.15.5",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"better-sqlite3": "^11.8.1", "better-sqlite3": "^11.8.1",
"dotenv": "^16.6.1", "dotenv": "^16.6.1",
"fastify": "^5.2.1", "fastify": "^5.2.1",
"ssh2": "^1.17.0", "ssh2": "^1.17.0",
"undici": "^8.5.0",
"zod": "^3.24.1" "zod": "^3.24.1"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,3 +1,4 @@
import { Agent } from 'undici'
import type { IntegrationAdapter, Resource } from './types.js' import type { IntegrationAdapter, Resource } from './types.js'
interface ProxmoxResourceEntry { interface ProxmoxResourceEntry {
@ -12,6 +13,9 @@ function authHeader(apiKey: string): Record<string, string> {
return { Authorization: `PVEAPIToken=${apiKey}` } 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 = { export const proxmox: IntegrationAdapter = {
async testConnection(config, secrets) { async testConnection(config, secrets) {
const baseUrl = config.baseUrl?.replace(/\/$/, '') const baseUrl = config.baseUrl?.replace(/\/$/, '')
@ -19,7 +23,10 @@ export const proxmox: IntegrationAdapter = {
if (!baseUrl) return { ok: false, message: 'Missing baseUrl' } if (!baseUrl) return { ok: false, message: 'Missing baseUrl' }
if (!apiKey) return { ok: false, message: 'Missing API token' } if (!apiKey) return { ok: false, message: 'Missing API token' }
try { 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}` } if (!res.ok) return { ok: false, message: `HTTP ${res.status}` }
return { ok: true, message: 'Connected' } return { ok: true, message: 'Connected' }
} catch (err) { } catch (err) {
@ -31,7 +38,10 @@ export const proxmox: IntegrationAdapter = {
const baseUrl = config.baseUrl?.replace(/\/$/, '') const baseUrl = config.baseUrl?.replace(/\/$/, '')
const apiKey = secrets.apiKey const apiKey = secrets.apiKey
if (!baseUrl || !apiKey) return [] 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 [] if (!res.ok) return []
const body = (await res.json()) as { data: ProxmoxResourceEntry[] } const body = (await res.json()) as { data: ProxmoxResourceEntry[] }
return body.data.map((entry) => ({ return body.data.map((entry) => ({