Skip to content

[BUG]: Leak detection hang when ServerResponse.end throws on leaked secret #897

Description

@cursor

varlock version

current main (packages/varlock/src/runtime/patch-server-response.ts)

Steps to reproduce

  1. Use Next.js Pages Router (or any path that calls res.end(body) / res.json(...) without going through res.write).
  2. Enable leak prevention (default @preventLeaks).
  3. Return a response body that contains a @sensitive resolved value (for example an API route that does res.status(200).json({ token: process.env.SECRET }) or equivalent via ENV).
  4. Hit that route.

Related code (maintainer TODO already notes the hang):

// packages/varlock/src/runtime/patch-server-response.ts
ServerResponse.prototype.end = function patchedServerResponseEnd(...args) {
  const endChunk = args[0];
  if (endChunk && typeof endChunk === 'string') {
    // TODO: currently this throws the error and then things just hang... do we want to try to return an error type response instead?
    scanForLeaks(endChunk, { method: 'patched ServerResponse.end' });
  }
  return serverResponseEnd.apply(this, args);
};

scanForLeaks throws on detection (🚨 DETECTED LEAKED SENSITIVE CONFIG - ${itemKey}), so serverResponseEnd never runs.

What is expected?

Leak is blocked and the HTTP response finishes cleanly: client gets a clear error status/body (or connection is destroyed), terminal still shows the leak banner, and the request does not hang.

What is actually happening?

The throw prevents ServerResponse.end from completing. The client request can hang/spin while the secret is blocked from being written. Detection works; UX/failure mode does not.

Contrast with the patched write path, which at least has redactInsteadOfThrow handling (and for compressed streams intentionally fails closed). The end path has neither a catch nor a safe terminal response.

Proposed fix direction

In patchedServerResponseEnd, catch the leak error and finish the response safely before returning, for example:

  • if headers not sent: statusCode = 500, write a non-secret error body, then call original end
  • else: this.destroy() / destroy the socket so the client is not left waiting
  • keep logging via existing scanForLeaks console output
  • honor redactInsteadOfThrow on end the same way write does when scrubbing is possible (string chunks)

Add coverage in packages/varlock/src/runtime/test/patch-server-response.test.ts (and ideally a Next Pages Router framework test that asserts the request completes).

System Info

N/A (code-path review). Affects Next.js Pages Router JSON responses and any other stack that ends responses via res.end(string) without write.

Any additional comments?

Found while scanning for contribution opportunities. Happy to take a PR if this direction looks right. Related: outgoing response leak scanning; distinct from the open feature request to re-implement HTTP request leak detection (#169).

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