Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/safe-ravens-unflatten.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@reflag/flag-evaluation": patch
---

Prevent prototype pollution when unflattening JSON with unsafe property paths.
9 changes: 8 additions & 1 deletion packages/flag-evaluation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,16 @@ export function flattenJSON(data: object): Record<string, string> {
*/
export function unflattenJSON(data: Record<string, any>): Record<string, any> {
const result: Record<string, any> = {};
// Traversing these properties on a plain object can reach Object.prototype.
const unsafePathSegments = new Set(["__proto__", "constructor", "prototype"]);

for (const i in data) {
for (const i of Object.keys(data)) {
const keys = i.split(".");

if (keys.some((key) => unsafePathSegments.has(key))) {
continue;
}

keys.reduce((acc, key, index) => {
if (index === keys.length - 1) {
if (typeof acc === "object") {
Expand Down
29 changes: 29 additions & 0 deletions packages/flag-evaluation/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,35 @@ describe("unflattenJSON", () => {
expect(output).toEqual({});
});

it("should prevent prototype pollution", () => {
const unexpectedProperties = [
"unexpectedConstructorPathProperty",
"unexpectedProtoPathProperty",
];

for (const property of unexpectedProperties) {
delete Object.prototype[property];
}

try {
const output = unflattenJSON({
"constructor.prototype.unexpectedConstructorPathProperty": "value",
"__proto__.unexpectedProtoPathProperty": "value",
});

expect(output).toEqual({});
for (const property of unexpectedProperties) {
expect(
Object.prototype.hasOwnProperty.call(Object.prototype, property),
).toBe(false);
}
} finally {
for (const property of unexpectedProperties) {
delete Object.prototype[property];
}
}
});

it("should convert a flat object with one level deep keys to a nested object", () => {
const input = {
"a.b.c": "value",
Expand Down
Loading