CVE-2025-27597
Description
Vue I18n is the internationalization plugin for Vue.js. @intlify/message-resolver and @intlify/vue-i18n-core are vulnerable to Prototype Pollution through the entry function: handleFlatJson. An attacker can supply a payload with Object.prototype setter to introduce or modify properties within the global prototype chain, causing denial of service (DoS) a the minimum consequence. Moreover, the consequences of this vulnerability can escalate to other injection-based attacks, depending on how the library integrates within the application. For instance, if the polluted property propagates to sensitive Node.js APIs (e.g., exec, eval), it could enable an attacker to execute arbitrary commands within the application's context.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@intlify/message-resolvernpm | >= 9.1.0, < 9.1.11 | 9.1.11 |
@intlify/vue-i18n-corenpm | >= 9.2.0, < 9.14.3 | 9.14.3 |
petite-vue-i18nnpm | >= 10.0.0, < 10.0.6 | 10.0.6 |
vue-i18nnpm | >= 9.1.0, < 9.14.3 | 9.14.3 |
@intlify/core-basenpm | >= 9.1.0, < 9.1.11 | 9.1.11 |
@intlify/corenpm | >= 9.1.0, < 9.1.11 | 9.1.11 |
@intlify/vue-i18n-corenpm | >= 10.0.0-alpha.1, < 11.1.2 | 11.1.2 |
@intlify/vue-i18n-corenpm | >= 11.0.0-beta.0, < 11.1.2 | 11.1.2 |
petite-vue-i18nnpm | >= 11.0.0-beta.0, < 11.1.2 | 11.1.2 |
vue-i18nnpm | >= 10.0.0-alpha.1, < 10.0.6 | 10.0.6 |
vue-i18nnpm | >= 11.0.0-beta.0, < 11.1.2 | 11.1.2 |
Patches
7e40c1601656d2e255c59ded728ccc4cd85a54bb6eacda7fcfix: prototype pollution in `handleFlatJson`
2 files changed · +67 −49
packages/vue-i18n-core/src/utils.ts+3 −0 modified@@ -69,6 +69,9 @@ export function handleFlatJson(obj: unknown): unknown { let currentObj = obj let hasStringValue = false for (let i = 0; i < lastIndex; i++) { + if (subKeys[i] === '__proto__') { + throw new Error(`unsafe key: ${subKeys[i]}`) + } if (!(subKeys[i] in currentObj)) { currentObj[subKeys[i]] = create() }
packages/vue-i18n-core/test/utils.test.ts+64 −49 modified@@ -1,65 +1,80 @@ // utils import * as shared from '@intlify/shared' +import { handleFlatJson } from '../src/utils' +import { I18nWarnCodes, getWarnMessage } from '../src/warnings' vi.mock('@intlify/shared', async () => { const actual = await vi.importActual<object>('@intlify/shared') return { ...actual, warn: vi.fn() } }) -import { handleFlatJson } from '../src/utils' -import { I18nWarnCodes, getWarnMessage } from '../src/warnings' -test('handleFlatJson', () => { - const mockWarn = vi.spyOn(shared, 'warn') - // eslint-disable-next-line @typescript-eslint/no-empty-function - mockWarn.mockImplementation(() => {}) +describe('handleFlatJson', () => { + test('basic', () => { + const mockWarn = vi.spyOn(shared, 'warn') + // eslint-disable-next-line @typescript-eslint/no-empty-function + mockWarn.mockImplementation(() => {}) - const obj = { - a: { a1: 'a1.value' }, - 'a.a2': 'a.a2.value', - 'b.x': { - 'b1.x': 'b1.x.value', - 'b2.x': ['b2.x.value0', 'b2.x.value1'], - 'b3.x': { 'b3.x': 'b3.x.value' } - }, - c: { - 'animal.dog': 'Dog', - animal: 'Animal' - }, - d: { - 'animal.dog': 'Dog', - animal: {} - } - } - const expectObj = { - a: { - a1: 'a1.value', - a2: 'a.a2.value' - }, - b: { - x: { - b1: { x: 'b1.x.value' }, - b2: { x: ['b2.x.value0', 'b2.x.value1'] }, - b3: { x: { b3: { x: 'b3.x.value' } } } + const obj = { + a: { a1: 'a1.value' }, + 'a.a2': 'a.a2.value', + 'b.x': { + 'b1.x': 'b1.x.value', + 'b2.x': ['b2.x.value0', 'b2.x.value1'], + 'b3.x': { 'b3.x': 'b3.x.value' } + }, + c: { + 'animal.dog': 'Dog', + animal: 'Animal' + }, + d: { + 'animal.dog': 'Dog', + animal: {} } - }, - c: { - 'animal.dog': 'Dog', - animal: 'Animal' - }, - d: { - animal: { - dog: 'Dog' + } + const expectObj = { + a: { + a1: 'a1.value', + a2: 'a.a2.value' + }, + b: { + x: { + b1: { x: 'b1.x.value' }, + b2: { x: ['b2.x.value0', 'b2.x.value1'] }, + b3: { x: { b3: { x: 'b3.x.value' } } } + } + }, + c: { + 'animal.dog': 'Dog', + animal: 'Animal' + }, + d: { + animal: { + dog: 'Dog' + } } } - } - expect(handleFlatJson(obj)).toEqual(expectObj) - expect(mockWarn).toHaveBeenCalled() - expect(mockWarn.mock.calls[0][0]).toEqual( - getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, { - key: 'animal' - }) - ) + expect(handleFlatJson(obj)).toEqual(expectObj) + expect(mockWarn).toHaveBeenCalled() + expect(mockWarn.mock.calls[0][0]).toEqual( + getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, { + key: 'animal' + }) + ) + }) + + // security advisories + // ref: https://github.com/intlify/vue-i18n/security/advisories/GHSA-p2ph-7g93-hw3m + test('prototype pollution', () => { + expect(() => + handleFlatJson({ '__proto__.pollutedKey': 'pollutedValue' }) + ).toThrow() + // @ts-ignore -- test + // eslint-disable-next-line no-proto + expect({}.__proto__.pollutedKey).toBeUndefined() + // @ts-ignore -- test + expect(Object.prototype.pollutedKey).toBeUndefined() + }) })
feaf13fcff42Merge commit from fork
3 files changed · +71 −49
.github/workflows/release.yml+4 −0 modified@@ -8,6 +8,9 @@ on: - 'v*' env: NODE_OPTIONS: --max-old-space-size=6144 +permissions: + id-token: write + contents: write jobs: release: @@ -59,3 +62,4 @@ jobs: ./scripts/release.sh env: NPM_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} + NPM_CONFIG_PROVENANCE: true
packages/vue-i18n-core/src/utils.ts+3 −0 modified@@ -69,6 +69,9 @@ export function handleFlatJson(obj: unknown): unknown { let currentObj = obj let hasStringValue = false for (let i = 0; i < lastIndex; i++) { + if (subKeys[i] === '__proto__') { + throw new Error(`unsafe key: ${subKeys[i]}`) + } if (!(subKeys[i] in currentObj)) { currentObj[subKeys[i]] = create() }
packages/vue-i18n-core/test/utils.test.ts+64 −49 modified@@ -1,65 +1,80 @@ // utils import * as shared from '@intlify/shared' +import { handleFlatJson } from '../src/utils' +import { I18nWarnCodes, getWarnMessage } from '../src/warnings' vi.mock('@intlify/shared', async () => { const actual = await vi.importActual<object>('@intlify/shared') return { ...actual, warn: vi.fn() } }) -import { handleFlatJson } from '../src/utils' -import { I18nWarnCodes, getWarnMessage } from '../src/warnings' -test('handleFlatJson', () => { - const mockWarn = vi.spyOn(shared, 'warn') - // eslint-disable-next-line @typescript-eslint/no-empty-function - mockWarn.mockImplementation(() => {}) +describe('handleFlatJson', () => { + test('basic', () => { + const mockWarn = vi.spyOn(shared, 'warn') + // eslint-disable-next-line @typescript-eslint/no-empty-function + mockWarn.mockImplementation(() => {}) - const obj = { - a: { a1: 'a1.value' }, - 'a.a2': 'a.a2.value', - 'b.x': { - 'b1.x': 'b1.x.value', - 'b2.x': ['b2.x.value0', 'b2.x.value1'], - 'b3.x': { 'b3.x': 'b3.x.value' } - }, - c: { - 'animal.dog': 'Dog', - animal: 'Animal' - }, - d: { - 'animal.dog': 'Dog', - animal: {} - } - } - const expectObj = { - a: { - a1: 'a1.value', - a2: 'a.a2.value' - }, - b: { - x: { - b1: { x: 'b1.x.value' }, - b2: { x: ['b2.x.value0', 'b2.x.value1'] }, - b3: { x: { b3: { x: 'b3.x.value' } } } + const obj = { + a: { a1: 'a1.value' }, + 'a.a2': 'a.a2.value', + 'b.x': { + 'b1.x': 'b1.x.value', + 'b2.x': ['b2.x.value0', 'b2.x.value1'], + 'b3.x': { 'b3.x': 'b3.x.value' } + }, + c: { + 'animal.dog': 'Dog', + animal: 'Animal' + }, + d: { + 'animal.dog': 'Dog', + animal: {} } - }, - c: { - 'animal.dog': 'Dog', - animal: 'Animal' - }, - d: { - animal: { - dog: 'Dog' + } + const expectObj = { + a: { + a1: 'a1.value', + a2: 'a.a2.value' + }, + b: { + x: { + b1: { x: 'b1.x.value' }, + b2: { x: ['b2.x.value0', 'b2.x.value1'] }, + b3: { x: { b3: { x: 'b3.x.value' } } } + } + }, + c: { + 'animal.dog': 'Dog', + animal: 'Animal' + }, + d: { + animal: { + dog: 'Dog' + } } } - } - expect(handleFlatJson(obj)).toEqual(expectObj) - expect(mockWarn).toHaveBeenCalled() - expect(mockWarn.mock.calls[0][0]).toEqual( - getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, { - key: 'animal' - }) - ) + expect(handleFlatJson(obj)).toEqual(expectObj) + expect(mockWarn).toHaveBeenCalled() + expect(mockWarn.mock.calls[0][0]).toEqual( + getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, { + key: 'animal' + }) + ) + }) + + // security advisories + // ref: https://github.com/intlify/vue-i18n/security/advisories/GHSA-p2ph-7g93-hw3m + test('prototype pollution', () => { + expect(() => + handleFlatJson({ '__proto__.pollutedKey': 'pollutedValue' }) + ).toThrow() + // @ts-ignore -- test + // eslint-disable-next-line no-proto + expect({}.__proto__.pollutedKey).toBeUndefined() + // @ts-ignore -- test + expect(Object.prototype.pollutedKey).toBeUndefined() + }) })
fbda9988d3ddMerge commit from fork
4 files changed · +72 −50
.github/workflows/release.yml+4 −0 modified@@ -8,6 +8,9 @@ on: - 'v*' env: NODE_OPTIONS: --max-old-space-size=6144 +permissions: + id-token: write + contents: write jobs: release: @@ -59,3 +62,4 @@ jobs: ./scripts/release.sh env: NPM_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} + NPM_CONFIG_PROVENANCE: true
packages/vue-i18n-core/src/utils.ts+3 −0 modified@@ -69,6 +69,9 @@ export function handleFlatJson(obj: unknown): unknown { let currentObj = obj let hasStringValue = false for (let i = 0; i < lastIndex; i++) { + if (subKeys[i] === '__proto__') { + throw new Error(`unsafe key: ${subKeys[i]}`) + } if (!(subKeys[i] in currentObj)) { currentObj[subKeys[i]] = create() }
packages/vue-i18n-core/test/utils.test.ts+64 −49 modified@@ -1,65 +1,80 @@ // utils import * as shared from '@intlify/shared' +import { handleFlatJson } from '../src/utils' +import { I18nWarnCodes, getWarnMessage } from '../src/warnings' vi.mock('@intlify/shared', async () => { const actual = await vi.importActual<object>('@intlify/shared') return { ...actual, warn: vi.fn() } }) -import { handleFlatJson } from '../src/utils' -import { I18nWarnCodes, getWarnMessage } from '../src/warnings' -test('handleFlatJson', () => { - const mockWarn = vi.spyOn(shared, 'warn') - // eslint-disable-next-line @typescript-eslint/no-empty-function - mockWarn.mockImplementation(() => {}) +describe('handleFlatJson', () => { + test('basic', () => { + const mockWarn = vi.spyOn(shared, 'warn') + // eslint-disable-next-line @typescript-eslint/no-empty-function + mockWarn.mockImplementation(() => {}) - const obj = { - a: { a1: 'a1.value' }, - 'a.a2': 'a.a2.value', - 'b.x': { - 'b1.x': 'b1.x.value', - 'b2.x': ['b2.x.value0', 'b2.x.value1'], - 'b3.x': { 'b3.x': 'b3.x.value' } - }, - c: { - 'animal.dog': 'Dog', - animal: 'Animal' - }, - d: { - 'animal.dog': 'Dog', - animal: {} - } - } - const expectObj = { - a: { - a1: 'a1.value', - a2: 'a.a2.value' - }, - b: { - x: { - b1: { x: 'b1.x.value' }, - b2: { x: ['b2.x.value0', 'b2.x.value1'] }, - b3: { x: { b3: { x: 'b3.x.value' } } } + const obj = { + a: { a1: 'a1.value' }, + 'a.a2': 'a.a2.value', + 'b.x': { + 'b1.x': 'b1.x.value', + 'b2.x': ['b2.x.value0', 'b2.x.value1'], + 'b3.x': { 'b3.x': 'b3.x.value' } + }, + c: { + 'animal.dog': 'Dog', + animal: 'Animal' + }, + d: { + 'animal.dog': 'Dog', + animal: {} } - }, - c: { - 'animal.dog': 'Dog', - animal: 'Animal' - }, - d: { - animal: { - dog: 'Dog' + } + const expectObj = { + a: { + a1: 'a1.value', + a2: 'a.a2.value' + }, + b: { + x: { + b1: { x: 'b1.x.value' }, + b2: { x: ['b2.x.value0', 'b2.x.value1'] }, + b3: { x: { b3: { x: 'b3.x.value' } } } + } + }, + c: { + 'animal.dog': 'Dog', + animal: 'Animal' + }, + d: { + animal: { + dog: 'Dog' + } } } - } - expect(handleFlatJson(obj)).toEqual(expectObj) - expect(mockWarn).toHaveBeenCalled() - expect(mockWarn.mock.calls[0][0]).toEqual( - getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, { - key: 'animal' - }) - ) + expect(handleFlatJson(obj)).toEqual(expectObj) + expect(mockWarn).toHaveBeenCalled() + expect(mockWarn.mock.calls[0][0]).toEqual( + getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, { + key: 'animal' + }) + ) + }) + + // security advisories + // ref: https://github.com/intlify/vue-i18n/security/advisories/GHSA-p2ph-7g93-hw3m + test('prototype pollution', () => { + expect(() => + handleFlatJson({ '__proto__.pollutedKey': 'pollutedValue' }) + ).toThrow() + // @ts-ignore -- test + // eslint-disable-next-line no-proto + expect({}.__proto__.pollutedKey).toBeUndefined() + // @ts-ignore -- test + expect(Object.prototype.pollutedKey).toBeUndefined() + }) })
scripts/release.sh+1 −1 modified@@ -23,7 +23,7 @@ for PKG in packages/* ; do continue fi pushd $PKG - TAG="latest" + TAG="legacy10" echo "⚡ Publishing $PKG with tag $TAG" pnpm publish --access public --no-git-checks --tag $TAG popd > /dev/null
d21e06a7440eMerge commit from fork
3 files changed · +69 −47
.github/workflows/release.yml+4 −0 modified@@ -8,6 +8,9 @@ on: - 'v*' env: NODE_OPTIONS: --max-old-space-size=6144 +permissions: + id-token: write + contents: write jobs: release: @@ -59,3 +62,4 @@ jobs: ./scripts/release.sh env: NPM_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} + NPM_CONFIG_PROVENANCE: true
packages/vue-i18n-core/src/utils.ts+3 −0 modified@@ -77,6 +77,9 @@ export function handleFlatJson(obj: unknown): unknown { let currentObj = obj let hasStringValue = false for (let i = 0; i < lastIndex; i++) { + if (subKeys[i] === '__proto__') { + throw new Error(`unsafe key: ${subKeys[i]}`) + } if (!(subKeys[i] in currentObj)) { currentObj[subKeys[i]] = create() }
packages/vue-i18n-core/test/utils.test.ts+62 −47 modified@@ -10,56 +10,71 @@ vi.mock('@intlify/shared', async () => { import { handleFlatJson } from '../src/utils' import { I18nWarnCodes, getWarnMessage } from '../src/warnings' -test('handleFlatJson', () => { - const mockWarn = vi.spyOn(shared, 'warn') - // eslint-disable-next-line @typescript-eslint/no-empty-function - mockWarn.mockImplementation(() => {}) +describe('handleFlatJson', () => { + test('basic', () => { + const mockWarn = vi.spyOn(shared, 'warn') + // eslint-disable-next-line @typescript-eslint/no-empty-function + mockWarn.mockImplementation(() => {}) - const obj = { - a: { a1: 'a1.value' }, - 'a.a2': 'a.a2.value', - 'b.x': { - 'b1.x': 'b1.x.value', - 'b2.x': ['b2.x.value0', 'b2.x.value1'], - 'b3.x': { 'b3.x': 'b3.x.value' } - }, - c: { - 'animal.dog': 'Dog', - animal: 'Animal' - }, - d: { - 'animal.dog': 'Dog', - animal: {} - } - } - const expectObj = { - a: { - a1: 'a1.value', - a2: 'a.a2.value' - }, - b: { - x: { - b1: { x: 'b1.x.value' }, - b2: { x: ['b2.x.value0', 'b2.x.value1'] }, - b3: { x: { b3: { x: 'b3.x.value' } } } + const obj = { + a: { a1: 'a1.value' }, + 'a.a2': 'a.a2.value', + 'b.x': { + 'b1.x': 'b1.x.value', + 'b2.x': ['b2.x.value0', 'b2.x.value1'], + 'b3.x': { 'b3.x': 'b3.x.value' } + }, + c: { + 'animal.dog': 'Dog', + animal: 'Animal' + }, + d: { + 'animal.dog': 'Dog', + animal: {} } - }, - c: { - 'animal.dog': 'Dog', - animal: 'Animal' - }, - d: { - animal: { - dog: 'Dog' + } + const expectObj = { + a: { + a1: 'a1.value', + a2: 'a.a2.value' + }, + b: { + x: { + b1: { x: 'b1.x.value' }, + b2: { x: ['b2.x.value0', 'b2.x.value1'] }, + b3: { x: { b3: { x: 'b3.x.value' } } } + } + }, + c: { + 'animal.dog': 'Dog', + animal: 'Animal' + }, + d: { + animal: { + dog: 'Dog' + } } } - } - expect(handleFlatJson(obj)).toEqual(expectObj) - expect(mockWarn).toHaveBeenCalled() - expect(mockWarn.mock.calls[0][0]).toEqual( - getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, { - key: 'animal' - }) - ) + expect(handleFlatJson(obj)).toEqual(expectObj) + expect(mockWarn).toHaveBeenCalled() + expect(mockWarn.mock.calls[0][0]).toEqual( + getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, { + key: 'animal' + }) + ) + }) + + // security advisories + // ref: https://github.com/intlify/vue-i18n/security/advisories/GHSA-p2ph-7g93-hw3m + test('prototype pollution', () => { + expect(() => + handleFlatJson({ '__proto__.pollutedKey': 'pollutedValue' }) + ).toThrow() + // @ts-ignore -- test + // eslint-disable-next-line no-proto + expect({}.__proto__.pollutedKey).toBeUndefined() + // @ts-ignore -- test + expect(Object.prototype.pollutedKey).toBeUndefined() + }) })
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/advisories/GHSA-p2ph-7g93-hw3mghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-27597ghsaADVISORY
- github.com/intlify/vue-i18n/commit/4bb6eacda7fc2cde5687549afa0efb27ca40862anvdWEB
- github.com/intlify/vue-i18n/commit/d21e06a7440eed8ada7f522b22fcf830b98d3a53ghsaWEB
- github.com/intlify/vue-i18n/commit/fbda9988d3ddd3a1a21740d506d2c183d6b6e36aghsaWEB
- github.com/intlify/vue-i18n/commit/feaf13fcff427f2cb1d5ec8076e639506ba28f9eghsaWEB
- github.com/intlify/vue-i18n/releases/tag/v10.0.6ghsaWEB
- github.com/intlify/vue-i18n/releases/tag/v11.1.2ghsaWEB
- github.com/intlify/vue-i18n/releases/tag/v9.14.3ghsaWEB
- github.com/intlify/vue-i18n/security/advisories/GHSA-p2ph-7g93-hw3mnvdWEB
News mentions
0No linked articles in our index yet.