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

  1. 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).
  2. Fix the columns.data / dataSrc dotted path so it targets real fields instead of __proto__ or constructor.
  3. 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.
  4. Use a safe deep-parse (e.g. parse with Object.create(null) or a hardened JSON.parse) so polluted keys never reach the table.
  5. 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

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.