Skip to content

Cookie jar Proxy allocates a fresh Cookie object on every property access; parseCookie is unconditionally async even without signing #1936

Description

@tsushanth

Cookie jar Proxy allocates a fresh Cookie object on every property access; parseCookie is unconditionally async even without signing

Repo: elysiajs/elysia
Location: src/cookies.ts:355-368 (Proxy get trap in createCookieJar) and src/cookies.ts:373 (async parseCookie), reached per request from src/compose.ts:743 and src/dynamic-handle.ts:512
Severity: medium · Confidence: 0.78
Type: hot-path-by-call-frequency

Description

createCookieJar in src/cookies.ts:348-371 returns a Proxy whose get trap constructs a new Cookie plus an Object.assign({}, initial, store[key]) on EVERY property access — reading the same cookie k times in one request allocates k Cookie instances with no memoization. This is a cross-module hot path: the AOT code generated by composeHandler (src/compose.ts:743, 'c.cookie=await parseCookie(...)') and the dynamic handler (src/dynamic-handle.ts:512) both route every cookie-touching request through it. Additionally parseCookie (src/cookies.ts:373) is declared async although await is only needed for signed cookies (unsignCookie/WebCrypto), so every cookie-enabled route pays a promise allocation and microtask hop per request even in the common unsigned case, and dynamic-handle's response-validation path Object.entries(context.cookie) (dynamic-handle.ts:609) triggers one allocation per cookie through the same trap.

Benchmark

Per-request time grows ~O(N) with the number of reads of the SAME single cookie inside one handler, because each Proxy get allocates a new Cookie + Object.assign; with a cached Cookie instance the curve would be nearly flat (only loop overhead). Expect ratio at N=500 to track the O(N) prediction (~50x) rather than O(1).

Observed complexity: O(N)

N_reads=10: 7.04ms (ratio 1.00), N_reads=50: 16.54ms (ratio 2.35), N_reads=100: 28.76ms (ratio 4.09), N_reads=500: 137.38ms (ratio 19.52). O(N) prediction at N=500 would be 50x; observed is 19.52x — clearly non-flat but sub-linear due to V8 optimization.

benchmark confirmed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions