facebook/react · error · Error

To taint a value, a lifetime must be defined by passing an o

Error message

To taint a value, a lifetime must be defined by passing an object that holds the value.

What it means

React's taintUniqueValue requires a lifetime object — an object or function that holds the secret and stays alive exactly as long as the secret does — so React can forget the taint via weak semantics when the holder is collected. Passing null or a primitive (string, number, boolean) as the lifetime throws because React would either never clean up or track the wrong scope.

Source

Thrown at packages/react/src/ReactTaint.js:70

    ? new FinalizationRegistry(cleanup)
    : null;

export function taintUniqueValue(
  message: ?string,
  lifetime: Reference,
  value: string | bigint | $ArrayBufferView,
): void {
  if (!enableTaint) {
    throw new Error('Not implemented.');
  }
  // eslint-disable-next-line react-internal/safe-string-coercion
  message = '' + (message || defaultMessage);
  if (
    // $FlowFixMe[invalid-compare]
    lifetime === null ||
    (typeof lifetime !== 'object' && typeof lifetime !== 'function')
  ) {
    throw new Error(
      'To taint a value, a lifetime must be defined by passing an object that holds ' +
        'the value.',
    );
  }
  let entryValue: string | bigint;
  if (typeof value === 'string' || typeof value === 'bigint') {
    // Use as is.
    entryValue = value;
  } else if (
    value instanceof TypedArrayConstructor ||
    value instanceof DataView
  ) {
    // For now, we just convert binary data to a string so that we can just use the native
    // hashing in the Map implementation. It doesn't really matter what form the string
    // take as long as it's the same when we look it up.
    // We're not too worried about collisions since this should be a high entropy value.
    TaintRegistryByteLengths.add(value.byteLength);
    entryValue = binaryToComparableString(value);

View on GitHub (pinned to eafeac097b)

Solutions

  1. Pass the object that holds the secret (e.g. the per-request cache/session object) as the lifetime: taintUniqueValue(message, requestContext, token)
  2. If no natural holder exists, create one (const lifetime = {token}) and keep it alive as long as the secret
  3. Ensure the lifetime is neither null nor a primitive; functions are also accepted as holders

Example fix

// before
taintUniqueValue(msg, 'session-token', token); // string lifetime -> throws

// after
const session = await loadSession();
taintUniqueValue(msg, session, session.token); // object lifetime
Defensive patterns

Strategy: validation

Validate before calling

// Validate the lifetime argument before tainting
function isLifetime(v: unknown): v is object | Function {
  return v !== null && (typeof v === 'object' || typeof v === 'function');
}
if (isLifetime(lifetime)) {
  taintUniqueValue(message, lifetime, secret);
} else {
  throw new TypeError('lifetime must be the object holding the secret');
}

Type guard

const isTaintLifetime = (v: unknown): v is object | Function =>
  v != null && (typeof v === 'object' || typeof v === 'function');

Prevention

When it happens

Trigger: taintUniqueValue(message, 'user', secret) or taintUniqueValue(message, null, secret) — passing a primitive or null where the API expects the object that contains the value (e.g. the request or user session object).

Common situations: Calling the server-security taint APIs for the first time and misunderstanding the second parameter as a label/scope string; refactoring code that previously passed a symbol or string as the lifetime.

Related errors


AI-assisted analysis of facebook/react@eafeac097b (2026-08-21). Data as JSON: /api/errors/57130d7404c63d8a. Report an issue: GitHub.