Skip to content

fix: don't write POST body to a request that will be hijacked for stdin - #198

Open
mliem2k wants to merge 1 commit into
apocas:masterfrom
mliem2k:fix/attach-hijack-body-leak
Open

fix: don't write POST body to a request that will be hijacked for stdin#198
mliem2k wants to merge 1 commit into
apocas:masterfrom
mliem2k:fix/attach-hijack-body-leak

Conversation

@mliem2k

@mliem2k mliem2k commented Jul 16, 2026

Copy link
Copy Markdown

Summary

Modem#dial always JSON-stringifies its options object for POST requests and buildRequest() unconditionally req.write()s it, with no guard for the openStdin/hijack case. For container.attach() / exec.start() calls, that write happens before the HTTP connection upgrades to a raw duplex socket, so the serialized options object gets delivered straight into the remote container's stdin.

buildRequest() already skips req.end() in this case; this PR extends the same !context.openStdin guard to the req.write()/.pipe() call above it, and adds an explicit req.flushHeaders() for that branch, since the write being removed was also implicitly what flushed the request headers to the socket (without it, an openStdin request would hang instead of upgrading).

Found via a downstream MCSManager bug report where users saw the literal attach-options JSON auto-typed into their container's terminal on every start: MCSManager/MCSManager#2136

Test plan

  • Added a regression test that spins up a real HTTP server, upgrades the connection, and asserts no body bytes arrive before the upgrade completes; fails against unpatched v5.0.6, passes with the fix.
  • Existing npm test suite (19/19) still passes.

Modem#dial always JSON-stringifies its options object and
buildRequest() unconditionally req.write()s it for POST requests.
For attach()/exec-start() calls with hijack:true + openStdin:true,
that write happens before the HTTP connection upgrades to a raw
duplex stream, so the serialized options end up delivered to the
remote container's stdin as if a user had typed them.

buildRequest() already skips req.end() in this case; extend the
same !context.openStdin guard to the req.write()/pipe() call above
it, since attach/exec-start never need a body (their parameters are
query-string based).

Skipping the write also means the request headers are no longer
implicitly flushed by that write() call, so the connection would
otherwise never reach the server to upgrade. Add an explicit
req.flushHeaders() for the openStdin case to send the headers
without a body.
Copilot AI review requested due to automatic review settings July 16, 2026 05:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants