termbeam 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -68
- package/bin/termbeam.js +129 -0
- package/package.json +5 -1
- package/src/cli.js +15 -0
- package/src/client.js +169 -0
- package/src/interactive.js +4 -4
- package/src/prompts.js +2 -2
- package/src/resume.js +387 -0
- package/src/routes.js +60 -22
- package/src/server.js +48 -8
- package/src/service.js +4 -4
- package/src/sessions.js +22 -3
- package/src/tunnel.js +2 -2
package/src/service.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { execFileSync
|
|
1
|
+
const { execFileSync } = require('child_process');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
@@ -237,7 +237,7 @@ async function actionInstall() {
|
|
|
237
237
|
]);
|
|
238
238
|
if (pwChoice.index === 0) {
|
|
239
239
|
config.password = crypto.randomBytes(16).toString('base64url');
|
|
240
|
-
|
|
240
|
+
process.stdout.write(dim(` Generated password: ${config.password}`) + '\n');
|
|
241
241
|
} else if (pwChoice.index === 1) {
|
|
242
242
|
config.password = await ask(rl, 'Enter password:');
|
|
243
243
|
while (!config.password) {
|
|
@@ -297,7 +297,7 @@ async function actionInstall() {
|
|
|
297
297
|
if (config.publicTunnel && config.password === false) {
|
|
298
298
|
console.log(yellow(' ⚠ Public tunnels require password authentication.'));
|
|
299
299
|
config.password = crypto.randomBytes(16).toString('base64url');
|
|
300
|
-
|
|
300
|
+
process.stdout.write(dim(` Auto-generated password: ${config.password}`) + '\n');
|
|
301
301
|
}
|
|
302
302
|
} else if (accessChoice.index === 1) {
|
|
303
303
|
// LAN mode: bind to all interfaces, no tunnel
|
|
@@ -352,7 +352,7 @@ async function actionInstall() {
|
|
|
352
352
|
console.log(bold('\n── Configuration Summary ──────────────────'));
|
|
353
353
|
console.log(` Service name: ${cyan(config.name)}`);
|
|
354
354
|
console.log(
|
|
355
|
-
` Password: ${config.password === false ? yellow('disabled') : cyan(
|
|
355
|
+
` Password: ${config.password === false ? yellow('disabled') : cyan('••••••••')}`,
|
|
356
356
|
);
|
|
357
357
|
console.log(` Port: ${cyan(String(config.port))}`);
|
|
358
358
|
console.log(
|
package/src/sessions.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const { execSync, exec } = require('child_process');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const pty = require('node-pty');
|
|
5
6
|
const log = require('./logger');
|
|
6
7
|
const { getGitInfo } = require('./git');
|
|
7
8
|
|
|
8
|
-
function
|
|
9
|
+
function _getProcessCwd(pid) {
|
|
9
10
|
try {
|
|
10
11
|
if (process.platform === 'linux') {
|
|
11
12
|
return fs.readlinkSync(`/proc/${pid}/cwd`);
|
|
@@ -108,6 +109,24 @@ class SessionManager {
|
|
|
108
109
|
cols = 120,
|
|
109
110
|
rows = 30,
|
|
110
111
|
}) {
|
|
112
|
+
// Defense-in-depth: reject shells with dangerous characters or relative paths
|
|
113
|
+
if (
|
|
114
|
+
typeof shell !== 'string' ||
|
|
115
|
+
!shell ||
|
|
116
|
+
/[;&|`$(){}\[\]!#~]/.test(shell) ||
|
|
117
|
+
(!path.isAbsolute(shell) && !shell.match(/^[a-zA-Z0-9._-]+(\.exe)?$/))
|
|
118
|
+
) {
|
|
119
|
+
throw new Error('Invalid shell');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Defense-in-depth: validate args and initialCommand types
|
|
123
|
+
if (!Array.isArray(args) || !args.every((a) => typeof a === 'string')) {
|
|
124
|
+
throw new Error('args must be an array of strings');
|
|
125
|
+
}
|
|
126
|
+
if (initialCommand !== null && typeof initialCommand !== 'string') {
|
|
127
|
+
throw new Error('initialCommand must be a string');
|
|
128
|
+
}
|
|
129
|
+
|
|
111
130
|
const id = crypto.randomBytes(16).toString('hex');
|
|
112
131
|
if (!color) {
|
|
113
132
|
color = SESSION_COLORS[this.sessions.size % SESSION_COLORS.length];
|
|
@@ -117,7 +136,7 @@ class SessionManager {
|
|
|
117
136
|
cols,
|
|
118
137
|
rows,
|
|
119
138
|
cwd,
|
|
120
|
-
env: { ...process.env, TERM: 'xterm-256color' },
|
|
139
|
+
env: { ...process.env, TERM: 'xterm-256color', TERMBEAM_SESSION: '1' },
|
|
121
140
|
});
|
|
122
141
|
|
|
123
142
|
// Send initial command once the shell is ready
|
|
@@ -209,7 +228,7 @@ class SessionManager {
|
|
|
209
228
|
}
|
|
210
229
|
|
|
211
230
|
shutdown() {
|
|
212
|
-
for (const [
|
|
231
|
+
for (const [_id, s] of this.sessions) {
|
|
213
232
|
try {
|
|
214
233
|
s.pty.kill();
|
|
215
234
|
} catch {
|
package/src/tunnel.js
CHANGED
|
@@ -67,7 +67,7 @@ function savePersistedTunnel(id) {
|
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function
|
|
70
|
+
function _deletePersisted() {
|
|
71
71
|
const persisted = loadPersistedTunnel();
|
|
72
72
|
if (persisted) {
|
|
73
73
|
try {
|
|
@@ -139,7 +139,7 @@ async function startTunnel(port, options = {}) {
|
|
|
139
139
|
log.info('A code will be displayed — open the URL on any device to authenticate.');
|
|
140
140
|
try {
|
|
141
141
|
execFileSync(devtunnelCmd, ['user', 'login', '-d'], { stdio: 'inherit' });
|
|
142
|
-
} catch (
|
|
142
|
+
} catch (_loginErr) {
|
|
143
143
|
log.error('');
|
|
144
144
|
log.error(' DevTunnel login failed. To use tunnels, run:');
|
|
145
145
|
log.error(' devtunnel user login');
|