Summary
pkijs's crypto-engine registry stores its state at global[process.pid] (in initCryptoEngine / setEngine / getEngine). On runtimes where a process object exists but process.pid is undefined — notably current Supabase Edge Functions (Deno-based, exposing a partial process shim) — the property key evaluates to the string "undefined":
global[process.pid] = ... // → global["undefined"] = ...
globalThis.undefined is a non-writable, non-configurable property of every global object, so the assignment throws:
TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'
Because this happens at module load time, merely import-ing pkijs (directly or transitively) kills the whole isolate before any user code runs.
Impact
- Affects 3.3.3 and 3.4.0 identically (verified against the published tarballs — the
process.pid guard is unchanged between them).
- On 2026-08-14 this took down three production Supabase edge functions at our org (plus two latent importers) for ~44 minutes. Nothing in our code had changed — a redeploy simply landed on a runtime revision where
process.pid was undefined.
- Subtle failure mode: in that environment pkijs loads as CJS with its own injected
global, while ES-module code sees a different global shape — so naive attempts to patch globalThis from application code silently do nothing. The only reliable workaround is defining a numeric own process.pid before pkijs loads.
Minimal repro
Simulated in plain Node (mirrors what the Deno shim exposes) — verified against the published pkijs@3.4.0 tarball:
// repro.mjs — node >= 18
Object.defineProperty(process, "pid", { value: undefined, configurable: true });
await import("pkijs"); // TypeError: Cannot assign to read only property 'undefined'
Output:
TypeError: Cannot assign to read only property 'undefined' of object '#<Object>'
The same script with an unmodified process.pid imports cleanly. The offending lines in the published build (node_modules/pkijs/build/index.js:6794-6799):
if (typeof global[process.pid] === "undefined") {
global[process.pid] = {};
}
else {
if (typeof global[process.pid] !== "object") {
throw new Error(`Name global.${process.pid} already exists and it is not an object`);
On an actual Supabase Edge Function, a bare import * as pkijs from "npm:pkijs"; is sufficient to crash the isolate at boot when the runtime reports no pid.
Suggested fix
In the engine registry, don't trust process.pid to be a usable key:
const engineKey =
typeof process !== "undefined" && Number.isInteger(process?.pid)
? process.pid
: Symbol.for("pkijs.engine"); // fixed fallback key
(or any fixed string/symbol fallback). This keeps the current per-pid behavior on Node while making import safe on runtimes with a partial process shim.
Workaround for anyone hitting this
Define a numeric own process.pid before pkijs is imported. Our shim: supabase/functions/_shared/pkijsEdgeRuntimeShim.ts (introduced in ninth-planet/ninth-planet-publishing#626).
Happy to open a PR for the fallback-key fix if the approach is acceptable.
Summary
pkijs's crypto-engine registry stores its state at
global[process.pid](ininitCryptoEngine/setEngine/getEngine). On runtimes where aprocessobject exists butprocess.pidisundefined— notably current Supabase Edge Functions (Deno-based, exposing a partialprocessshim) — the property key evaluates to the string"undefined":globalThis.undefinedis a non-writable, non-configurable property of every global object, so the assignment throws:Because this happens at module load time, merely
import-ing pkijs (directly or transitively) kills the whole isolate before any user code runs.Impact
process.pidguard is unchanged between them).process.pidwasundefined.global, while ES-module code sees a different global shape — so naive attempts to patchglobalThisfrom application code silently do nothing. The only reliable workaround is defining a numeric ownprocess.pidbefore pkijs loads.Minimal repro
Simulated in plain Node (mirrors what the Deno shim exposes) — verified against the published
pkijs@3.4.0tarball:Output:
The same script with an unmodified
process.pidimports cleanly. The offending lines in the published build (node_modules/pkijs/build/index.js:6794-6799):On an actual Supabase Edge Function, a bare
import * as pkijs from "npm:pkijs";is sufficient to crash the isolate at boot when the runtime reports no pid.Suggested fix
In the engine registry, don't trust
process.pidto be a usable key:(or any fixed string/symbol fallback). This keeps the current per-pid behavior on Node while making import safe on runtimes with a partial
processshim.Workaround for anyone hitting this
Define a numeric own
process.pidbefore pkijs is imported. Our shim:supabase/functions/_shared/pkijsEdgeRuntimeShim.ts(introduced in ninth-planet/ninth-planet-publishing#626).Happy to open a PR for the fallback-key fix if the approach is acceptable.