Skip to content

Commit 4bb6eac

Browse files
committed
fix: prototype pollution in handleFlatJson
1 parent cd4c9c9 commit 4bb6eac

File tree

2 files changed

+67
-49
lines changed

2 files changed

+67
-49
lines changed

packages/vue-i18n-core/src/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export function handleFlatJson(obj: unknown): unknown {
6969
let currentObj = obj
7070
let hasStringValue = false
7171
for (let i = 0; i < lastIndex; i++) {
72+
if (subKeys[i] === '__proto__') {
73+
throw new Error(`unsafe key: ${subKeys[i]}`)
74+
}
7275
if (!(subKeys[i] in currentObj)) {
7376
currentObj[subKeys[i]] = create()
7477
}
Lines changed: 64 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,80 @@
11
// utils
22
import * as shared from '@intlify/shared'
3+
import { handleFlatJson } from '../src/utils'
4+
import { I18nWarnCodes, getWarnMessage } from '../src/warnings'
35
vi.mock('@intlify/shared', async () => {
46
const actual = await vi.importActual<object>('@intlify/shared')
57
return {
68
...actual,
79
warn: vi.fn()
810
}
911
})
10-
import { handleFlatJson } from '../src/utils'
11-
import { I18nWarnCodes, getWarnMessage } from '../src/warnings'
1212

13-
test('handleFlatJson', () => {
14-
const mockWarn = vi.spyOn(shared, 'warn')
15-
// eslint-disable-next-line @typescript-eslint/no-empty-function
16-
mockWarn.mockImplementation(() => {})
13+
describe('handleFlatJson', () => {
14+
test('basic', () => {
15+
const mockWarn = vi.spyOn(shared, 'warn')
16+
// eslint-disable-next-line @typescript-eslint/no-empty-function
17+
mockWarn.mockImplementation(() => {})
1718

18-
const obj = {
19-
a: { a1: 'a1.value' },
20-
'a.a2': 'a.a2.value',
21-
'b.x': {
22-
'b1.x': 'b1.x.value',
23-
'b2.x': ['b2.x.value0', 'b2.x.value1'],
24-
'b3.x': { 'b3.x': 'b3.x.value' }
25-
},
26-
c: {
27-
'animal.dog': 'Dog',
28-
animal: 'Animal'
29-
},
30-
d: {
31-
'animal.dog': 'Dog',
32-
animal: {}
33-
}
34-
}
35-
const expectObj = {
36-
a: {
37-
a1: 'a1.value',
38-
a2: 'a.a2.value'
39-
},
40-
b: {
41-
x: {
42-
b1: { x: 'b1.x.value' },
43-
b2: { x: ['b2.x.value0', 'b2.x.value1'] },
44-
b3: { x: { b3: { x: 'b3.x.value' } } }
19+
const obj = {
20+
a: { a1: 'a1.value' },
21+
'a.a2': 'a.a2.value',
22+
'b.x': {
23+
'b1.x': 'b1.x.value',
24+
'b2.x': ['b2.x.value0', 'b2.x.value1'],
25+
'b3.x': { 'b3.x': 'b3.x.value' }
26+
},
27+
c: {
28+
'animal.dog': 'Dog',
29+
animal: 'Animal'
30+
},
31+
d: {
32+
'animal.dog': 'Dog',
33+
animal: {}
4534
}
46-
},
47-
c: {
48-
'animal.dog': 'Dog',
49-
animal: 'Animal'
50-
},
51-
d: {
52-
animal: {
53-
dog: 'Dog'
35+
}
36+
const expectObj = {
37+
a: {
38+
a1: 'a1.value',
39+
a2: 'a.a2.value'
40+
},
41+
b: {
42+
x: {
43+
b1: { x: 'b1.x.value' },
44+
b2: { x: ['b2.x.value0', 'b2.x.value1'] },
45+
b3: { x: { b3: { x: 'b3.x.value' } } }
46+
}
47+
},
48+
c: {
49+
'animal.dog': 'Dog',
50+
animal: 'Animal'
51+
},
52+
d: {
53+
animal: {
54+
dog: 'Dog'
55+
}
5456
}
5557
}
56-
}
5758

58-
expect(handleFlatJson(obj)).toEqual(expectObj)
59-
expect(mockWarn).toHaveBeenCalled()
60-
expect(mockWarn.mock.calls[0][0]).toEqual(
61-
getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, {
62-
key: 'animal'
63-
})
64-
)
59+
expect(handleFlatJson(obj)).toEqual(expectObj)
60+
expect(mockWarn).toHaveBeenCalled()
61+
expect(mockWarn.mock.calls[0][0]).toEqual(
62+
getWarnMessage(I18nWarnCodes.IGNORE_OBJ_FLATTEN, {
63+
key: 'animal'
64+
})
65+
)
66+
})
67+
68+
// security advisories
69+
// ref: https://github.com/intlify/vue-i18n/security/advisories/GHSA-p2ph-7g93-hw3m
70+
test('prototype pollution', () => {
71+
expect(() =>
72+
handleFlatJson({ '__proto__.pollutedKey': 'pollutedValue' })
73+
).toThrow()
74+
// @ts-ignore -- test
75+
// eslint-disable-next-line no-proto
76+
expect({}.__proto__.pollutedKey).toBeUndefined()
77+
// @ts-ignore -- test
78+
expect(Object.prototype.pollutedKey).toBeUndefined()
79+
})
6580
})

0 commit comments

Comments
 (0)