Information disclosure via logs (log leakage)

MEDIUM
vercel/next.js
Commit: a5f659034626
Affected: Next.js 16.2.0 through 16.2.1 (i.e., releases prior to the patch included in 16.2.2).
2026-04-04 06:41 UTC

Description

The commit mitigates an information disclosure vulnerability by suppressing logging for 'use cache' server functions in development. Previously, when a client invoked a server function marked with the 'use cache' pattern, server logs could reveal internal identifiers (for example $$RSC_SERVER_CACHE_0) and the encoded bound arguments. This would leak internal implementation details through logs that developers or log collectors could access. The fix adds a guard so that such use-cache actions do not emit verbose internal logs, reducing the potential exposure.

Proof of Concept

Proof-of-concept (pre-patch exploit against logs): Prerequisites: - Next.js 16.2.x in development with server function logging enabled (logServerFunctions in development). - A server action intended to be used with caching and invoked from the client. 1) Define a server action with useCache in app dir (pseudo syntax): // app/actions/myAction.server.ts 'use server' export async function myAction(input: string) { return input.toUpperCase() } // Client call invoking a use-cache server action (pseudo syntax): // app/page.tsx 'use client' import { myAction } from './actions/myAction.server' export default async function Page() { // Trigger a use-cache call from client await myAction.useCache('secret-token') } 2) Trigger the action by visiting the page in dev. 3) Observe logs in the terminal/stdout. Before this patch, you would see something like: POST /some-endpoint └─ ƒ myAction (use-cache) { internalId: '$$RSC_SERVER_CACHE_0', boundArgs: 'encoded...'} This reveals internal identifiers and encoded bound arguments, constituting information disclosure. 4) After applying this patch (the fix in this commit), logs for use-cache actions are suppressed, and you should only see the HTTP request line (e.g., POST /some-endpoint) without the function line. Notes: - The PoC focuses on log output exposure, not on remote code execution. Access to logs is the prerequisite for exploitation.

Commit Details

Author: Hendrik Liebau

Date: 2026-02-02 19:26 UTC

Message:

Skip Server Function logging for `'use cache'` functions (#89408) Follow-up to #89407. Disables Server Function logging for `'use cache'` functions called from the client. The logging output for these Server Functions needs more work to be useful. Currently it shows internal details like `$$RSC_SERVER_CACHE_0` instead of the actual function name, as well as the encoded bound args promise.

Triage Assessment

Vulnerability Type: Information disclosure

Confidence: MEDIUM

Reasoning:

The commit disables logging for certain 'use cache' Server Functions to avoid leaking internal details (like internal identifiers and bound args) in logs. This reduces potential information disclosure exposure. While not fixing a direct attack vector, it mitigates an information leakage vulnerability via logs.

Verification Assessment

Vulnerability Type: Information disclosure via logs (log leakage)

Confidence: MEDIUM

Affected Versions: Next.js 16.2.0 through 16.2.1 (i.e., releases prior to the patch included in 16.2.2).

Code Diff

diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index cd1f11d4acc2ac..b183998acd1904 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -58,6 +58,7 @@ import { } from './manifests-singleton' import { isNodeNextRequest, isWebNextRequest } from '../base-http/helpers' import { normalizeFilePath } from './segment-explorer-path' +import { extractInfoFromServerReferenceId } from '../../shared/lib/server-reference-info' import type { ServerActionLogInfo } from '../dev/server-action-logger' import { RedirectStatusCode } from '../../client/components/redirect-status-code' import { synchronizeMutableCookies } from '../async-storage/request-store' @@ -1086,9 +1087,13 @@ export async function handleAction({ // Log server action call in development when enabled let logInfo: ServerActionLogInfo | null = null + const { type: actionType } = extractInfoFromServerReferenceId(actionId!) if ( process.env.NODE_ENV === 'development' && - ctx.renderOpts.logServerFunctions + ctx.renderOpts.logServerFunctions && + // TODO: For now, skip logging for 'use cache' Server Functions as the + // output needs more work, or a different approach entirely. + actionType !== 'use-cache' ) { const serverActionsManifest = getServerActionsManifest() const runtime = process.env.NEXT_RUNTIME === 'edge' ? 'edge' : 'node' diff --git a/test/e2e/app-dir/use-cache/use-cache.test.ts b/test/e2e/app-dir/use-cache/use-cache.test.ts index 5caa8ea6d74cad..859326adb46736 100644 --- a/test/e2e/app-dir/use-cache/use-cache.test.ts +++ b/test/e2e/app-dir/use-cache/use-cache.test.ts @@ -1419,6 +1419,22 @@ describe('use-cache', () => { const numbers = await browser.elementById('numbers').text() expect(numbers).toBe(initialNumbers) }) + + if (isNextDev) { + it('should not log "use cache" functions called from client', async () => { + const browser = await next.browser('/passed-to-client') + const outputIndex = next.cliOutput.length + + await browser.elementByCss('#submit-button').click() + + await retry(() => { + const logs = stripAnsi(next.cliOutput.slice(outputIndex)) + // Should have the POST request but not the function log + expect(logs).toContain('POST /passed-to-client') + expect(logs).not.toContain('└─ ƒ') + }) + }) + } }) async function getSanitizedLogs(browser: Playwright): Promise<string[]> {
← Back to Alerts View on GitHub →