termbeam 1.22.3 → 1.22.4
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/cli/resume.js +23 -9
- package/src/server/routes.js +26 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.22.4] - 2026-04-26
|
|
4
|
+
|
|
5
|
+
- perf(test): parallelize e2e suite via shared server + workers (#214) (@dorlugasigal)
|
|
6
|
+
- fix(security): add inline path validation guards for CodeQL (#216) (@dorlugasigal)
|
|
7
|
+
- fix(deps): update vulnerable dependencies and pin Docker image (#218) (@dorlugasigal)
|
|
8
|
+
- refactor(site): clean up landing page design and optimize CI (#220) (@dorlugasigal)
|
|
9
|
+
|
|
3
10
|
## [1.22.3] - 2026-04-26
|
|
4
11
|
|
|
5
12
|
- fix(site): auto-detect Cloudflare Pages build target (@dorlugasigal)
|
package/package.json
CHANGED
package/src/cli/resume.js
CHANGED
|
@@ -6,15 +6,22 @@ const log = require('../utils/logger');
|
|
|
6
6
|
const { createTerminalClient } = require('./client');
|
|
7
7
|
const { bold, dim, red, yellow, choose, createRL, ask } = require('./prompts');
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Resolve config paths at call time so that tests (and other code paths) which
|
|
10
|
+
// set TERMBEAM_CONFIG_DIR after this module is first required still take effect.
|
|
11
|
+
function getConfigDir() {
|
|
12
|
+
return process.env.TERMBEAM_CONFIG_DIR || path.join(os.homedir(), '.termbeam');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getConnectionFile() {
|
|
16
|
+
return path.join(getConfigDir(), 'connection.json');
|
|
17
|
+
}
|
|
11
18
|
|
|
12
19
|
// ── Connection config ────────────────────────────────────────────────────────
|
|
13
20
|
|
|
14
21
|
function readConnectionConfig() {
|
|
15
22
|
log.debug('Reading connection config');
|
|
16
23
|
try {
|
|
17
|
-
return JSON.parse(fs.readFileSync(
|
|
24
|
+
return JSON.parse(fs.readFileSync(getConnectionFile(), 'utf8'));
|
|
18
25
|
} catch {
|
|
19
26
|
return null;
|
|
20
27
|
}
|
|
@@ -22,14 +29,15 @@ function readConnectionConfig() {
|
|
|
22
29
|
|
|
23
30
|
function writeConnectionConfig({ port, host, password }) {
|
|
24
31
|
log.debug('Writing connection config');
|
|
25
|
-
|
|
26
|
-
fs.
|
|
32
|
+
const connectionFile = getConnectionFile();
|
|
33
|
+
fs.mkdirSync(getConfigDir(), { recursive: true });
|
|
34
|
+
fs.writeFileSync(connectionFile, JSON.stringify({ port, host, password }, null, 2) + '\n', {
|
|
27
35
|
mode: 0o600,
|
|
28
36
|
});
|
|
29
37
|
// Ensure restrictive permissions even if the file already existed
|
|
30
38
|
if (process.platform !== 'win32') {
|
|
31
39
|
try {
|
|
32
|
-
fs.chmodSync(
|
|
40
|
+
fs.chmodSync(connectionFile, 0o600);
|
|
33
41
|
} catch {
|
|
34
42
|
/* best-effort */
|
|
35
43
|
}
|
|
@@ -39,7 +47,7 @@ function writeConnectionConfig({ port, host, password }) {
|
|
|
39
47
|
function removeConnectionConfig() {
|
|
40
48
|
log.debug('Removing connection config');
|
|
41
49
|
try {
|
|
42
|
-
fs.unlinkSync(
|
|
50
|
+
fs.unlinkSync(getConnectionFile());
|
|
43
51
|
} catch {
|
|
44
52
|
/* ignore */
|
|
45
53
|
}
|
|
@@ -402,6 +410,12 @@ module.exports = {
|
|
|
402
410
|
readConnectionConfig,
|
|
403
411
|
printResumeHelp,
|
|
404
412
|
parseDetachKey,
|
|
405
|
-
|
|
406
|
-
|
|
413
|
+
// Lazy getters so tests reading these after setting TERMBEAM_CONFIG_DIR see
|
|
414
|
+
// the current value, not the value at module load time.
|
|
415
|
+
get CONFIG_DIR() {
|
|
416
|
+
return getConfigDir();
|
|
417
|
+
},
|
|
418
|
+
get CONNECTION_FILE() {
|
|
419
|
+
return getConnectionFile();
|
|
420
|
+
},
|
|
407
421
|
};
|
package/src/server/routes.js
CHANGED
|
@@ -98,23 +98,28 @@ function validateMagicBytes(buffer, contentType) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function setupRoutes(app, { auth, sessions, config, state, pushManager, copilotService }) {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
const noopLimit = (_req, _res, next) => next();
|
|
102
|
+
const pageRateLimit = config.disableRateLimit
|
|
103
|
+
? noopLimit
|
|
104
|
+
: rateLimit({
|
|
105
|
+
windowMs: 1 * 60 * 1000,
|
|
106
|
+
max: 120,
|
|
107
|
+
standardHeaders: true,
|
|
108
|
+
legacyHeaders: false,
|
|
109
|
+
handler: (_req, res) =>
|
|
110
|
+
res.status(429).json({ error: 'Too many requests, please try again later.' }),
|
|
111
|
+
});
|
|
109
112
|
|
|
110
|
-
const apiRateLimit =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
const apiRateLimit = config.disableRateLimit
|
|
114
|
+
? noopLimit
|
|
115
|
+
: rateLimit({
|
|
116
|
+
windowMs: 1 * 60 * 1000,
|
|
117
|
+
max: 120,
|
|
118
|
+
standardHeaders: true,
|
|
119
|
+
legacyHeaders: false,
|
|
120
|
+
handler: (_req, res) =>
|
|
121
|
+
res.status(429).json({ error: 'Too many requests, please try again later.' }),
|
|
122
|
+
});
|
|
118
123
|
|
|
119
124
|
// Serve static files — sw.js must never be cached by the browser
|
|
120
125
|
app.get('/sw.js', (_req, res, next) => {
|
|
@@ -860,7 +865,7 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager, copilotS
|
|
|
860
865
|
|
|
861
866
|
const rootDir = path.resolve(sessions.getSessionCwd(req.params.id));
|
|
862
867
|
const dir = safePath(rootDir, req.query.dir || '.');
|
|
863
|
-
if (!dir) {
|
|
868
|
+
if (!dir || (dir !== rootDir && !dir.startsWith(rootDir + path.sep))) {
|
|
864
869
|
return res.status(403).json({ error: 'Path is outside session directory' });
|
|
865
870
|
}
|
|
866
871
|
|
|
@@ -930,7 +935,7 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager, copilotS
|
|
|
930
935
|
let startDir = rootDir;
|
|
931
936
|
if (typeof req.query.path === 'string' && req.query.path.length > 0) {
|
|
932
937
|
const resolved = safePath(rootDir, req.query.path);
|
|
933
|
-
if (!resolved) {
|
|
938
|
+
if (!resolved || (resolved !== rootDir && !resolved.startsWith(rootDir + path.sep))) {
|
|
934
939
|
return res.status(403).json({ error: 'Path is outside session directory' });
|
|
935
940
|
}
|
|
936
941
|
try {
|
|
@@ -1040,7 +1045,7 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager, copilotS
|
|
|
1040
1045
|
|
|
1041
1046
|
const rootDir = path.resolve(sessions.getSessionCwd(req.params.id));
|
|
1042
1047
|
const filePath = safePath(rootDir, file);
|
|
1043
|
-
if (!filePath) {
|
|
1048
|
+
if (!filePath || (filePath !== rootDir && !filePath.startsWith(rootDir + path.sep))) {
|
|
1044
1049
|
return res.status(403).json({ error: 'Path is outside session directory' });
|
|
1045
1050
|
}
|
|
1046
1051
|
|
|
@@ -1074,7 +1079,7 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager, copilotS
|
|
|
1074
1079
|
|
|
1075
1080
|
const rootDir = path.resolve(sessions.getSessionCwd(req.params.id));
|
|
1076
1081
|
const filePath = safePath(rootDir, file);
|
|
1077
|
-
if (!filePath) {
|
|
1082
|
+
if (!filePath || (filePath !== rootDir && !filePath.startsWith(rootDir + path.sep))) {
|
|
1078
1083
|
return res.status(403).json({ error: 'Path is outside session directory' });
|
|
1079
1084
|
}
|
|
1080
1085
|
|
|
@@ -1108,7 +1113,7 @@ function setupRoutes(app, { auth, sessions, config, state, pushManager, copilotS
|
|
|
1108
1113
|
|
|
1109
1114
|
const rootDir = path.resolve(sessions.getSessionCwd(req.params.id));
|
|
1110
1115
|
const filePath = safePath(rootDir, file);
|
|
1111
|
-
if (!filePath) {
|
|
1116
|
+
if (!filePath || (filePath !== rootDir && !filePath.startsWith(rootDir + path.sep))) {
|
|
1112
1117
|
return res.status(403).json({ error: 'Path is outside session directory' });
|
|
1113
1118
|
}
|
|
1114
1119
|
|