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/src/service.js CHANGED
@@ -1,4 +1,4 @@
1
- const { execFileSync, execFile } = require('child_process');
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
- console.log(dim(` Generated password: ${config.password}`));
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
- console.log(dim(` Auto-generated password: ${config.password}`));
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(config.password)}`,
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 getProcessCwd(pid) {
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 [id, s] of this.sessions) {
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 deletePersisted() {
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 (loginErr) {
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');