FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add --terminal flag for WebSocket shell access by melvincarvalho · Pull Request #235 · JavaScriptSolidServer/JavaScriptSolidServer · GitHub

Repository navigation

Add --terminal flag for WebSocket shell access - #235

Merged
melvincarvalho merged 2 commits into
gh-pagesfrom
issue-234-terminal-endpoint
Mar 24, 2026
Merged

melvincarvalho merged 2 commits into
gh-pagesfrom
issue-234-terminal-endpoint

Conversation

Copy link
Copy Markdown
Contributor

Summary

  • Adds a --terminal CLI flag and JSS_TERMINAL env var to enable a WebSocket shell endpoint at /.terminal
  • Authenticated users can connect via WebSocket to get a /bin/sh session with stdin/stdout/stderr piped over the connection
  • Follows the same plugin pattern as WebRTC and tunnel (Fastify plugin, @fastify/websocket, getWebIdFromRequestAsync for auth)

Details

  • New file: src/terminal/index.js -- Fastify plugin that spawns a shell per authenticated WebSocket connection
  • bin/jss.js -- added --terminal / --no-terminal options and startup log line
  • src/config.js -- added terminal: false default and JSS_TERMINAL env mapping
  • src/server.js -- imports and registers terminalPlugin, adds dotfile and WAC bypass (auth is enforced inside the plugin itself)

Security

  • Authentication is required -- unauthenticated connections are rejected with an error and closed immediately
  • WAC bypass is necessary (same as WebRTC/tunnel) because the plugin handles its own token-based auth via getWebIdFromRequestAsync

Test plan

  • Start server with jss start --terminal and verify Terminal: enabled (/.terminal) appears in output
  • Connect with a valid auth token via WebSocket to /.terminal and verify shell commands work
  • Connect without auth and verify the connection is rejected with Authentication required
  • Verify closing the WebSocket kills the shell process
  • Verify server shutdown kills all active shells

Fixes #234

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Pull request overview

Adds an opt-in WebSocket terminal endpoint to JSS (enabled via --terminal / JSS_TERMINAL) and wires it through configuration, server routing, and CLI startup output. The PR also introduces a new jss passwd CLI command for changing IdP user passwords.

Changes:

  • Add src/terminal/index.js Fastify WebSocket plugin that spawns /bin/sh per authenticated connection on /.terminal.
  • Wire terminal feature flag through src/config.js, src/server.js (plugin registration + dotfile/WAC bypass), and bin/jss.js (CLI options + startup log).
  • Add jss passwd <username> command to update IdP account password hash on disk.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.

File Description
src/terminal/index.js New WebSocket terminal plugin that authenticates then spawns a shell and pipes data over WS.
src/server.js Registers terminal plugin when enabled and bypasses dotfile/WAC checks for /.terminal.
src/config.js Adds terminal default config and JSS_TERMINAL env mapping.
bin/jss.js Adds --terminal flag/config plumbing and a new passwd command.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/terminal/index.js
Comment on lines +53 to +56
// Authenticate — query param token support for browser WebSocket
const queryToken = request.query?.token;
if (queryToken && !request.headers.authorization) {
request.headers.authorization = `Bearer ${queryToken}`;

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This endpoint allows passing bearer tokens via ?token= and then copies them into the Authorization header. For a privileged shell endpoint, putting credentials in the URL is high-risk (tokens can leak via logs, proxies, referrers, browser history). Consider requiring the Authorization header (or using a safer mechanism like Sec-WebSocket-Protocol / first message auth) and documenting that query-string auth is not supported for /.terminal.

Suggested change
// Authenticate — query param token support for browser WebSocket
const queryToken = request.query?.token;
if (queryToken && !request.headers.authorization) {
request.headers.authorization = `Bearer ${queryToken}`;
// Authenticate — require Authorization header; query-string tokens are not supported
if (request.query?.token) {
socket.send(JSON.stringify({
type: 'error',
message: 'Query-string token authentication is not supported; use the Authorization header.',
}));
socket.close();
return;

Copilot uses AI. Check for mistakes.
Comment thread src/terminal/index.js
Comment on lines +109 to +115
// Pipe WebSocket messages to shell stdin
socket.on('message', (data) => {
if (shell.stdin.writable) {
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
try { shell.stdin.write(buf); } catch { /* stdin closed */ }
}
});

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Incoming WebSocket messages are forwarded to the shell without any size limit. A client can send very large frames and cause high memory/CPU usage or overwhelm the shell. Add a max message size check (like the WebRTC/tunnel plugins) and close the connection on abuse.

Copilot uses AI. Check for mistakes.
Comment thread src/server.js
Comment on lines +255 to +256
if (terminalEnabled) {
fastify.register(terminalPlugin, { path: '/.terminal' });

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

/.terminal is hard-coded in multiple places (plugin registration, dotfile bypass, and WAC bypass). This increases the chance of drift if the path ever changes. Consider defining a single terminalPath constant (like webrtcPath/tunnelPath) and reusing it for registration and bypass checks.

Suggested change
if (terminalEnabled) {
fastify.register(terminalPlugin, { path: '/.terminal' });
const terminalPath = '/.terminal';
if (terminalEnabled) {
fastify.register(terminalPlugin, { path: terminalPath });

Copilot uses AI. Check for mistakes.
Comment thread src/terminal/index.js
Comment on lines +32 to +36
export async function terminalPlugin(fastify, options = {}) {
const wsPath = options.path || '/.terminal';

// Track active shell processes for cleanup
const shells = new Set();

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

New Terminal WebSocket plugin introduces significant new behavior (auth + process spawning + lifecycle cleanup) but there are no automated tests added. The repo already has WebSocket plugin tests (e.g., test/webrtc.test.js, test/tunnel.test.js); please add a test/terminal.test.js covering at least: unauthenticated rejection, owner-only enforcement, basic command execution, and shell termination on socket close/server shutdown.

Copilot uses AI. Check for mistakes.
Comment thread src/terminal/index.js
socket.close();
return;
}

Copilot AI Mar 24, 2026

Copy link

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Terminal WebSocket currently grants a /bin/sh session to any authenticated user (it only checks that webId is present). For multi-user servers this is a privilege escalation (any user can execute arbitrary commands as the server OS user). Please enforce an ownership check (similar to the /db/* plugin) so only the pod owner (or single-user mode) can open a terminal session, and return 403 otherwise.

Suggested change
// Authorization — only pod owner (or single-user mode) may open a shell
let isOwner = false;
// Explicit single-user mode: any authenticated user is treated as the owner
if (options.singleUser === true) {
isOwner = true;
} else if (typeof options.isOwner === 'function') {
try {
// Allow async/sync ownership check similar to other plugins (e.g. /db/*)
const result = options.isOwner(request, webId);
isOwner = result instanceof Promise ? await result : result;
} catch {
isOwner = false;
}
} else if (typeof options.ownerWebId === 'string') {
isOwner = webId === options.ownerWebId;
}
if (!isOwner) {
// Forbidden: authenticated but not the pod owner
socket.send(JSON.stringify({ type: 'error', code: 403, message: 'Forbidden' }));
try { socket.close(); } catch { /* already closed */ }
return;
}

Copilot uses AI. Check for mistakes.
melvincarvalho merged commit 7bd09aa into gh-pages Mar 24, 2026
4 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

Add built-in WebSocket terminal endpoint (--terminal)

2 participants


Back | FazBrowse Home | New Git URL