varlock version
current main (packages/varlock/src/runtime/patch-server-response.ts)
Steps to reproduce
- Use Next.js Pages Router (or any path that calls
res.end(body) / res.json(...) without going through res.write).
- Enable leak prevention (default
@preventLeaks).
- 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).
- 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).
varlock version
current main (
packages/varlock/src/runtime/patch-server-response.ts)Steps to reproduce
res.end(body)/res.json(...)without going throughres.write).@preventLeaks).@sensitiveresolved value (for example an API route that doesres.status(200).json({ token: process.env.SECRET })or equivalent viaENV).Related code (maintainer TODO already notes the hang):
scanForLeaksthrows on detection (🚨 DETECTED LEAKED SENSITIVE CONFIG - ${itemKey}), soserverResponseEndnever 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.endfrom 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
writepath, which at least hasredactInsteadOfThrowhandling (and for compressed streams intentionally fails closed). Theendpath 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:statusCode = 500, write a non-secret error body, then call originalendthis.destroy()/ destroy the socket so the client is not left waitingscanForLeaksconsole outputredactInsteadOfThrowonendthe same waywritedoes 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)withoutwrite.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).