dani-garcia/vaultwarden · error · Error
Cannot set prototype values
Error message
Cannot set prototype values
What it means
DataTables' data accessor (the set() helper behind `columns.data` / `row.data()`) resolves dotted string paths like 'user.name' or '[0].id' to read/write nested values. Before walking the path it checks every segment and throws 'Cannot set prototype values' if a segment is `__proto__` or `constructor`. This is an intentional security guard against prototype pollution: without it, hostile data or config could overwrite Object.prototype and corrupt every object in the page.
Source
Thrown at src/static/scripts/datatables.js:1161
}
else if (typeof dataPoint === 'function') {
return function (data, val, meta) {
dataPoint(data, 'set', val, meta);
};
}
else if (typeof dataPoint === 'string' &&
(dataPoint.indexOf('.') !== -1 ||
dataPoint.indexOf('[') !== -1 ||
dataPoint.indexOf('(') !== -1)) {
// Like the get, we need to get data from a nested object
let setData = function (data, val, src) {
let a = splitObjNotation(src), b;
let aLast = a[a.length - 1];
let arrayNotation, funcNotation, o, innerSrc;
for (let i = 0, iLen = a.length - 1; i < iLen; i++) {
// Protect against prototype pollution
if (a[i] === '__proto__' || a[i] === 'constructor') {
throw new Error('Cannot set prototype values');
}
// Check if we are dealing with an array notation request
arrayNotation = a[i].match(__reArray);
funcNotation = a[i].match(__reFn);
if (arrayNotation) {
a[i] = a[i].replace(__reArray, '');
data[a[i]] = [];
// Get the remainder of the nested object to set so we can recurse
b = a.slice();
b.splice(0, i + 1);
innerSrc = b.join('.');
// Traverse each entry in the array setting the properties requested
if (Array.isArray(val)) {
for (let j = 0, jLen = val.length; j < jLen; j++) {
o = {};
setData(o, val[j], innerSrc);
data[a[i]].push(o);
}View on GitHub (pinned to 6729e83521)
Solutions
- Sanitize incoming JSON/data before handing it to DataTables: strip or rename any '__proto__' or 'constructor' keys (e.g. JSON.parse with a reviver that rejects them).
- Fix the columns.data / dataSrc dotted path so it targets real fields instead of __proto__ or constructor.
- If you legitimately have fields with those names, access them via a function data source — columns.data as a function receives the raw row and avoids the path walker.
- Use a safe deep-parse (e.g. parse with Object.create(null) or a hardened JSON.parse) so polluted keys never reach the table.
- Keep DataTables updated; the prototype-pollution guard was added as a security fix, so downgrading reintroduces the vulnerability.
Example fix
// before
columns: [
{ data: '__proto__.name' } // throws: Cannot set prototype values
]
// after
columns: [
{ data: 'name' } // correct dotted path to a real field
]
// or, for genuinely hostile keys, use a function accessor:
columns: [
{ data: row => row['constructor'] ?? '' }
] Defensive patterns
Strategy: validation
Validate before calling
const UNSAFE_PATH = /(^|\.|\[)(__proto__|constructor|prototype)(\.|\]|$)/;
function isSafeDataPath(path) {
return typeof path === 'string' && !UNSAFE_PATH.test(path);
}
// before building columns:
// columns.forEach(c => { if (typeof c.data === 'string' && !isSafeDataPath(c.data)) throw new Error('unsafe columns.data: ' + c.data); })
// and sanitize row data:
function sanitize(obj) {
for (const k of Object.keys(obj)) {
if (k === '__proto__' || k === 'constructor') { delete obj[k]; continue; }
if (obj[k] && typeof obj[k] === 'object') sanitize(obj[k]);
}
return obj;
} Type guard
function isSafeColumnData(data) {
return typeof data !== 'string' ||
!/(^|\.|\[)(__proto__|constructor|prototype)(\.|\]|$)/.test(data);
} Try / catch
try {
table.rows.add(rows).draw();
} catch (e) {
if (e.message === 'Cannot set prototype values') {
console.error('Rejected data containing __proto__/constructor keys', e);
rows = rows.map(sanitize);
table.rows.add(rows).draw();
} else { throw e; }
} Prevention
- Parse untrusted JSON with a reviver that rejects __proto__ and constructor keys before it reaches DataTables.
- Never build columns.data paths by concatenating user input.
- Prefer function-based data accessors for rows that may contain unusual keys.
- Freeze Object.prototype (Object.freeze(Object.prototype)) in hardened pages to blunt pollution attempts.
- Test column definitions against sample payloads with hostile keys in CI.
When it happens
Trigger: Passing a `columns.data` string option containing '__proto__' or 'constructor' as a path segment (e.g. columns: [{data: '__proto__.x'}]); calling table.row().data() / row().set() with a dotted source naming those keys; rendering JSON payloads that contain '__proto__' or 'constructor' properties when DataTables resolves them via splitObjNotation.
Common situations: Rendering untrusted third-party JSON (API responses, uploaded files) whose objects were crafted with __proto__/constructor keys; a typo or over-broad dotted path in column definitions; server-side code echoing user input into keys; older DataTables versions lacked this check, so payloads that 'worked' before may now throw after upgrading.
Related errors
AI-assisted analysis of dani-garcia/vaultwarden@6729e83521 (2026-09-02).
Data as JSON: /api/errors/15985230498f1f4a.
Report an issue: GitHub.