Denial of Service (DoS)
Description
JointJS <3.3.0 allows denial of service via prototype pollution in unsetByPath, corrupting object behavior.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
JointJS <3.3.0 allows denial of service via prototype pollution in unsetByPath, corrupting object behavior.
Vulnerability
CVE-2020-28479 is a denial-of-service (DoS) vulnerability in the JointJS JavaScript diagramming library, affecting versions before 3.3.0 [1]. The root cause lies in the unsetByPath function, which failed to properly validate property keys when deleting nested object properties. This allowed an attacker to traverse up the prototype chain by providing a path that includes keys like __proto__ or constructor, leading to prototype pollution [3][4].
Exploitation
Exploitation requires no authentication and can be triggered by any untrusted input that reaches the unsetByPath function. The Snyk advisory demonstrates a proof-of-concept where calling jointjs.util.unsetByPath({}, '__proto__/toString', '/') deletes the toString method from Object.prototype [4]. After such an operation, standard object methods become undefined, causing crashes in any code that relies on them [4].
Impact
A successful attack results in a complete denial of service of the application using JointJS. Because the pollution affects the global Object.prototype, it can break not only diagram-related functionality but any part of the application that depends on built-in object methods like toString, valueOf, or hasOwnProperty [3][4]. The vulnerability is classified with a CVSS score determined by the vendor (NVD lists severity information, though it was not fully provided in the input).
Mitigation
The issue was fixed in JointJS version 3.3.0, which introduced the isGetSafe helper to reject dangerous keys (constructor, __proto__) in both setByPath and unsetByPath [2][3]. Users of JointJS should upgrade to v3.3.0 or later. There is no viable workaround that preserves all functionality. The vulnerability is tracked by Snyk as SNYK-JS-JOINTJS-1062038 [4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
jointjsnpm | < 3.3.0 | 3.3.0 |
Affected products
2- jointjs/jointjsdescription
Patches
1ec7ab01b512autil.unsetByPath(): prevent prototype modification (#1406)
2 files changed · +34 −24
src/util/util.mjs+25 −24 modified@@ -137,6 +137,18 @@ export const getByPath = function(obj, path, delimiter) { return obj; }; +const isGetSafe = function(obj, key) { + // Prevent prototype pollution + // https://snyk.io/vuln/SNYK-JS-JSON8MERGEPATCH-1038399 + if (key === 'constructor' && typeof obj[key] === 'function') { + return false; + } + if (key === '__proto__') { + return false; + } + return true; +}; + export const setByPath = function(obj, path, value, delimiter) { const keys = Array.isArray(path) ? path : path.split(delimiter || '/'); @@ -146,15 +158,8 @@ export const setByPath = function(obj, path, value, delimiter) { for (; i < last; i++) { const key = keys[i]; + if (!isGetSafe(diver, key)) return obj; const value = diver[key]; - // Prevent prototype pollution - // https://snyk.io/vuln/SNYK-JS-JSON8MERGEPATCH-1038399 - if (key === 'constructor' && typeof value === 'function') { - return obj; - } - if (key === '__proto__') { - return obj; - } // diver creates an empty object if there is no nested object under such a key. // This means that one can populate an empty nested object with setByPath(). diver = value || (diver[key] = {}); @@ -167,25 +172,21 @@ export const setByPath = function(obj, path, value, delimiter) { export const unsetByPath = function(obj, path, delimiter) { - delimiter || (delimiter = '/'); - - var pathArray = Array.isArray(path) ? path.slice() : path.split(delimiter); - - var propertyToRemove = pathArray.pop(); - if (pathArray.length > 0) { - - // unsetting a nested attribute - var parent = getByPath(obj, pathArray, delimiter); - if (parent) { - delete parent[propertyToRemove]; - } - - } else { + const keys = Array.isArray(path) ? path : path.split(delimiter || '/'); + const last = keys.length - 1; + let diver = obj; + let i = 0; - // unsetting a primitive attribute - delete obj[propertyToRemove]; + for (; i < last; i++) { + const key = keys[i]; + if (!isGetSafe(diver, key)) return obj; + const value = diver[key]; + if (!value) return obj; + diver = value; } + delete diver[keys[last]]; + return obj; };
test/jointjs/core/util.js+9 −0 modified@@ -401,6 +401,15 @@ QUnit.module('util', function(hooks) { joint.util.unsetByPath(obj, ['c', 'd']); assert.deepEqual(obj, { a: 1 }, 'Attempt to delete non-existing attribute doesn\'t affect object.'); }); + + ['__proto__/toString', 'constructor/prototype/toString'].forEach(function(path) { + QUnit.test('unsetting "' + path + '" does not modify prototype' , function(assert) { + var obj = {}; + assert.equal(typeof obj.toString, 'function'); + joint.util.unsetByPath({}, path, '/'); + assert.equal(typeof obj.toString, 'function'); + }); + }); }); QUnit.test('util.normalizeSides()', function(assert) {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-cq8r-fc3q-6hg2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-28479ghsaADVISORY
- github.com/clientIO/joint/commit/ec7ab01b512a3c06a9944a25d50f255bf07c3499ghsaWEB
- github.com/clientIO/joint/releases/tag/v3.3.0ghsax_refsource_MISCWEB
- snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1062040ghsax_refsource_MISCWEB
- snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1062039ghsax_refsource_MISCWEB
- snyk.io/vuln/SNYK-JS-JOINTJS-1062038ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.