zen-gitsync 2.11.39 → 2.12.2

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 (27) hide show
  1. package/README.md +695 -695
  2. package/package.json +1 -1
  3. package/src/ui/public/assets/EditorView-CHBjgiZc.css +1 -0
  4. package/src/ui/public/assets/EditorView-bnJmBq-i.js +21 -0
  5. package/src/ui/public/assets/SourceMapView-DhQX0K7t.css +1 -0
  6. package/src/ui/public/assets/SourceMapView-Rz5SD0A0.js +3 -0
  7. package/src/ui/public/assets/index-Bo3tntQh.js +73 -0
  8. package/src/ui/public/assets/{index-DXO3Lvqi.css → index-bOs5P8fz.css} +1 -1
  9. package/src/ui/public/assets/{ts.worker-Dth06zuC.js → ts.worker-METxwbDZ.js} +1 -16
  10. package/src/ui/public/assets/{vendor-B1T2uxYO.js → vendor-DITsiaGj.js} +294 -287
  11. package/src/ui/public/assets/vendor-q83wvJns.css +1 -0
  12. package/src/ui/public/index.html +4 -4
  13. package/src/ui/server/.claude/codediff.txt +6 -0
  14. package/src/ui/server/routes/fs.js +33 -45
  15. package/src/ui/server/routes/instances.js +24 -24
  16. package/src/ui/server/utils/instanceRegistry.js +256 -256
  17. package/src/ui/server/utils/pathGuard.js +141 -0
  18. package/src/ui/server/utils/pathGuard.test.js +124 -0
  19. package/src/ui/server/utils/randomStartPort.js +37 -37
  20. package/src/utils/index.js +1044 -1044
  21. package/src/ui/public/assets/devopicons-QN4QXivI.woff2 +0 -0
  22. package/src/ui/public/assets/file-icons-C0jOugUK.woff2 +0 -0
  23. package/src/ui/public/assets/fontawesome-B-jkhYfk.woff2 +0 -0
  24. package/src/ui/public/assets/index-BvVl-092.js +0 -95
  25. package/src/ui/public/assets/mfixx-CpAhKOZz.woff2 +0 -0
  26. package/src/ui/public/assets/octicons-CaZ_fok2.woff2 +0 -0
  27. package/src/ui/public/assets/vendor-hOO_r_AU.css +0 -1
@@ -0,0 +1,124 @@
1
+ // 路径越界检查单元测试(用 node:test 内置)
2
+ import { test } from 'node:test'
3
+ import assert from 'node:assert/strict'
4
+ import { ensureWithinCwd, ensureWithinCwdSync, pathGuard } from '../utils/pathGuard.js'
5
+
6
+ const cwd = process.platform === 'win32' ? 'E:\\project' : '/tmp/project'
7
+
8
+ test('合法路径在 cwd 内', async () => {
9
+ const r = await ensureWithinCwd('src/foo.ts', cwd)
10
+ assert.ok(r, '应返回结果')
11
+ assert.match(r.safePath, /[\\/]project[\\/]src[\\/]foo\.ts$/)
12
+ })
13
+
14
+ test('合法绝对路径在 cwd 内', async () => {
15
+ const absInCwd = process.platform === 'win32' ? 'E:\\project\\sub' : '/tmp/project/sub'
16
+ const r = await ensureWithinCwd(absInCwd, cwd)
17
+ assert.ok(r, '应通过')
18
+ assert.equal(r.safePath, absInCwd)
19
+ })
20
+
21
+ test('../ 父目录逃逸被拒绝', async () => {
22
+ const r = await ensureWithinCwd('../etc/passwd', cwd)
23
+ assert.equal(r, null, '../ 应该被拒')
24
+ })
25
+
26
+ test('cwd 的同级目录(startsWith 假阳性)被拒绝', async () => {
27
+ const evil = process.platform === 'win32' ? 'E:\\project-evil' : '/tmp/project-evil'
28
+ const r = await ensureWithinCwd(evil, cwd)
29
+ assert.equal(r, null, '同名前缀目录应该被拒')
30
+ })
31
+
32
+ test('绝对路径在 cwd 外被拒', async () => {
33
+ const outside = process.platform === 'win32' ? 'C:\\Windows\\System32' : '/etc/passwd'
34
+ const r = await ensureWithinCwd(outside, cwd)
35
+ assert.equal(r, null, '绝对外部路径应该被拒')
36
+ })
37
+
38
+ test('空字符串 / null / undefined 被拒', async () => {
39
+ assert.equal(await ensureWithinCwd('', cwd), null)
40
+ assert.equal(await ensureWithinCwd(null, cwd), null)
41
+ assert.equal(await ensureWithinCwd(undefined, cwd), null)
42
+ assert.equal(await ensureWithinCwd(123, cwd), null)
43
+ })
44
+
45
+ test('多重 ../ 也被拒', async () => {
46
+ const r = await ensureWithinCwd('../../../../etc/passwd', cwd)
47
+ assert.equal(r, null)
48
+ })
49
+
50
+ test('相对 cwd 自身(.)允许', async () => {
51
+ const r = await ensureWithinCwd('.', cwd)
52
+ assert.ok(r, '点 应解析为 cwd 本身')
53
+ })
54
+
55
+ test('Windows 反斜杠与正斜杠混用允许', async () => {
56
+ if (process.platform !== 'win32') return
57
+ const r = await ensureWithinCwd('src\\sub/foo.ts', cwd)
58
+ assert.ok(r)
59
+ })
60
+
61
+ test('Windows 大小写不敏感', async () => {
62
+ if (process.platform !== 'win32') return
63
+ const r = await ensureWithinCwd('SRC/FOO.TS', 'E:\\Project')
64
+ assert.ok(r, 'Windows 下大小写不敏感应允许')
65
+ })
66
+
67
+ test('sync 版本对 .. 同样拒绝', () => {
68
+ assert.equal(ensureWithinCwdSync('../foo', cwd), null)
69
+ const r = ensureWithinCwdSync('sub/file', cwd)
70
+ assert.ok(r)
71
+ })
72
+
73
+ test('中间件: 合法 path 通过 + 挂到 res.locals.safePath', async () => {
74
+ let nextCalled = false
75
+ const req = { query: { path: 'src/foo.ts' } }
76
+ const res = {
77
+ locals: {},
78
+ status() { return this },
79
+ json() { return this },
80
+ }
81
+ await pathGuard(cwd)(req, res, () => { nextCalled = true })
82
+ assert.equal(nextCalled, true)
83
+ assert.match(res.locals.safePath, /[\\/]src[\\/]foo\.ts$/)
84
+ })
85
+
86
+ test('中间件: 越界 path 返回 403', async () => {
87
+ let nextCalled = false
88
+ let statusCode = 0
89
+ let body = null
90
+ const req = { query: { path: '../etc/passwd' } }
91
+ const res = {
92
+ locals: {},
93
+ status(c) { statusCode = c; return this },
94
+ json(b) { body = b; return this },
95
+ }
96
+ await pathGuard(cwd)(req, res, () => { nextCalled = true })
97
+ assert.equal(nextCalled, false)
98
+ assert.equal(statusCode, 403)
99
+ assert.match(body.error, /禁止访问/)
100
+ })
101
+
102
+ test('中间件: 缺字段返回 400', async () => {
103
+ let statusCode = 0
104
+ const req = { query: {} }
105
+ const res = {
106
+ locals: {},
107
+ status(c) { statusCode = c; return this },
108
+ json() { return this },
109
+ }
110
+ await pathGuard(cwd)(req, res, () => {})
111
+ assert.equal(statusCode, 400)
112
+ })
113
+
114
+ test('中间件: 多字段校验(rename 用)', async () => {
115
+ let nextCalled = false
116
+ const req = { query: { oldPath: 'src/a.ts', newPath: '../b.ts' } }
117
+ const res = {
118
+ locals: {},
119
+ status() { return this },
120
+ json() { return this },
121
+ }
122
+ await pathGuard(cwd, ['oldPath', 'newPath'])(req, res, () => { nextCalled = true })
123
+ assert.equal(nextCalled, false, 'newPath 越界应拒')
124
+ })
@@ -1,37 +1,37 @@
1
- // 随机起点端口选择
2
- // 默认:避免与系统保留 / 常见服务端口重叠,在较宽的"用户态"范围里随机挑一个起点
3
- // 然后由 startServerOnAvailablePort 在该起点基础上顺序往上扫描 EADDRINUSE
4
- // 覆盖:环境变量 PORT 强制使用固定端口(向后兼容 + 便于书签/调试)
5
-
6
- const DEFAULT_MIN = 4000;
7
- const DEFAULT_MAX = 6000; // 2000 端口的池子;配合 maxTries 100 实际能覆盖 (max - min) 区间
8
-
9
- function pickRandomInt(min, max) {
10
- // 含 min,不含 max(与 Math.random 习惯一致)
11
- return Math.floor(Math.random() * (max - min)) + min;
12
- }
13
-
14
- /**
15
- * 解析本次启动应使用的起始端口
16
- * @param {object} [opts]
17
- * @param {number} [opts.min] 随机范围下界(默认 4000)
18
- * @param {number} [opts.max] 随机范围上界(默认 6000)
19
- * @returns {{ startPort: number, source: 'env'|'random', min: number, max: number }}
20
- */
21
- export function resolveStartPort({ min = DEFAULT_MIN, max = DEFAULT_MAX } = {}) {
22
- // 1) 环境变量优先:用户显式指定 > 一切
23
- const envPort = Number(process.env.PORT);
24
- if (Number.isInteger(envPort) && envPort > 0 && envPort < 65536) {
25
- return { startPort: envPort, source: 'env', min, max };
26
- }
27
-
28
- // 2) 兜底:参数范围非法就回退到 [4000, 6000)
29
- if (!Number.isInteger(min) || !Number.isInteger(max) || min < 1 || max > 65535 || min >= max) {
30
- min = DEFAULT_MIN;
31
- max = DEFAULT_MAX;
32
- }
33
-
34
- // 3) 随机挑一个起点
35
- const startPort = pickRandomInt(min, max);
36
- return { startPort, source: 'random', min, max };
37
- }
1
+ // 随机起点端口选择
2
+ // 默认:避免与系统保留 / 常见服务端口重叠,在较宽的"用户态"范围里随机挑一个起点
3
+ // 然后由 startServerOnAvailablePort 在该起点基础上顺序往上扫描 EADDRINUSE
4
+ // 覆盖:环境变量 PORT 强制使用固定端口(向后兼容 + 便于书签/调试)
5
+
6
+ const DEFAULT_MIN = 4000;
7
+ const DEFAULT_MAX = 6000; // 2000 端口的池子;配合 maxTries 100 实际能覆盖 (max - min) 区间
8
+
9
+ function pickRandomInt(min, max) {
10
+ // 含 min,不含 max(与 Math.random 习惯一致)
11
+ return Math.floor(Math.random() * (max - min)) + min;
12
+ }
13
+
14
+ /**
15
+ * 解析本次启动应使用的起始端口
16
+ * @param {object} [opts]
17
+ * @param {number} [opts.min] 随机范围下界(默认 4000)
18
+ * @param {number} [opts.max] 随机范围上界(默认 6000)
19
+ * @returns {{ startPort: number, source: 'env'|'random', min: number, max: number }}
20
+ */
21
+ export function resolveStartPort({ min = DEFAULT_MIN, max = DEFAULT_MAX } = {}) {
22
+ // 1) 环境变量优先:用户显式指定 > 一切
23
+ const envPort = Number(process.env.PORT);
24
+ if (Number.isInteger(envPort) && envPort > 0 && envPort < 65536) {
25
+ return { startPort: envPort, source: 'env', min, max };
26
+ }
27
+
28
+ // 2) 兜底:参数范围非法就回退到 [4000, 6000)
29
+ if (!Number.isInteger(min) || !Number.isInteger(max) || min < 1 || max > 65535 || min >= max) {
30
+ min = DEFAULT_MIN;
31
+ max = DEFAULT_MAX;
32
+ }
33
+
34
+ // 3) 随机挑一个起点
35
+ const startPort = pickRandomInt(min, max);
36
+ return { startPort, source: 'random', min, max };
37
+ }