x11 screen number silently ignored: opts.x11 compared against the string 'number' in exec() and shell()
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 5.8k
- Forks
- 734
- PR merge metrics
- No merged PRs in 30d
Description
Summary
In both exec() and shell(), the check that decides whether to send an x11-req compares opts.x11 against the string 'number' rather than using typeof. As a result, passing a numeric screen number — which the TypeScript declarations document as supported — silently does nothing: no x11-req is sent, no error is raised, and X11 forwarding simply never happens.
Version: 1.17.0 (latest published). Also present on master at the time of writing, at the same lines.
The code
lib/client.js, in exec() at 1235-1238 and identically in shell() at 1296-1299:
if ((typeof opts.x11 === 'object' && opts.x11 !== null)
|| opts.x11 === 'number' // <-- compares against the string "number"
|| opts.x11 === true) {
todo.push(() => reqX11(chan, opts.x11, reqCb));
}
opts.x11 === 'number' is true only when the caller literally passes the string 'number'. A real screen number such as 0 or 1 falls through all three branches, so reqX11 is never queued.
Why this looks supported
@types/ssh2 declares it as accepted, in both option types:
export interface ExecOptions {
/** Set either to `true` to use defaults, a number to specify a specific screen number, or an object containing x11 settings. */
x11?: X11Options | number | boolean;
}
export interface ShellOptions {
/** Set either to `true` to use defaults, a number to specify a specific screen number, or an object containing x11 settings. */
x11?: X11Options | number | boolean;
}
So a caller following the types passes a number, gets no error, and no forwarding.
Reproduce
Against any server with X11Forwarding yes:
// Silently no-ops: no x11-req is sent.
conn.shell({ x11: 0 }, (err, stream) => { /* remote DISPLAY is unset */ });
// Works.
conn.shell({ x11: true }, (err, stream) => { /* … */ });
conn.shell({ x11: { screen: 0 } }, (err, stream) => { /* … */ });
The difference is observable on the remote side: echo $DISPLAY is empty in the first case and set in the other two.
Suggested fix
if ((typeof opts.x11 === 'object' && opts.x11 !== null)
|| typeof opts.x11 === 'number'
|| opts.x11 === true) {
reqX11 already ignores a bare number — it only reads single, screen, protocol and cookie from an object — so a caller passing a number would get defaults rather than their requested screen. If the intent is that numbers were never meant to be supported, the fix would instead be to drop number from the two type declarations; either way the code and the types currently disagree.
Related, and possibly documentation rather than a bug
shell() reassigns its arguments at 1259-1262:
if (wndopts && (wndopts.x11 !== undefined || wndopts.env !== undefined)) {
opts = wndopts;
wndopts = undefined;
}
This is presumably how shell(opts, cb) is disambiguated from shell(wndopts, opts, cb). The consequence is that putting x11 (or env) into the window options object silently discards term, rows and cols, and the session gets a default PTY instead.
Measured against a real sshd:
shell({ term, cols: 203, rows: 47 }, cb) -> TERM=xterm-256color 47x203
shell({ term, cols: 203, rows: 47, x11: {...} }, cb) -> TERM=vt100 24x80
shell({ term, cols: 203, rows: 47 }, { x11: {...} }, cb) -> TERM=xterm-256color 47x203
The three-argument form is correct and works. This is easy to get wrong when adding X11 to an existing shell() call that already passes window options, and the silent downgrade of the PTY is hard to attribute. A note in the docs would probably be enough.
Happy to open the type-declarations side separately against DefinitelyTyped if that's preferred.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in lib/client.js at exec() lines 1235-1238 and shell() lines 1296-1299, then inspect reqX11 to understand how numeric options are handled. Confirm the fix makes numeric x11 values reach the existing request path while preserving true and object behavior, and check the shell argument handling around lines 1259-1262 separately as documentation scope.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 82/100