toeverything/AFFiNE · error · Error

Invalid rowId

Error message

Invalid rowId

What it means

updateCells guards the cells map (model.props.cells) against prototype-pollution by rejecting the keys '__proto__', 'constructor', and 'prototype'. These keys, if written into a plain object used as a map, can poison Object/prototype in JS engines. The guard is a hard stop: no fallback, the transaction still runs but the throw propagates.

Source

Thrown at blocksuite/affine/blocks/database/src/utils/block-utils.ts:190

        value: cell.value,
      };
    }
  });
}

export function updateCells(
  model: DatabaseBlockModel,
  columnId: string,
  cells: Record<string, unknown>
) {
  model.store.transact(() => {
    Object.entries(cells).forEach(([rowId, value]) => {
      if (
        rowId === '__proto__' ||
        rowId === 'constructor' ||
        rowId === 'prototype'
      ) {
        throw new Error('Invalid rowId');
      }
      if (!model.props.cells[rowId]) {
        model.props.cells[rowId] = Object.create(null);
      }
      if (model.props.cells[rowId]) {
        model.props.cells[rowId][columnId] = {
          columnId,
          value,
        };
      }
    });
  });
}

export function updateProperty(
  model: DatabaseBlockModel,
  id: string,
  updater: ColumnUpdater,

View on GitHub (pinned to 26c515e050)

Solutions

  1. Sanitise row ids before calling updateCells: reject or rename keys in {'__proto__','constructor','prototype'} (and ideally any non-string or empty key).
  2. Construct model.props.cells with Object.create(null) (the function already does per-row) but also keep the input record null-prototype to avoid accidental pollution upstream.
  3. Validate external payloads against a row-id schema before they reach the database block model.

Example fix

// before
updateCells(model, columnId, rawData); // rawData may contain '__proto__'

// after
const FORBIDDEN = new Set(['__proto__', 'constructor', 'prototype']);
const safe = Object.fromEntries(
  Object.entries(rawData).filter(([k]) => k && !FORBIDDEN.has(k))
);
updateCells(model, columnId, safe);
Defensive patterns

Strategy: validation

Validate before calling

const FORBIDDEN = new Set(['__proto__', 'constructor', 'prototype']);
function sanitizeCells(cells) {
  return Object.fromEntries(
    Object.entries(cells).filter(([k]) => typeof k === 'string' && k && !FORBIDDEN.has(k))
  );
}
updateCells(model, columnId, sanitizeCells(rawCells));

Type guard

const FORBIDDEN = new Set(['__proto__', 'constructor', 'prototype']);
function isSafeRowId(id: string): boolean {
  return !!id && !FORBIDDEN.has(id);
}

Prevention

When it happens

Trigger: Calling updateCells(model, columnId, cells) where cells has a key equal to '__proto__', 'constructor', or 'prototype'. This typically arises from untrusted/parsed input (JSON.parse of remote data, snapshot imports, copy-paste payloads, or CSV import where a row header is one of these strings).

Common situations: Importing external data (CSV/JSON) whose row identifiers collide with built-in object property names; deserializing a snapshot that an attacker or buggy exporter crafted; feeding user-typed row ids straight into updateCells without sanitisation.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/cf2e2a8eed42e663. Report an issue: GitHub.