diff --git a/e2e/hotfix.spec.ts b/e2e/hotfix.spec.ts
new file mode 100644
index 000000000..b14680764
--- /dev/null
+++ b/e2e/hotfix.spec.ts
@@ -0,0 +1,11 @@
+import { getText } from './helper'
+
+describe('CVE-2024-52809', () => {
+ beforeAll(async () => {
+ await page.goto(`http://localhost:8080/e2e/hotfix/CVE-2024-52809.html`)
+ })
+
+ test('fix', async () => {
+ expect(await getText(page, 'p')).toMatch('hello world!')
+ })
+})
diff --git a/e2e/hotfix/CVE-2024-52809.html b/e2e/hotfix/CVE-2024-52809.html
new file mode 100644
index 000000000..c2e6c3cbf
--- /dev/null
+++ b/e2e/hotfix/CVE-2024-52809.html
@@ -0,0 +1,69 @@
+
+
+
+
+ vue-i18n XSS
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/core-base/src/compilation.ts b/packages/core-base/src/compilation.ts
index 8a0f83707..9b809e1f3 100644
--- a/packages/core-base/src/compilation.ts
+++ b/packages/core-base/src/compilation.ts
@@ -3,13 +3,21 @@ import {
defaultOnError,
detectHtmlTag
} from '@intlify/message-compiler'
-import { format, isBoolean, isObject, isString, warn } from '@intlify/shared'
-import { format as formatMessage } from './format'
+import {
+ format,
+ hasOwn,
+ isBoolean,
+ isObject,
+ isString,
+ warn
+} from '@intlify/shared'
+import { format as formatMessage, resolveType } from './format'
import type {
CompileError,
CompileOptions,
CompilerResult,
+ Node,
ResourceNode
} from '@intlify/message-compiler'
import type { MessageCompilerContext } from './context'
@@ -30,10 +38,13 @@ export function clearCompileCache(): void {
compileCache = Object.create(null)
}
-export const isMessageAST = (val: unknown): val is ResourceNode =>
- isObject(val) &&
- (val.t === 0 || val.type === 0) &&
- ('b' in val || 'body' in val)
+export function isMessageAST(val: unknown): val is ResourceNode {
+ return (
+ isObject(val) &&
+ resolveType(val as Node) === 0 &&
+ (hasOwn(val, 'b') || hasOwn(val, 'body'))
+ )
+}
function baseCompile(
message: string,
diff --git a/packages/core-base/src/errors.ts b/packages/core-base/src/errors.ts
index f09dc9adb..d63ed43f9 100644
--- a/packages/core-base/src/errors.ts
+++ b/packages/core-base/src/errors.ts
@@ -1,6 +1,6 @@
import {
- createCompileError,
- COMPILE_ERROR_CODES_EXTEND_POINT
+ COMPILE_ERROR_CODES_EXTEND_POINT,
+ createCompileError
} from '@intlify/message-compiler'
import type { BaseError } from '@intlify/shared'
diff --git a/packages/core-base/src/format.ts b/packages/core-base/src/format.ts
index 947ceff7f..0c2804620 100644
--- a/packages/core-base/src/format.ts
+++ b/packages/core-base/src/format.ts
@@ -1,23 +1,21 @@
import { NodeTypes } from '@intlify/message-compiler'
+import { hasOwn, isNumber } from '@intlify/shared'
import type {
- Node,
- TextNode,
- LiteralNode,
+ LinkedModifierNode,
+ LinkedNode,
ListNode,
MessageNode,
NamedNode,
- LinkedNode,
- LinkedKeyNode,
- LinkedModifierNode,
+ Node,
PluralNode,
ResourceNode
} from '@intlify/message-compiler'
import type {
MessageContext,
MessageFunction,
- MessageType,
- MessageFunctionReturn
+ MessageFunctionReturn,
+ MessageType
} from './runtime'
export function format(
@@ -28,14 +26,18 @@ export function format(
return msg
}
-function formatParts(
+export function formatParts(
ctx: MessageContext,
ast: ResourceNode
): MessageFunctionReturn {
- const body = ast.b || ast.body
- if ((body.t || body.type) === NodeTypes.Plural) {
+ const body = resolveBody(ast)
+ if (body == null) {
+ throw createUnhandleNodeError(NodeTypes.Resource)
+ }
+ const type = resolveType(body)
+ if (type === NodeTypes.Plural) {
const plural = body as PluralNode
- const cases = plural.c || plural.cases
+ const cases = resolveCases(plural)
return ctx.plural(
cases.reduce(
(messages, c) =>
@@ -51,17 +53,33 @@ function formatParts(
}
}
-function formatMessageParts(
+const PROPS_BODY = ['b', 'body']
+
+function resolveBody(node: ResourceNode) {
+ return resolveProps(node, PROPS_BODY)
+}
+
+const PROPS_CASES = ['c', 'cases']
+
+function resolveCases(node: PluralNode) {
+ return resolveProps(
+ node,
+ PROPS_CASES,
+ []
+ )
+}
+
+export function formatMessageParts(
ctx: MessageContext,
node: MessageNode
): MessageFunctionReturn {
- const _static = node.s || node.static
- if (_static != null) {
+ const static_ = resolveStatic(node)
+ if (static_ != null) {
return ctx.type === 'text'
- ? (_static as MessageFunctionReturn)
- : ctx.normalize([_static] as MessageType[])
+ ? (static_ as MessageFunctionReturn)
+ : ctx.normalize([static_] as MessageType[])
} else {
- const messages = (node.i || node.items).reduce(
+ const messages = resolveItems(node).reduce(
(acm, c) => [...acm, formatMessagePart(ctx, c)],
[] as MessageType[]
)
@@ -69,46 +87,136 @@ function formatMessageParts(
}
}
-function formatMessagePart(
+const PROPS_STATIC = ['s', 'static']
+
+function resolveStatic(node: MessageNode) {
+ return resolveProps(node, PROPS_STATIC)
+}
+
+const PROPS_ITEMS = ['i', 'items']
+
+function resolveItems(node: MessageNode) {
+ return resolveProps(
+ node,
+ PROPS_ITEMS,
+ []
+ )
+}
+
+type NodeValue = {
+ v?: MessageType
+ value?: MessageType
+}
+
+export function formatMessagePart(
ctx: MessageContext,
node: Node
): MessageType {
- const type = node.t || node.type
+ const type = resolveType(node)
switch (type) {
case NodeTypes.Text: {
- const text = node as TextNode
- return (text.v || text.value) as MessageType
+ return resolveValue(node as NodeValue, type)
}
case NodeTypes.Literal: {
- const literal = node as LiteralNode
- return (literal.v || literal.value) as MessageType
+ return resolveValue(node as NodeValue, type)
}
case NodeTypes.Named: {
const named = node as NamedNode
- return ctx.interpolate(ctx.named(named.k || named.key))
+ if (hasOwn(named, 'k') && named.k) {
+ return ctx.interpolate(ctx.named(named.k))
+ }
+ if (hasOwn(named, 'key') && named.key) {
+ return ctx.interpolate(ctx.named(named.key))
+ }
+ throw createUnhandleNodeError(type)
}
case NodeTypes.List: {
const list = node as ListNode
- return ctx.interpolate(ctx.list(list.i != null ? list.i : list.index))
+ if (hasOwn(list, 'i') && isNumber(list.i)) {
+ return ctx.interpolate(ctx.list(list.i))
+ }
+ if (hasOwn(list, 'index') && isNumber(list.index)) {
+ return ctx.interpolate(ctx.list(list.index))
+ }
+ throw createUnhandleNodeError(type)
}
case NodeTypes.Linked: {
const linked = node as LinkedNode
- const modifier = linked.m || linked.modifier
+ const modifier = resolveLinkedModifier(linked)
+ const key = resolveLinkedKey(linked)
return ctx.linked(
- formatMessagePart(ctx, linked.k || linked.key) as string,
+ formatMessagePart(ctx, key!) as string,
modifier ? (formatMessagePart(ctx, modifier) as string) : undefined,
ctx.type
)
}
case NodeTypes.LinkedKey: {
- const linkedKey = node as LinkedKeyNode
- return (linkedKey.v || linkedKey.value) as MessageType
+ return resolveValue(node as NodeValue, type)
}
case NodeTypes.LinkedModifier: {
- const linkedModifier = node as LinkedModifierNode
- return (linkedModifier.v || linkedModifier.value) as MessageType
+ return resolveValue(node as NodeValue, type)
}
default:
- throw new Error(`unhandled node type on format message part: ${type}`)
+ throw new Error(`unhandled node on format message part: ${type}`)
+ }
+}
+
+const PROPS_TYPE = ['t', 'type']
+
+export function resolveType(node: Node) {
+ return resolveProps(node, PROPS_TYPE)
+}
+
+const PROPS_VALUE = ['v', 'value']
+
+function resolveValue(
+ node: { v?: MessageType; value?: MessageType },
+ type: NodeTypes
+): MessageType {
+ const resolved = resolveProps(
+ node as Node,
+ PROPS_VALUE
+ ) as MessageType
+ if (resolved) {
+ return resolved
+ } else {
+ throw createUnhandleNodeError(type)
+ }
+}
+
+const PROPS_MODIFIER = ['m', 'modifier']
+
+function resolveLinkedModifier(node: LinkedNode) {
+ return resolveProps(node, PROPS_MODIFIER)
+}
+
+const PROPS_KEY = ['k', 'key']
+
+function resolveLinkedKey(node: LinkedNode) {
+ const resolved = resolveProps(node, PROPS_KEY)
+ if (resolved) {
+ return resolved
+ } else {
+ throw createUnhandleNodeError(NodeTypes.Linked)
}
}
+
+function resolveProps(
+ node: Node,
+ props: string[],
+ defaultValue?: Default
+): T | Default {
+ for (let i = 0; i < props.length; i++) {
+ const prop = props[i]
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if (hasOwn(node, prop) && (node as any)[prop] != null) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return (node as any)[prop] as T
+ }
+ }
+ return defaultValue as Default
+}
+
+function createUnhandleNodeError(type: NodeTypes) {
+ return new Error(`unhandled node type: ${type}`)
+}
diff --git a/packages/core-base/test/format.test.ts b/packages/core-base/test/format.test.ts
index aa6794262..3709a1282 100644
--- a/packages/core-base/test/format.test.ts
+++ b/packages/core-base/test/format.test.ts
@@ -1,7 +1,24 @@
-import { baseCompile as compile } from '@intlify/message-compiler'
-import { format } from '../src/format'
+import { baseCompile as compile, NodeTypes } from '@intlify/message-compiler'
+import {
+ format,
+ formatMessagePart,
+ formatMessageParts,
+ formatParts
+} from '../src/format'
import { createMessageContext as context } from '../src/runtime'
+import type {
+ LinkedKeyNode,
+ LinkedModifierNode,
+ LinkedNode,
+ ListNode,
+ LiteralNode,
+ MessageNode,
+ NamedNode,
+ ResourceNode,
+ TextNode
+} from '@intlify/message-compiler'
+
describe('features', () => {
test('text: hello world', () => {
const { ast } = compile('hello world', { jit: true })
@@ -143,3 +160,456 @@ describe('edge cases', () => {
expect(msg(ctx)).toBe('')
})
})
+
+describe('formatParts', () => {
+ test('prop: body', () => {
+ const node: ResourceNode = {
+ type: NodeTypes.Resource,
+ body: {
+ type: NodeTypes.Message,
+ items: [
+ {
+ type: NodeTypes.Text,
+ value: 'hello world'
+ }
+ ]
+ }
+ }
+
+ const ctx = context()
+ expect(formatParts(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: b', () => {
+ const node: ResourceNode = {
+ type: NodeTypes.Resource,
+ body: {
+ type: NodeTypes.Message,
+ items: [
+ {
+ type: NodeTypes.Text,
+ value: 'hello world'
+ }
+ ]
+ }
+ }
+
+ const ctx = context()
+ expect(formatParts(ctx, node)).toBe('hello world')
+ })
+
+ test(`body has plural prop cases`, () => {
+ const node: ResourceNode = {
+ type: NodeTypes.Resource,
+ body: {
+ type: NodeTypes.Plural,
+ cases: [
+ {
+ type: NodeTypes.Message,
+ items: [
+ {
+ type: NodeTypes.Text,
+ value: 'hello'
+ }
+ ]
+ },
+ {
+ type: NodeTypes.Message,
+ items: [
+ {
+ type: NodeTypes.Text,
+ value: 'world'
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+ const ctx = context({
+ pluralIndex: 2
+ })
+ expect(formatParts(ctx, node)).toBe('world')
+ })
+
+ test(`body has plural prop c`, () => {
+ const node: ResourceNode = {
+ type: NodeTypes.Resource,
+ // @ts-ignore
+ body: {
+ type: NodeTypes.Plural,
+ c: [
+ {
+ type: NodeTypes.Message,
+ items: [
+ {
+ type: NodeTypes.Text,
+ value: 'hello'
+ }
+ ]
+ },
+ {
+ type: NodeTypes.Message,
+ items: [
+ {
+ type: NodeTypes.Text,
+ value: 'world'
+ }
+ ]
+ }
+ ]
+ }
+ }
+
+ const ctx = context({
+ pluralIndex: 1
+ })
+ expect(formatParts(ctx, node)).toBe('hello')
+ })
+
+ test('not found prop body', () => {
+ // @ts-ignore
+ const node: ResourceNode = {
+ type: NodeTypes.Resource
+ }
+
+ const ctx = context()
+ expect(() => formatParts(ctx, node)).toThrow(
+ `unhandled node type: ${NodeTypes.Resource}`
+ )
+ })
+})
+
+describe('formatMessageParts', () => {
+ test('prop: static', () => {
+ const node: MessageNode = {
+ type: NodeTypes.Message,
+ static: 'hello world',
+ items: []
+ }
+ const ctx = context()
+ expect(formatMessageParts(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: s', () => {
+ const node: MessageNode = {
+ type: NodeTypes.Message,
+ s: 'hello world',
+ items: []
+ }
+ const ctx = context()
+ expect(formatMessageParts(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: items', () => {
+ const node: MessageNode = {
+ type: NodeTypes.Message,
+ items: [
+ {
+ type: NodeTypes.Text,
+ value: 'hello'
+ },
+ {
+ type: NodeTypes.Text,
+ value: 'world'
+ }
+ ]
+ }
+ const ctx = context()
+ expect(formatMessageParts(ctx, node)).toEqual('helloworld')
+ })
+
+ test('prop: i', () => {
+ // @ts-ignore
+ const node: MessageNode = {
+ type: NodeTypes.Message,
+ i: [
+ {
+ type: NodeTypes.Text,
+ value: 'hello'
+ },
+ {
+ type: NodeTypes.Text,
+ value: 'world'
+ }
+ ]
+ }
+ const ctx = context()
+ expect(formatMessageParts(ctx, node)).toEqual('helloworld')
+ })
+})
+
+describe('formatMessagePart', () => {
+ describe('text node', () => {
+ test('prop: value', () => {
+ const node: TextNode = {
+ type: NodeTypes.Text,
+ value: 'hello world'
+ }
+ const ctx = context()
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: v', () => {
+ const node: TextNode = {
+ type: NodeTypes.Text,
+ v: 'hello world'
+ }
+ const ctx = context()
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test(`prop 'value' and 'v' not found`, () => {
+ const node: TextNode = {
+ type: NodeTypes.Text
+ }
+ const ctx = context()
+ expect(() => formatMessagePart(ctx, node)).toThrow(
+ `unhandled node type: ${NodeTypes.Text}`
+ )
+ })
+ })
+
+ describe('literal node', () => {
+ test('prop: value', () => {
+ const node: LiteralNode = {
+ type: NodeTypes.Literal,
+ value: 'hello world'
+ }
+ const ctx = context()
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: v', () => {
+ const node: LiteralNode = {
+ type: NodeTypes.Literal,
+ v: 'hello world'
+ }
+ const ctx = context()
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test(`prop 'value' and 'v' not found`, () => {
+ const node: LiteralNode = {
+ type: NodeTypes.Literal
+ }
+ const ctx = context()
+ expect(() => formatMessagePart(ctx, node)).toThrow(
+ `unhandled node type: ${NodeTypes.Literal}`
+ )
+ })
+ })
+
+ describe('named node', () => {
+ test('prop: key', () => {
+ const node: NamedNode = {
+ type: NodeTypes.Named,
+ key: 'key'
+ }
+ const ctx = context({
+ named: { key: 'hello world' }
+ })
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: k', () => {
+ // @ts-ignore
+ const node: NamedNode = {
+ type: NodeTypes.Named,
+ k: 'key'
+ }
+ const ctx = context({
+ named: { key: 'hello world' }
+ })
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test(`prop 'key' and 'k' not found`, () => {
+ // @ts-ignore
+ const node: NamedNode = {
+ type: NodeTypes.Named
+ }
+ const ctx = context()
+ expect(() => formatMessagePart(ctx, node)).toThrow(
+ `unhandled node type: ${NodeTypes.Named}`
+ )
+ })
+ })
+
+ describe('list node', () => {
+ test('prop: index', () => {
+ const node: ListNode = {
+ type: NodeTypes.List,
+ index: 0
+ }
+ const ctx = context({
+ list: ['hello world']
+ })
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: i', () => {
+ // @ts-ignore
+ const node: ListNode = {
+ type: NodeTypes.List,
+ i: 0
+ }
+ const ctx = context({
+ list: ['hello world']
+ })
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test(`prop 'index' and 'i' not found`, () => {
+ // @ts-ignore
+ const node: ListNode = {
+ type: NodeTypes.List
+ }
+ const ctx = context()
+ expect(() => formatMessagePart(ctx, node)).toThrow(
+ `unhandled node type: ${NodeTypes.List}`
+ )
+ })
+ })
+
+ describe('linked key node', () => {
+ test('prop: value', () => {
+ const node: LinkedKeyNode = {
+ type: NodeTypes.LinkedKey,
+ value: 'hello world'
+ }
+ const ctx = context()
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: v', () => {
+ // @ts-ignore
+ const node: LinkedKeyNode = {
+ type: NodeTypes.LinkedKey,
+ v: 'hello world'
+ }
+ const ctx = context()
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test(`prop 'value' and 'v' not found`, () => {
+ // @ts-ignore
+ const node: LinkedKeyNode = {
+ type: NodeTypes.LinkedKey
+ }
+ const ctx = context()
+ expect(() => formatMessagePart(ctx, node)).toThrow(
+ `unhandled node type: ${NodeTypes.LinkedKey}`
+ )
+ })
+ })
+
+ describe('linked modifier node', () => {
+ test('prop: value', () => {
+ const node: LinkedModifierNode = {
+ type: NodeTypes.LinkedModifier,
+ value: 'hello world'
+ }
+ const ctx = context()
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test('prop: v', () => {
+ // @ts-ignore
+ const node: LinkedModifierNode = {
+ type: NodeTypes.LinkedModifier,
+ v: 'hello world'
+ }
+ const ctx = context()
+ expect(formatMessagePart(ctx, node)).toBe('hello world')
+ })
+
+ test(`prop 'value' and 'v' not found`, () => {
+ // @ts-ignore
+ const node: LinkedModifierNode = {
+ type: NodeTypes.LinkedModifier
+ }
+ const ctx = context()
+ expect(() => formatMessagePart(ctx, node)).toThrow(
+ `unhandled node type: ${NodeTypes.LinkedModifier}`
+ )
+ })
+ })
+
+ describe('linked node', () => {
+ test('prop: modifier, key', () => {
+ const node: LinkedNode = {
+ type: NodeTypes.Linked,
+ modifier: {
+ type: NodeTypes.LinkedModifier,
+ value: 'upper'
+ },
+ key: {
+ type: NodeTypes.LinkedKey,
+ value: 'name'
+ }
+ }
+ const ctx = context({
+ modifiers: {
+ upper: (val: string) => val.toUpperCase()
+ },
+ messages: {
+ name: () => 'kazupon'
+ }
+ })
+ expect(formatMessagePart(ctx, node)).toBe('KAZUPON')
+ })
+
+ test('prop: m, k', () => {
+ // @ts-ignore
+ const node: LinkedNode = {
+ type: NodeTypes.Linked,
+ m: {
+ type: NodeTypes.LinkedModifier,
+ value: 'upper'
+ },
+ k: {
+ type: NodeTypes.LinkedKey,
+ value: 'name'
+ }
+ }
+ const ctx = context({
+ modifiers: {
+ upper: (val: string) => val.toUpperCase()
+ },
+ messages: {
+ name: () => 'kazupon'
+ }
+ })
+ expect(formatMessagePart(ctx, node)).toBe('KAZUPON')
+ })
+
+ test(`prop 'key' not found`, () => {
+ // @ts-ignore
+ const node: LinkedNode = {
+ type: NodeTypes.Linked
+ }
+ const ctx = context({
+ modifiers: {
+ upper: (val: string) => val.toUpperCase()
+ },
+ messages: {
+ name: () => 'kazupon'
+ }
+ })
+ expect(() => formatMessagePart(ctx, node)).toThrow(
+ `unhandled node type: ${NodeTypes.Linked}`
+ )
+ })
+ })
+
+ test('unhandled node', () => {
+ const node = {
+ type: -1
+ }
+ const ctx = context()
+ expect(() => formatMessagePart(ctx, node)).toThrow(
+ `unhandled node on format message part: -1`
+ )
+ })
+})
diff --git a/packages/core-base/test/issues.test.ts b/packages/core-base/test/issues.test.ts
new file mode 100644
index 000000000..1a39aa0f5
--- /dev/null
+++ b/packages/core-base/test/issues.test.ts
@@ -0,0 +1,58 @@
+import { format } from '../src/format'
+import { createMessageContext as context } from '../src/runtime'
+
+import { NodeTypes, ResourceNode } from '@intlify/message-compiler'
+
+describe('CVE-2024-52809', () => {
+ function attackGetter() {
+ return 'polluted'
+ }
+
+ afterEach(() => {
+ // @ts-ignore -- initialize polluted property
+ delete Object.prototype.static
+ })
+
+ test('success', () => {
+ Object.defineProperty(Object.prototype, 'static', {
+ configurable: true,
+ get: attackGetter
+ })
+ const ast: ResourceNode = {
+ type: NodeTypes.Resource,
+ body: {
+ type: NodeTypes.Message,
+ static: 'hello world',
+ items: [
+ {
+ type: NodeTypes.Text
+ }
+ ]
+ }
+ }
+ const msg = format(ast)
+ const ctx = context()
+ expect(msg(ctx)).toEqual('hello world')
+ })
+
+ test('error', () => {
+ Object.defineProperty(Object.prototype, 'static', {
+ configurable: true,
+ get: attackGetter
+ })
+ const ast: ResourceNode = {
+ type: NodeTypes.Resource,
+ body: {
+ type: NodeTypes.Message,
+ items: [
+ {
+ type: NodeTypes.Text
+ }
+ ]
+ }
+ }
+ const msg = format(ast)
+ const ctx = context()
+ expect(() => msg(ctx)).toThrow(`unhandled node type: ${NodeTypes.Text}`)
+ })
+})
diff --git a/packages/vue-i18n-core/test/issues.test.ts b/packages/vue-i18n-core/test/issues.test.ts
index 552aa9dcb..8ca2c22d1 100644
--- a/packages/vue-i18n-core/test/issues.test.ts
+++ b/packages/vue-i18n-core/test/issues.test.ts
@@ -1406,7 +1406,7 @@ test('#1912', async () => {
expect(el?.innerHTML).include(`No apples found`)
})
-test('#1972', async () => {
+test('#1972', () => {
const i18n = createI18n({
legacy: false,
locale: 'en',
@@ -1418,3 +1418,71 @@ test('#1972', async () => {
})
expect(i18n.global.t('test', 0)).toEqual('')
})
+
+describe('CVE-2024-52809', () => {
+ function attackGetter() {
+ return 'polluted'
+ }
+
+ afterEach(() => {
+ // @ts-ignore -- initialize polluted property
+ delete Object.prototype.static
+ })
+
+ test('success', () => {
+ Object.defineProperty(Object.prototype, 'static', {
+ configurable: true,
+ get: attackGetter
+ })
+ const en = {
+ hello: {
+ type: 0,
+ body: {
+ type: 2,
+ static: 'hello world',
+ items: [
+ {
+ type: 3
+ }
+ ]
+ }
+ }
+ }
+ const i18n = createI18n({
+ legacy: false,
+ locale: 'en',
+ messages: {
+ en
+ }
+ })
+ expect(i18n.global.t('hello')).toEqual('hello world')
+ })
+
+ test('error', () => {
+ Object.defineProperty(Object.prototype, 'static', {
+ configurable: true,
+ get: attackGetter
+ })
+ const en = {
+ hello: {
+ type: 0,
+ body: {
+ type: 2,
+ items: [
+ {
+ type: 3
+ }
+ ]
+ }
+ }
+ }
+ const i18n = createI18n({
+ legacy: false,
+ locale: 'en',
+ messages: {
+ en
+ }
+ })
+ expect(() => i18n.global.t('hello')).toThrow(`unhandled node type: 3`)
+ })
+})