VYPR
Moderate severityNVD Advisory· Published Mar 18, 2026· Updated Mar 18, 2026

Elysia Cookie Value Prototype Pollution

CVE-2026-31865

Description

Elysia is a Typescript framework for request validation, type inference, OpenAPI documentation, and client-server communication. Prior to version 1.4.27, an Elysia cookie can be overridden by prototype pollution , eg. __proto__. This issue is patched in 1.4.27. As a workaround, use t.Cookie validation to enforce validation value and/or prevent iterable over cookie if possible.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
elysianpm
< 1.4.271.4.27

Affected products

1

Patches

1
e9d6b1743fa7

:tada: feat: 1.4.27

https://github.com/elysiajs/elysiasaltyaomMar 1, 2026via ghsa
7 files changed · +62 14
  • CHANGELOG.md+2 0 modified
    @@ -1,10 +1,12 @@
     # 1.4.27 - 1 Mar 2025
     Bug fix:
     - getSchemaValidator: handle TypeBox as sub type
    +- handle cookie prototype pollution when parsing cookie
     
     Improvement:
     - conditional async on getSchemaValidator when schema is Standard Schema
     - use Response.json on Bun
    +- export `AnySchema`, `UnwrapSchema`, `ModelsToTypes` from root
     
     # 1.4.26 - 25 Feb 2025
     Bug fix:
    
  • example/a.ts+5 3 modified
    @@ -2,6 +2,8 @@ import { Elysia, t, getSchemaValidator, fileType } from '../src'
     import { z } from 'zod'
     import { post } from '../test/utils'
     
    -console.log(Response.json({
    -	a: 'a'
    -}))
    +console.log(
    +	Response.json({
    +		a: 'a'
    +	})
    +)
    
  • package.json+1 1 modified
    @@ -1,7 +1,7 @@
     {
     	"name": "elysia",
     	"description": "Ergonomic Framework for Human",
    -	"version": "1.4.26",
    +	"version": "1.4.27",
     	"author": {
     		"name": "saltyAom",
     		"url": "https://github.com/SaltyAom",
    
  • src/cookies.ts+14 9 modified
    @@ -150,7 +150,7 @@ export class Cookie<T> implements ElysiaCookie {
     	constructor(
     		private name: string,
     		private jar: Record<string, ElysiaCookie>,
    -		private initial: Partial<ElysiaCookie> = {}
    +		private initial: Partial<ElysiaCookie> = Object.create(null)
     	) {}
     
     	get cookie() {
    @@ -350,7 +350,7 @@ export const createCookieJar = (
     	store: Record<string, ElysiaCookie>,
     	initial?: Partial<ElysiaCookie>
     ): Record<string, Cookie<unknown>> => {
    -	if (!set.cookie) set.cookie = {}
    +	if (!set.cookie) set.cookie = Object.create(null)
     
     	return new Proxy(store, {
     		get(_, key: string) {
    @@ -379,18 +379,24 @@ export const parseCookie = async (
     		...initial
     	}: CookieOptions & {
     		sign?: true | string | string[]
    -	} = {}
    +	} = Object.create(null)
     ) => {
    -	if (!cookieString) return createCookieJar(set, {}, initial)
    +	if (!cookieString) return createCookieJar(set, Object.create(null), initial)
     
     	const isStringKey = typeof secrets === 'string'
     	if (sign && sign !== true && !Array.isArray(sign)) sign = [sign]
     
    -	const jar: Record<string, ElysiaCookie> = {}
    +	const jar: Record<string, ElysiaCookie> = Object.create(null)
     
     	const cookies = parse(cookieString)
     	for (const [name, v] of Object.entries(cookies)) {
    -		if (v === undefined) continue
    +		if (
    +			v === undefined ||
    +			name === '__proto__' ||
    +			name === 'constructor' ||
    +			name === 'prototype'
    +		)
    +			continue
     
     		let value = decode(v)
     
    @@ -440,9 +446,8 @@ export const parseCookie = async (
     				} catch {}
     		}
     
    -		jar[name] = {
    -			value
    -		}
    +		jar[name] = Object.create(null)
    +		jar[name].value = value
     	}
     
     	return createCookieJar(set, jar, initial)
    
  • src/index.ts+2 1 modified
    @@ -164,7 +164,8 @@ import type {
     	UnknownRouteSchema,
     	MaybeFunction,
     	InlineHandlerNonMacro,
    -	Router
    +	Router,
    +	ModelsToTypes
     } from './types'
     import {
     	coercePrimitiveRoot,
    
  • src/types.ts+4 0 modified
    @@ -2711,3 +2711,7 @@ export interface Router {
     	}
     	history: InternalRoute[]
     }
    +
    +export type ModelsToTypes<T extends Record<keyof any, AnySchema>> = {
    +	[K in keyof T]: UnwrapSchema<T[K]>
    +}
    
  • test/validator/cookie.test.ts+34 0 modified
    @@ -638,4 +638,38 @@ describe('Cookie Validation', () => {
     
     		expect(second.status).toBe(200)
     	})
    +
    +	it('handle prototype pollution', () => {
    +		const app = new Elysia().get('/profile', ({ cookie }) => {
    +			const proto = Object.getPrototypeOf(cookie)
    +			const protoIsClean = proto === Object.prototype || proto === null
    +			return {
    +				hasPhantomValue: 'value' in cookie,
    +				prototypeIsClean: protoIsClean,
    +				prototype: proto,
    +				enumeratedKeys: (() => {
    +					const keys: string[] = []
    +					for (const k in cookie) keys.push(k)
    +					return keys
    +				})()
    +			}
    +		})
    +
    +		expect(
    +			app
    +				.handle(
    +					new Request('http://localhost/profile', {
    +						headers: {
    +							cookie: 'a=hi;__proto__=%7B%22injected%22%3A%22polluted%22%7D'
    +						}
    +					})
    +				)
    +				.then((x) => x.json())
    +		).resolves.toEqual({
    +			hasPhantomValue: false,
    +			prototypeIsClean: true,
    +			prototype: null,
    +			enumeratedKeys: ['a']
    +		})
    +	})
     })
    

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

4

News mentions

0

No linked articles in our index yet.