remotosh 1.0.2 → 1.1.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.
Files changed (2) hide show
  1. package/bin/remoto.js +100 -20
  2. package/package.json +1 -1
package/bin/remoto.js CHANGED
@@ -11,6 +11,57 @@ const WS_SERVER_URL = process.env.REMOTO_WS_URL || 'wss://remoto-ws.fly.dev';
11
11
  const WEB_APP_URL = process.env.REMOTO_WEB_URL || 'https://remoto.sh';
12
12
  const API_KEY = process.env.REMOTO_API_KEY;
13
13
 
14
+ // Sensitive environment variable patterns to filter out
15
+ const SENSITIVE_ENV_PATTERNS = [
16
+ /^AWS_/i,
17
+ /^AZURE_/i,
18
+ /^GCP_/i,
19
+ /^GOOGLE_/i,
20
+ /^GITHUB_TOKEN$/i,
21
+ /^GH_TOKEN$/i,
22
+ /^GITLAB_/i,
23
+ /^NPM_TOKEN$/i,
24
+ /^NUGET_/i,
25
+ /^DOCKER_/i,
26
+ /^KUBERNETES_/i,
27
+ /^K8S_/i,
28
+ /SECRET/i,
29
+ /PASSWORD/i,
30
+ /PRIVATE_KEY/i,
31
+ /API_KEY/i,
32
+ /AUTH_TOKEN/i,
33
+ /ACCESS_TOKEN/i,
34
+ /REFRESH_TOKEN/i,
35
+ /DATABASE_URL/i,
36
+ /DB_PASSWORD/i,
37
+ /POSTGRES_/i,
38
+ /MYSQL_/i,
39
+ /MONGO_/i,
40
+ /REDIS_/i,
41
+ /STRIPE_/i,
42
+ /TWILIO_/i,
43
+ /SENDGRID_/i,
44
+ /MAILGUN_/i,
45
+ /SUPABASE_/i,
46
+ /FIREBASE_/i,
47
+ /OPENAI_/i,
48
+ /ANTHROPIC_/i,
49
+ /SLACK_/i,
50
+ /DISCORD_/i,
51
+ /^REMOTO_API_KEY$/i, // Our own API key
52
+ ];
53
+
54
+ // Create sanitized environment for PTY
55
+ function getSanitizedEnv() {
56
+ const env = { ...process.env };
57
+ for (const key of Object.keys(env)) {
58
+ if (SENSITIVE_ENV_PATTERNS.some(pattern => pattern.test(key))) {
59
+ delete env[key];
60
+ }
61
+ }
62
+ return env;
63
+ }
64
+
14
65
  // Detect shell
15
66
  const shell = process.env.SHELL || (os.platform() === 'win32' ? 'powershell.exe' : 'zsh');
16
67
 
@@ -21,18 +72,33 @@ let rows = process.stdout.rows || 24;
21
72
  console.clear();
22
73
  console.log(chalk.bold.white('\n remoto'));
23
74
  console.log(chalk.dim(' control your terminal from your phone\n'));
75
+
76
+ // API key is required
77
+ if (!API_KEY) {
78
+ console.log(chalk.yellow(' authentication required\n'));
79
+ console.log(chalk.white(' to use remoto, you need an API key:\n'));
80
+ console.log(chalk.dim(' 1. create a free account at ') + chalk.cyan('https://remoto.sh/signup'));
81
+ console.log(chalk.dim(' 2. go to ') + chalk.cyan('https://remoto.sh/dashboard/api-keys'));
82
+ console.log(chalk.dim(' 3. create an API key and set it as an environment variable:\n'));
83
+ console.log(chalk.green(' export REMOTO_API_KEY="your_key_here"'));
84
+ console.log(chalk.dim('\n free plan includes:'));
85
+ console.log(chalk.dim(' • 2 concurrent sessions'));
86
+ console.log(chalk.dim(' • 1 hour session duration'));
87
+ console.log(chalk.dim(' • unlimited sessions per month'));
88
+ console.log(chalk.dim('\n pro plans with higher limits coming soon!\n'));
89
+ process.exit(1);
90
+ }
91
+
24
92
  console.log(chalk.dim(' connecting...'));
25
93
 
26
- // Connect to WebSocket server (API key is optional)
27
- const wsUrl = API_KEY
28
- ? `${WS_SERVER_URL}/cli/?apiKey=${encodeURIComponent(API_KEY)}`
29
- : `${WS_SERVER_URL}/cli/`;
94
+ // Connect to WebSocket server
95
+ const wsUrl = `${WS_SERVER_URL}/cli/?apiKey=${encodeURIComponent(API_KEY)}`;
30
96
  const ws = new WebSocket(wsUrl);
31
97
 
32
98
  let ptyProcess = null;
33
99
  let sessionId = null;
34
100
  let sessionToken = null;
35
- let isAnonymous = true;
101
+ let maxDuration = null;
36
102
  let outputBuffer = '';
37
103
  let flushTimeout = null;
38
104
 
@@ -53,7 +119,25 @@ ws.on('message', (data) => {
53
119
  });
54
120
 
55
121
  ws.on('close', (code, reason) => {
56
- console.log(chalk.dim(`\n disconnected (${code})`));
122
+ const reasonStr = reason?.toString() || '';
123
+
124
+ if (code === 4001) {
125
+ console.log(chalk.red('\n error: API key required'));
126
+ console.log(chalk.dim(' set REMOTO_API_KEY environment variable'));
127
+ } else if (code === 4002) {
128
+ console.log(chalk.red('\n error: invalid API key'));
129
+ console.log(chalk.dim(' check your API key at https://remoto.sh/dashboard/api-keys'));
130
+ } else if (code === 4004) {
131
+ console.log(chalk.yellow('\n session limit reached'));
132
+ console.log(chalk.dim(' free plan allows 2 concurrent sessions'));
133
+ console.log(chalk.dim(' close an existing session and try again'));
134
+ console.log(chalk.dim('\n pro plans with higher limits coming soon!'));
135
+ } else if (reasonStr.includes('expired')) {
136
+ console.log(chalk.yellow('\n session expired (1 hour limit)'));
137
+ console.log(chalk.dim(' start a new session with: npx remotosh'));
138
+ } else {
139
+ console.log(chalk.dim(`\n disconnected (${code})`));
140
+ }
57
141
  cleanup();
58
142
  });
59
143
 
@@ -70,7 +154,7 @@ function handleServerMessage(message) {
70
154
  case 'session_created':
71
155
  sessionId = message.sessionId;
72
156
  sessionToken = message.sessionToken;
73
- isAnonymous = message.isAnonymous;
157
+ maxDuration = message.maxDuration;
74
158
  showQRCode();
75
159
  startPTY();
76
160
  break;
@@ -91,14 +175,14 @@ function handleServerMessage(message) {
91
175
  }
92
176
  break;
93
177
 
178
+ case 'session_expiring':
179
+ console.log(chalk.yellow(`\n ⚠ session expiring in ${message.minutesRemaining} minutes\n`));
180
+ break;
181
+
94
182
  case 'input':
95
183
  // Input from phone
96
- console.log(chalk.dim(` [debug] input handler, ptyProcess exists: ${!!ptyProcess}`));
97
184
  if (ptyProcess) {
98
- console.log(chalk.dim(` [debug] writing to pty: "${message.data}"`));
99
185
  ptyProcess.write(message.data);
100
- } else {
101
- console.log(chalk.red(' [debug] ptyProcess is null!'));
102
186
  }
103
187
  break;
104
188
 
@@ -116,6 +200,7 @@ function handleServerMessage(message) {
116
200
 
117
201
  function showQRCode() {
118
202
  const connectionUrl = `${WEB_APP_URL}/session/${sessionId}?token=${sessionToken}`;
203
+ const durationMinutes = maxDuration ? Math.floor(maxDuration / 60000) : 60;
119
204
 
120
205
  console.clear();
121
206
  console.log(chalk.bold.white('\n remoto'));
@@ -127,21 +212,23 @@ function showQRCode() {
127
212
  console.log(indentedQr);
128
213
  console.log(chalk.dim(`\n ${connectionUrl}\n`));
129
214
  console.log(chalk.dim(' scan the qr code or open the link on your phone'));
215
+ console.log(chalk.dim(` session expires in ${durationMinutes} minutes`));
130
216
  console.log(chalk.dim(' waiting for connection...\n'));
131
217
  console.log(chalk.dim('─'.repeat(Math.min(cols, 60))));
132
218
  });
133
219
  }
134
220
 
135
221
  function startPTY() {
136
- // Initialize PTY
222
+ // Initialize PTY with sanitized environment (removes secrets)
137
223
  console.log(chalk.dim(` [debug] spawning shell: ${shell}`));
138
224
  try {
225
+ const sanitizedEnv = getSanitizedEnv();
139
226
  ptyProcess = pty.spawn(shell, [], {
140
227
  name: 'xterm-256color',
141
228
  cols,
142
229
  rows,
143
230
  cwd: process.cwd(),
144
- env: process.env,
231
+ env: sanitizedEnv,
145
232
  });
146
233
  console.log(chalk.dim(` [debug] pty spawned successfully, pid: ${ptyProcess.pid}`));
147
234
  } catch (err) {
@@ -174,13 +261,6 @@ function startPTY() {
174
261
  ptyProcess.onExit(({ exitCode }) => {
175
262
  console.log(chalk.dim(`\n session ended`));
176
263
 
177
- // Show account nudge for anonymous users
178
- if (isAnonymous) {
179
- console.log(chalk.dim('\n ─────────────────────────────────────────'));
180
- console.log(chalk.white('\n create an account to save session history'));
181
- console.log(chalk.dim(` ${WEB_APP_URL}/signup\n`));
182
- }
183
-
184
264
  if (ws.readyState === WebSocket.OPEN) {
185
265
  ws.send(JSON.stringify({ type: 'exit', code: exitCode }));
186
266
  ws.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remotosh",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Control your terminal from your phone",
5
5
  "type": "module",
6
6
  "bin": {