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
- Sanitise row ids before calling updateCells: reject or rename keys in {'__proto__','constructor','prototype'} (and ideally any non-string or empty key).
- 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.
- 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
- Sanitise all external row ids before writing to the cells map.
- Prefer Object.create(null) for any record passed as cells.
- Validate imported CSV/JSON row headers against an allow-list pattern.
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.