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.
- package/README.md +695 -695
- package/package.json +1 -1
- package/src/ui/public/assets/EditorView-CHBjgiZc.css +1 -0
- package/src/ui/public/assets/EditorView-bnJmBq-i.js +21 -0
- package/src/ui/public/assets/SourceMapView-DhQX0K7t.css +1 -0
- package/src/ui/public/assets/SourceMapView-Rz5SD0A0.js +3 -0
- package/src/ui/public/assets/index-Bo3tntQh.js +73 -0
- package/src/ui/public/assets/{index-DXO3Lvqi.css → index-bOs5P8fz.css} +1 -1
- package/src/ui/public/assets/{ts.worker-Dth06zuC.js → ts.worker-METxwbDZ.js} +1 -16
- package/src/ui/public/assets/{vendor-B1T2uxYO.js → vendor-DITsiaGj.js} +294 -287
- package/src/ui/public/assets/vendor-q83wvJns.css +1 -0
- package/src/ui/public/index.html +4 -4
- package/src/ui/server/.claude/codediff.txt +6 -0
- package/src/ui/server/routes/fs.js +33 -45
- package/src/ui/server/routes/instances.js +24 -24
- package/src/ui/server/utils/instanceRegistry.js +256 -256
- package/src/ui/server/utils/pathGuard.js +141 -0
- package/src/ui/server/utils/pathGuard.test.js +124 -0
- package/src/ui/server/utils/randomStartPort.js +37 -37
- package/src/utils/index.js +1044 -1044
- package/src/ui/public/assets/devopicons-QN4QXivI.woff2 +0 -0
- package/src/ui/public/assets/file-icons-C0jOugUK.woff2 +0 -0
- package/src/ui/public/assets/fontawesome-B-jkhYfk.woff2 +0 -0
- package/src/ui/public/assets/index-BvVl-092.js +0 -95
- package/src/ui/public/assets/mfixx-CpAhKOZz.woff2 +0 -0
- package/src/ui/public/assets/octicons-CaZ_fok2.woff2 +0 -0
- package/src/ui/public/assets/vendor-hOO_r_AU.css +0 -1
|
@@ -5,6 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import open from 'open';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import { spawn, exec } from 'child_process';
|
|
8
|
+
import { ensureWithinCwd } from '../utils/pathGuard.js';
|
|
8
9
|
|
|
9
10
|
export function registerFsRoutes({
|
|
10
11
|
app,
|
|
@@ -17,6 +18,12 @@ export function registerFsRoutes({
|
|
|
17
18
|
setProjectRoomId,
|
|
18
19
|
setIsGitRepo
|
|
19
20
|
}) {
|
|
21
|
+
// ── 解析并校验 user 输入路径在当前项目 cwd 内(防 ../ 父目录逃逸、startsWith 假阳性、Windows 大小写)──
|
|
22
|
+
const safePathInProject = async (userPath) => {
|
|
23
|
+
const cwd = getCurrentProjectPath() || process.cwd()
|
|
24
|
+
return ensureWithinCwd(userPath, cwd)
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
// 新增获取当前工作目录接口
|
|
21
28
|
app.get('/api/current_directory', async (req, res) => {
|
|
22
29
|
try {
|
|
@@ -497,12 +504,9 @@ export function registerFsRoutes({
|
|
|
497
504
|
try {
|
|
498
505
|
const filePath = req.query.path;
|
|
499
506
|
if (!filePath) return res.status(400).json({ success: false, error: '缺少 path 参数' });
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const resolved =
|
|
503
|
-
if (!resolved.startsWith(path.resolve(cwd))) {
|
|
504
|
-
return res.status(403).json({ success: false, error: '禁止访问工作目录以外的文件' });
|
|
505
|
-
}
|
|
507
|
+
const safe = await safePathInProject(filePath);
|
|
508
|
+
if (!safe) return res.status(403).json({ success: false, error: '禁止访问工作目录以外的文件' });
|
|
509
|
+
const resolved = safe.safePath;
|
|
506
510
|
const stat = await fs.stat(resolved);
|
|
507
511
|
if (!stat.isFile()) return res.status(400).json({ success: false, error: '目标不是文件' });
|
|
508
512
|
// 超过 2MB 不读取
|
|
@@ -521,11 +525,9 @@ export function registerFsRoutes({
|
|
|
521
525
|
try {
|
|
522
526
|
const filePath = req.query.path;
|
|
523
527
|
if (!filePath) return res.status(400).end();
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return res.status(403).end();
|
|
528
|
-
}
|
|
528
|
+
const safe = await safePathInProject(filePath);
|
|
529
|
+
if (!safe) return res.status(403).end();
|
|
530
|
+
const resolved = safe.safePath;
|
|
529
531
|
const stat = await fs.stat(resolved);
|
|
530
532
|
if (!stat.isFile()) return res.status(400).end();
|
|
531
533
|
if (stat.size > 20 * 1024 * 1024) return res.status(413).end();
|
|
@@ -552,12 +554,9 @@ export function registerFsRoutes({
|
|
|
552
554
|
if (!filePath || content === undefined) {
|
|
553
555
|
return res.status(400).json({ success: false, error: '缺少 path 或 content 参数' });
|
|
554
556
|
}
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return res.status(403).json({ success: false, error: '禁止写入工作目录以外的文件' });
|
|
559
|
-
}
|
|
560
|
-
await fs.writeFile(resolved, content, 'utf-8');
|
|
557
|
+
const safe = await safePathInProject(filePath);
|
|
558
|
+
if (!safe) return res.status(403).json({ success: false, error: '禁止写入工作目录以外的文件' });
|
|
559
|
+
await fs.writeFile(safe.safePath, content, 'utf-8');
|
|
561
560
|
res.json({ success: true });
|
|
562
561
|
} catch (error) {
|
|
563
562
|
res.status(500).json({ success: false, error: error.message });
|
|
@@ -569,11 +568,9 @@ export function registerFsRoutes({
|
|
|
569
568
|
try {
|
|
570
569
|
const { path: filePath } = req.body;
|
|
571
570
|
if (!filePath) return res.status(400).json({ success: false, error: '缺少 path 参数' });
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
return res.status(403).json({ success: false, error: '禁止在工作目录以外创建文件' });
|
|
576
|
-
}
|
|
571
|
+
const safe = await safePathInProject(filePath);
|
|
572
|
+
if (!safe) return res.status(403).json({ success: false, error: '禁止在工作目录以外创建文件' });
|
|
573
|
+
const resolved = safe.safePath;
|
|
577
574
|
// 如果已存在则拒绝
|
|
578
575
|
try {
|
|
579
576
|
await fs.access(resolved);
|
|
@@ -592,11 +589,9 @@ export function registerFsRoutes({
|
|
|
592
589
|
try {
|
|
593
590
|
const { path: dirPath } = req.body;
|
|
594
591
|
if (!dirPath) return res.status(400).json({ success: false, error: '缺少 path 参数' });
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
return res.status(403).json({ success: false, error: '禁止在工作目录以外创建目录' });
|
|
599
|
-
}
|
|
592
|
+
const safe = await safePathInProject(dirPath);
|
|
593
|
+
if (!safe) return res.status(403).json({ success: false, error: '禁止在工作目录以外创建目录' });
|
|
594
|
+
const resolved = safe.safePath;
|
|
600
595
|
try {
|
|
601
596
|
await fs.access(resolved);
|
|
602
597
|
return res.status(409).json({ success: false, error: '目录已存在' });
|
|
@@ -613,11 +608,9 @@ export function registerFsRoutes({
|
|
|
613
608
|
try {
|
|
614
609
|
const filePath = req.query.path;
|
|
615
610
|
if (!filePath) return res.status(400).json({ success: false, error: '缺少 path 参数' });
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
return res.status(403).json({ success: false, error: '禁止删除工作目录以外的内容' });
|
|
620
|
-
}
|
|
611
|
+
const safe = await safePathInProject(filePath);
|
|
612
|
+
if (!safe) return res.status(403).json({ success: false, error: '禁止删除工作目录以外的内容' });
|
|
613
|
+
const resolved = safe.safePath;
|
|
621
614
|
const stat = await fs.stat(resolved);
|
|
622
615
|
if (stat.isDirectory()) {
|
|
623
616
|
await fs.rm(resolved, { recursive: true, force: true });
|
|
@@ -635,17 +628,14 @@ export function registerFsRoutes({
|
|
|
635
628
|
try {
|
|
636
629
|
const { oldPath, newPath } = req.body;
|
|
637
630
|
if (!oldPath || !newPath) return res.status(400).json({ success: false, error: '缺少参数' });
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
if (!resolvedOld.startsWith(path.resolve(cwd)) || !resolvedNew.startsWith(path.resolve(cwd))) {
|
|
642
|
-
return res.status(403).json({ success: false, error: '禁止操作工作目录以外的内容' });
|
|
643
|
-
}
|
|
631
|
+
const safeOld = await safePathInProject(oldPath)
|
|
632
|
+
const safeNew = await safePathInProject(newPath)
|
|
633
|
+
if (!safeOld || !safeNew) return res.status(403).json({ success: false, error: '禁止操作工作目录以外的内容' });
|
|
644
634
|
try {
|
|
645
|
-
await fs.access(
|
|
635
|
+
await fs.access(safeNew.safePath);
|
|
646
636
|
return res.status(409).json({ success: false, error: '目标名称已存在' });
|
|
647
637
|
} catch { /* 不存在,继续 */ }
|
|
648
|
-
await fs.rename(
|
|
638
|
+
await fs.rename(safeOld.safePath, safeNew.safePath);
|
|
649
639
|
res.json({ success: true });
|
|
650
640
|
} catch (error) {
|
|
651
641
|
res.status(500).json({ success: false, error: error.message });
|
|
@@ -661,11 +651,9 @@ export function registerFsRoutes({
|
|
|
661
651
|
const targetPath = req.body?.path;
|
|
662
652
|
if (!targetPath) return res.status(400).json({ success: false, error: '缺少 path 参数' });
|
|
663
653
|
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
return res.status(403).json({ success: false, error: '禁止访问工作目录以外的内容' });
|
|
668
|
-
}
|
|
654
|
+
const safe = await safePathInProject(targetPath);
|
|
655
|
+
if (!safe) return res.status(403).json({ success: false, error: '禁止访问工作目录以外的内容' });
|
|
656
|
+
const resolved = safe.safePath;
|
|
669
657
|
|
|
670
658
|
try {
|
|
671
659
|
await fs.access(resolved);
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
// 实例注册表 API 路由
|
|
2
|
-
// 当前只暴露只读列表;停止/启停他人实例属于 out-of-scope(v1 只做跳转导航)
|
|
3
|
-
|
|
4
|
-
export function registerInstancesRoutes({ app, registry, getCurrentInstanceId }) {
|
|
5
|
-
// 获取所有活跃实例(自动 prune 失效条目)
|
|
6
|
-
app.get('/api/instances', async (req, res) => {
|
|
7
|
-
try {
|
|
8
|
-
const instances = await registry.list({ pruneStale: true });
|
|
9
|
-
const currentInstanceId = typeof getCurrentInstanceId === 'function'
|
|
10
|
-
? getCurrentInstanceId()
|
|
11
|
-
: null;
|
|
12
|
-
res.json({
|
|
13
|
-
success: true,
|
|
14
|
-
instances,
|
|
15
|
-
currentInstanceId
|
|
16
|
-
});
|
|
17
|
-
} catch (error) {
|
|
18
|
-
res.status(500).json({
|
|
19
|
-
success: false,
|
|
20
|
-
error: error?.message || String(error)
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
1
|
+
// 实例注册表 API 路由
|
|
2
|
+
// 当前只暴露只读列表;停止/启停他人实例属于 out-of-scope(v1 只做跳转导航)
|
|
3
|
+
|
|
4
|
+
export function registerInstancesRoutes({ app, registry, getCurrentInstanceId }) {
|
|
5
|
+
// 获取所有活跃实例(自动 prune 失效条目)
|
|
6
|
+
app.get('/api/instances', async (req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
const instances = await registry.list({ pruneStale: true });
|
|
9
|
+
const currentInstanceId = typeof getCurrentInstanceId === 'function'
|
|
10
|
+
? getCurrentInstanceId()
|
|
11
|
+
: null;
|
|
12
|
+
res.json({
|
|
13
|
+
success: true,
|
|
14
|
+
instances,
|
|
15
|
+
currentInstanceId
|
|
16
|
+
});
|
|
17
|
+
} catch (error) {
|
|
18
|
+
res.status(500).json({
|
|
19
|
+
success: false,
|
|
20
|
+
error: error?.message || String(error)
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|