Latent mTLS trap — the
getHttpsAgent()pattern in vuer_oss is a silent no-opEvery
fetch()call invuer_osspasses a node-fetch-styleagent: new https.Agent({...}), but these are Node’s global fetch (undici-based). Global fetch ignoresagententirely — it only honorsdispatcher. The pattern is harmless today only becausecv.rejectUnauthorizeddefaults totrue(same as global fetch’s default). Any real attempt to send client certificates (mTLS) or to disable verification viaagentis silently dropped.
Discovered while scoping FKITDEV-8947 (migrate the UniCredit customization service off request-promise-native to fetch). All facts below were empirically verified on Node v22.22.3 (bundled undici 6.24.1).
The trap
In vuer_oss, the getHttpsAgent() idiom builds an https.Agent and passes it to fetch as agent. Call sites include:
server/cv/visions/*/*Api.jsserver/service/VuerCVService.jsserver/ocr/engines/*server/faceRecognition/engine/VuerCVFaceRecognitionEngine.jscustomization/api/sms/SmsApiSmsApiCom.js
For Agents
Neither
undicinornode-fetchis a declared dependency of vuer_oss. Therefore every one of thesefetch()calls resolves to Node’s global fetch (undici under the hood), and theagent:option is meaningless to it.
Verified facts
-
Global fetch ignores
agent. Node’s global (undici) fetch honors onlydispatcher, never the node-fetch-styleagent. Proof:fetch(selfSignedUrl, { agent: new https.Agent({ rejectUnauthorized: false }) })FAILED withTypeError: fetch failed→cause: DEPTH_ZERO_SELF_SIGNED_CERT. The agent’srejectUnauthorized: falsewas not applied. -
So
getHttpsAgent()is a silent no-op. It works only by accident:cv.rejectUnauthorizeddefaults totrue, which is identical to global fetch’s default behavior. Disabling verification, or supplying a client cert/key (mTLS), throughagentdoes nothing. -
The fix is to use the
undicipackage’s ownfetch+Agenttogether (version-matched within the package):const { fetch, Agent } = require('undici') const r = await fetch(url, { dispatcher: new Agent({ connect: { cert, key, ca, passphrase, rejectUnauthorized } }) })Proof: against a local mTLS server (
requestCert), this returned status 200 and the server confirmed it received the client certificate (peer subject CN present).
Cross-version gotcha (why undici’s own fetch, not a standalone Agent)
Do NOT feed a standalone-undici
Agentinto Node's GLOBAL fetchMixing Node’s global fetch (bundled undici 6.24.1) with a
dispatcherfrom a different-major standalone undici (tested 8.5.0) FAILS withUND_ERR_INVALID_ARG: invalid onRequestStart method— a handler-interface mismatch between undici majors.
Because Facekom deploys on both Node 22 and Node 24 (which bundle different undici majors), pinning a standalone undici to “exactly the bundled version” is fragile and breaks across the 22/24 split. Use the undici package’s own fetch paired with its own Agent — that pair is self-consistent regardless of which undici Node bundles.
Why this matters for FKITDEV-8947
The ticket migrates customization/server/service/UniCredit/ApiService.js (branch customization/unicredit, class UniCreditApiService) off request-promise-native. That service uses mTLS — cert / key / ca / passphrase merged from the portal.api config — so a naive agent:-style migration would silently break its TLS (client cert never sent). It must use the undici.fetch + undici.Agent pattern above.
Minor, ticket-specific (not part of the gotcha)
The working tree of that file currently has an uncommitted stray
}wtypo at line 267 (HEAD is clean) causing aReferenceErrorin thenon-realtime-sdkbranch ofsetIdentificationComplete.
Related
- FKITDEV-8947 — the scoping work that surfaced this; UniCredit
request-promise-native→ fetch migration (mTLS). - vuer_oss — the service whose
getHttpsAgent()pattern is the silent no-op.