denoland/deno · error · DOMException

Zero-length key is not supported

Error message

Zero-length key is not supported

What it means

SecretKeyObject.toCryptoKey exports the secret key's raw bytes and refuses zero-length material with a DataError DOMException, mirroring the WebCrypto rule that an imported secret must carry at least one byte. This guard runs before algorithm-specific checks (PBKDF2/HKDF/HMAC usage validation) and before importCryptoKeySync.

Source

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

      type: "NodeCryptoKeyObject",
      keyType: "secret",
      keyData: new Uint8Array(op_node_export_secret_key(this[kHandle])),
    };
  }

  toCryptoKey(
    algorithm: string | object,
    extractable: boolean,
    usages: string[],
  ): CryptoKey {
    const algName = typeof algorithm === "string"
      ? algorithm
      : (algorithm as { name: string }).name;

    const rawData = new Uint8Array(op_node_export_secret_key(this[kHandle]));

    if (TypedArrayPrototypeGetByteLength(rawData) === 0) {
      throw new DOMException(
        "Zero-length key is not supported",
        "DataError",
      );
    }

    if (algName === "PBKDF2") {
      if (extractable) {
        throw new DOMException(
          "PBKDF2 keys are not extractable",
          "SyntaxError",
        );
      }
      if (
        usages.length > 0 &&
        ArrayPrototypeSome(
          usages,
          (u: string) =>
            !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Validate byte length > 0 when creating the secret: if (!buf.length) throw ... at startup
  2. Treat empty secrets as configuration errors and refuse to boot
  3. Watch for Buffer.slice/subarray mistakes that produce zero-length views
  4. For AES keys also meet the algorithm minimum (16/24/32 bytes) — later checks enforce that separately

Example fix

// before: empty env value becomes a zero-length key
const secret = crypto.createSecretKey(Buffer.from(process.env.API_SECRET ?? ''));
const ck = secret.toCryptoKey('AES-GCM', false, ['encrypt']); // DataError

// after: fail fast on empty material
const raw = Buffer.from(process.env.API_SECRET ?? '', 'base64');
if (raw.length === 0) throw new Error('API_SECRET is empty');
const ck = crypto.createSecretKey(raw).toCryptoKey('AES-GCM', false, ['encrypt']);
Defensive patterns

Strategy: validation

Validate before calling

function toCryptoKey(secretKeyObject, alg, extractable, usages) {
  const raw = Buffer.from(secretKeyObject.export());
  if (raw.length === 0) {
    throw new Error('refusing to convert a zero-length secret key');
  }
  return secretKeyObject.toCryptoKey(alg, extractable, usages);
}

Type guard

function isNonEmptySecret(key: crypto.KeyObject): boolean {
  return key.type === 'secret' &&
    Buffer.from(key.export()).length > 0;
}

Try / catch

try {
  return secretKey.toCryptoKey(alg, extractable, usages);
} catch (err) {
  if (err instanceof DOMException && err.name === 'DataError' &&
      /Zero-length key/.test(err.message)) {
    throw new Error('Secret material is empty — check env/config source');
  }
  throw err;
}

Prevention

When it happens

Trigger: createSecretKey(Buffer.alloc(0)).toCryptoKey(alg, extractable, usages); a secret built from an empty string/env value (Buffer.from('') has length 0) then converted to a CryptoKey via KeyObject.toCryptoKey (used by webcrypto interop).

Common situations: An env var that exists but is empty (API_SECRET='') decoded to an empty buffer; slicing a buffer with wrong offsets yielding length 0; a default empty secret in staging config that nobody exercised; test fixtures with empty key material.

Related errors


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