| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,127 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Terminal Plugin — WebSocket Shell Access | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Provides a remote shell over WebSocket for authenticated pod owners. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Spawns /bin/sh on connection and pipes stdin/stdout/stderr between | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * the WebSocket and the shell process. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * SECURITY: Requires authentication. The connecting user's webId must | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * be present (verified via token). This is a privileged endpoint. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Usage: jss start --terminal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Endpoint: wss://your.pod/.terminal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Protocol (binary/text over WebSocket): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * -> (text/binary) stdin data sent to shell | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <- (text/binary) stdout/stderr data from shell | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <- JSON { type: "exit", code: <n> } shell exited | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * <- JSON { type: "error", message: "..." } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import websocket from '@fastify/websocket'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getWebIdFromRequestAsync } from '../auth/token.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { spawn } from 'child_process'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Register terminal WebSocket route on Fastify instance | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {object} fastify - Fastify instance | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {object} options - Options | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} options.path - WebSocket path (default: '/.terminal') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function terminalPlugin(fastify, options = {}) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const wsPath = options.path || '/.terminal'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Track active shell processes for cleanup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const shells = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment thread
Comment on lines
+32
to
+36
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!fastify.websocketServer) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await fastify.register(websocket); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Clean up all shells on server close | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fastify.addHook('onClose', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const proc of shells) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { proc.kill(); } catch { /* already dead */ } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| shells.clear(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fastify.get(wsPath, { websocket: true }, async (connection, request) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const socket = connection.socket; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Authenticate — query param token support for browser WebSocket | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const queryToken = request.query?.token; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (queryToken && !request.headers.authorization) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request.headers.authorization = `Bearer ${queryToken}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment thread
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}`; | |
| // 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; |
Sorry, something went wrong.
There was a problem 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 QualityTerminal 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.
| // 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; | |
| } |
Sorry, something went wrong.
There was a problem 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 QualityIncoming 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.
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
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.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.