diff --git a/lib/plugins/system/meta/HTMLMetaHandler.js b/lib/plugins/system/meta/HTMLMetaHandler.js
index fd467538b..e64f6fa1f 100644
--- a/lib/plugins/system/meta/HTMLMetaHandler.js
+++ b/lib/plugins/system/meta/HTMLMetaHandler.js
@@ -469,6 +469,12 @@ function merge(parentObj, props, value) {
return;
}
+ // Prototype pollution guard: never let attacker-controlled meta tag names
+ // (e.g. ) write into Object.prototype.
+ if (props.some(function(p) { return p === '__proto__' || p === 'constructor' || p === 'prototype'; })) {
+ return;
+ }
+
var currentNodeName = props[props.length - 1];
// Add 'url' to 'og:video'. And other same cases.
diff --git a/lib/utils.js b/lib/utils.js
index fd6f36706..dd3d58741 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1041,6 +1041,15 @@ export function unifyDate(date) {
export function lowerCaseKeys(obj) {
for (var k in obj) {
+ if (!Object.prototype.hasOwnProperty.call(obj, k)) {
+ // Skip inherited keys (e.g. from an already polluted prototype).
+ continue;
+ }
+ // Prototype pollution guard: never copy dangerous keys coming from
+ // attacker-controlled JSON (e.g. oembed `{"__proto__":{...}}`).
+ if (k === '__proto__' || k === 'constructor' || k === 'prototype') {
+ continue;
+ }
var lowerCaseKey = k.toLowerCase();
if (lowerCaseKey != k) {
obj[lowerCaseKey] = obj[k];