denoland/deno · error · TypeError

Unsupported KeyObject type for structured clone: ${data.keyT

Error message

Unsupported KeyObject type for structured clone: ${data.keyType}

What it means

deserializeNodeCryptoKeyObject is the structured-clone deserializer that resurrects node:crypto KeyObjects across postMessage/structuredClone boundaries. It only recognizes keyType 'secret', 'public' and 'private' from the internal NodeCryptoKeyObject brand; any other value falls into the default branch and throws this TypeError. Hitting it means the serialized payload was not produced by a matching Deno KeyObject implementation — version mismatch, hand-crafted clone data, or corruption.

Source

Thrown at ext/node/polyfills/internal/crypto/keys.ts:1120

      const handle = op_node_create_public_key(
        data.keyData,
        "der",
        "spki",
        undefined,
      );
      return new PublicKeyObject(handle);
    }
    case "private": {
      const handle = op_node_create_private_key(
        data.keyData,
        "der",
        "pkcs8",
        undefined,
      );
      return new PrivateKeyObject(handle);
    }
    default:
      throw new TypeError(
        `Unsupported KeyObject type for structured clone: ${data.keyType}`,
      );
  }
}

return {
  getArrayBufferOrView,
  deserializeNodeCryptoKeyObject,
  KeyObject,
  kConsumePublic,
  kConsumePrivate,
  kCreatePublic,
  kCreatePrivate,
  createPrivateKey,
  createPublicKey,
  createSecretKey,
  prepareSecretKey,
  prepareAsymmetricKey,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Transfer raw key material instead: export on the sender (keyObject.export()) and reconstruct on the receiver with createSecretKey/createPublicKey/createPrivateKey
  2. Pin the main thread and all workers to the same runtime version
  3. Never persist or hand-build the internal NodeCryptoKeyObject clone shape — treat it as an implementation detail of the current Deno version

Example fix

// before
worker.postMessage(secretKeyObject); // relies on internal clone brand

// after
worker.postMessage({ kind: 'secret', raw: secretKeyObject.export() });
// in the worker:
const key = createSecretKey(msg.raw);
Defensive patterns

Strategy: validation

Validate before calling

// Before posting a key to a worker, send plain data and rebuild it there.
if (isKeyObject(value)) {
  worker.postMessage({
    kind: 'key',
    keyType: value.type,
    raw: value.type === 'secret' ? value.export() : value.export({ format: 'der', type: 'pkcs8' }),
  });
} else {
  worker.postMessage(value);
}

Type guard

const KNOWN_KEY_TYPES = new Set(['secret', 'public', 'private']);
function isCloneableNodeCryptoKeyObject(data: unknown): data is { keyType: string } {
  return typeof data === 'object' && data !== null &&
    KNOWN_KEY_TYPES.has((data as { keyType?: string }).keyType as string);
}

Try / catch

// inside the worker's message handler
self.onmessage = (ev) => {
  try {
    handle(ev.data);
  } catch (e) {
    if (e instanceof TypeError && /structured clone/.test(e.message)) {
      // sender/runtime mismatch: request the sender re-send raw key bytes
    } else throw e;
  }
};

Prevention

When it happens

Trigger: worker.postMessage(keyObject) where the receiving side runs a Deno build whose KeyObject brand differs; structuredClone over an object manually shaped like { type: 'NodeCryptoKeyObject', keyType: 'foo', keyData } ; restoring persisted structured-clone blobs after a runtime upgrade.

Common situations: Main thread and workers on different Deno versions (or Deno vs another runtime that emits a lookalike brand); persisting structured-clone output of KeyObjects and restoring it later; library code that wraps/spreads KeyObjects before sending them through the clone pipeline.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/1ba7e6a03f4b6018. Report an issue: GitHub.