denoland/deno · error · TypeError

Illegal invocation

Error message

Illegal invocation

What it means

The internal SecureContext object returned by tls.createSecureContext installs an _external property whose getter only works when called on an object branded via a private WeakSet (ext/node/polyfills/_tls_common.ts:636). Calling the getter with a tampered this — via .call(), Reflect.get with a receiver, or a detached getter — throws TypeError('Illegal invocation'), the same WebIDL-style brand check browsers use.

Source

Thrown at ext/node/polyfills/_tls_common.ts:636

      ca: useDefaultCA ? undefined : normalizeCertValue(effectiveCa),
      useDefaultCA,
      cert: normalizeCertPem(options.cert) ?? pfxCert,
      key: normalizeKeyPem(options.key, options.passphrase) ?? pfxKey,
      minVersion,
      maxVersion,
      ciphers: options.ciphers,
      passphrase: options.passphrase,
      sigalgs: options.sigalgs,
      ecdhCurve: options.ecdhCurve,
    };
    secureContextBrand.add(this.context);
    ObjectDefineProperty(this.context, "_external", {
      __proto__: null,
      configurable: true,
      enumerable: false,
      get(this: object) {
        if (!WeakSetPrototypeHas(secureContextBrand, this)) {
          throw new TypeError("Illegal invocation");
        }
        return this;
      },
    });
    (this.context as any).setOptions = function setOptions(
      this: object,
      _options?: number,
    ) {
      if (!WeakSetPrototypeHas(secureContextBrand, this)) {
        throw new TypeError("Illegal invocation");
      }
    };
    (this.context as any).addCACert = function addCACert(
      this: any,
      cert: any,
    ) {
      if (!WeakSetPrototypeHas(secureContextBrand, this)) {
        throw new TypeError("Illegal invocation");

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Read the property normally (ctx._external) instead of extracting the getter and changing its receiver
  2. If you extracted the descriptor, invoke it bound to the original object: const d = Object.getOwnPropertyDescriptor(ctx, '_external'); d.get.call(ctx)
  3. In mocks/spies, wrap the original object rather than re-binding its accessors to a fake this

Example fix

// before
const desc = Object.getOwnPropertyDescriptor(ctx, '_external');
const ext = desc.get.call(myFakeThis); // TypeError

// after
const ext = ctx._external; // brand check passes
Defensive patterns

Strategy: validation

Validate before calling

// Never extract the getter; read the property on the branded object only.
const ext = ctx._external;

Try / catch

try { Reflect.get(target, '_external', receiver); } catch (e) { if (e instanceof TypeError && e.message === 'Illegal invocation') receiver = target; /* retry with correct receiver */ else throw e; }

Prevention

When it happens

Trigger: Reflect.get(ctx, '_external', someOtherObject), Object.getOwnPropertyDescriptor(ctx, '_external').get.call({}), or destructuring the property off a Proxy that forwards the receiver. Normal property reads like ctx._external never throw.

Common situations: Test suites or mocking libraries (sinon, test spies) that grab property descriptors and invoke getters with a stub receiver; code that copies accessors between objects with Object.assign on the descriptor level; deep-clone tools that re-invoke getters.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/1aa6cee88ea4f0a5. Report an issue: GitHub.