根本原因
先前的編輯器會將工作檔案上傳到目標檔案的上層目錄,而在 /etc 或 /var 下,SSH 帳戶通常沒有該目錄的寫入權限。SFTP 重新命名既不提供經授權的 sudo 路徑,也沒有基準衝突檢查、安全性中繼資料驗證或可靠的覆寫語意。其錯誤處理程式會刪除暫存副本,即使在無法確定作業是否完成時,也會移除可供復原的資料。
工作階段狀態先前只顯示連線狀態。沒有任何 ICMP 或經驗證的 SSH 回應量測可提供給終端機指示器。
儲存通訊協定
- 固定的 SSH 輔助程式使用以描述元為基準的相對操作,並對每個路徑元件使用
O_NOFOLLOW,開啟一個一般的單一連結檔案。它會記錄內容、SHA-256 雜湊、裝置/inode、UID、GID、完整的權限位元、奈秒級的修改/變更時間,以及所有可讀取的延伸屬性。Core 會驗證雜湊,並以嚴格的 UTF-8 解碼,同時保留 BOM。每份文件都會取得一個不透明的editId,其範圍限定於對應的外掛程式、終端機工作階段與伺服器身分。 - 一個非特權的準備作業會透過
pwd.getpwuid(os.getuid())解析已驗證帳戶的家目錄,以0700模式建立$HOME/ch-j-sm/,並建立一份私有、獨占的復原日誌。不安全的家目錄、以符號連結構成的工作區,或由其他帳戶擁有的工作區都會被拒絕。 - Core 透過 SFTP 上傳一個隱藏的、隨機 128 位元的
.tmp工作檔案,使用獨占建立(wx)、0600模式,以及六十秒的通道/上傳逾時。日誌包含目標、原始基準與預期的工作檔案雜湊;不包含任何登入憑據。 - 一次 SSH exec 完成整個最終處理,並可選擇透過既有的 sudo 執行器執行。輔助程式會安全地開啟私有工作副本、計算其雜湊並將其寫入磁碟。它會鎖定原始 inode、檢查其基準、在目標旁建立一個獨占的目的地檔案、寫入全部內容、套用並驗證中繼資料、再次檢查原始檔案,然後將替換檔案寫入磁碟。
- 在同一目錄中使用
os.replace以不可分割的方式替換目標。上層目錄會被寫入磁碟,並驗證最終路徑、雜湊、inode 與中繼資料。在替換完成之前,原始路徑一律可用。工作檔案絕不會跨檔案系統移動,也絕不會在最終處理過程中被刪除。 - Core 收到經驗證的成功結果後,會由獨立的清理步驟再次檢查最終內容與安全性中繼資料,然後才刪除工作檔案與日誌。即使清理失敗,仍會回報已確認的儲存,並附帶
recoveryAvailable: true。
上傳失敗、sudo 失敗、衝突、中繼資料錯誤或替換失敗時,會保留原始檔案以及所有已暫存的資料。未完成的上傳會如實標示,無法當作完整的已編輯文件復原。SSH 通訊中斷、缺少結束狀態、逾時以及非預期的最終處理結果,都會回報為未知,絕不會回報為已儲存。對於結果不確定的文件,Core 會阻止再次儲存;不會自動重試替換。
中繼資料、權限與安全性邊界
輔助程式會保留並驗證 UID、GID 以及所有 POSIX 權限位元,包括可執行、setuid、setgid 與 sticky 位元。它會逐位元組重現 Linux 延伸屬性,包括 POSIX ACL、capabilities 與 SELinux 標籤。擁有者與檔案內容會先於安全性屬性設定,因為寫入/chown 可能會清除 capabilities 或特殊權限位元。原始檔案中不存在、但目的地檔案繼承而來的屬性會被移除。修改時間反映新的寫入,不會從原始檔案複製。
無法讀取或無法表示的屬性會中止作業;中繼資料不會在未告知的情況下被捨棄。IMA/EVM 簽章會被明確拒絕,因為複製與內容/inode 綁定的完整性簽章會使其失效。目標路徑或家目錄路徑中的符號連結、硬連結檔案以及非一般檔案都會被拒絕。不使用任何就地覆寫或較弱的 SFTP 覆寫備援方式。
一般的可寫入儲存會以 SSH 帳戶身分執行。受保護的儲存需要明確指定 { sudo: true };對於不需要密碼的 sudo,sudo 密碼為選用。密碼經驗證後只會透過通道的 stdin 傳遞,絕不會出現在指令、日誌、工作檔案、記錄器或遠端錯誤中。root SSH 工作階段使用既有的直接 root 執行器。授權失敗時,原始檔案與工作副本維持不變。所有以 root 身分執行的儲存(直接 root SSH 或 sudo)也會拒絕由其他帳戶擁有或可寫入、或帶有存取 ACL 的目標目錄/上層目錄,而不會信任由其他帳戶控制的特權路徑名稱。
固定的輔助程式以 python3 -I -B 執行:它會忽略使用者的 Python 路徑、工作目錄與使用者 site-packages,且不寫入位元組碼。路徑與 JSON 參數在 Core 中進行 shell 跳脫。外掛程式絕不會取得任意的 SSH exec 或 SFTP 控制代碼。既有的沙箱、CSP、資訊清單權限、主機金鑰驗證與更新驗證仍持續有效。
Plugin API 與剩餘的 Monaco 整合
另行散布的 Monaco/File Manager UI 不在此簽出內容中。其 UI 整合、sudo 提示、未儲存緩衝區的處理與翻譯無法在此實作或測試。內建的外掛程式原始碼是 Hash & Checksum 外掛程式。並未虛構任何替代的 File Manager 原始碼。
既有的方法仍可使用。新增的方法/選項由沙箱化的 preload 與經過權限檢查的執行環境提供:
const opened = await chjPlugin.files.readText(sessionId, path);
// For a protected read, explicitly pass { sudo: true, sudoPassword }.
const result = await chjPlugin.files.saveText(sessionId, path, editor.getValue(), {
editId: opened.editId,
sudo: true, // Include only after the user authorizes elevation.
sudoPassword // Omit for passwordless sudo; never persist this value.
});
if (result.ok && result.value.status === "saved") {
// Mark this exact submitted editor version as saved. Later edits stay dirty.
} else {
// Keep the Monaco model dirty. Translate result.error.code/status.
// result.error.recoveryId/recoveryPath identify retained working material.
}
await chjPlugin.files.closeText(opened.editId);
const recovery = await chjPlugin.files.listRecovery(sessionId);
const copy = await chjPlugin.files.readRecovery(sessionId, recoveryId);
// Review copy.status and copy.text; open a fresh document before another save.
await chjPlugin.files.cleanupRecovery(sessionId, recoveryId);
// Cleanup rejects anything whose target is not currently confirmed.
當檢查目標需要提升權限時,復原方法可接受一個選用的最後參數 { sudo: true, sudoPassword }。即使透過 sudo 執行,它們也會保留已驗證使用者的工作區。日誌在應用程式重新啟動與 SSH 重新連線後仍然存在;復原絕不會自動重複最終處理。在明確儲存復原的緩衝區之前,請先將其與重新開啟的目標進行比對。
files.read 保護讀取、檢查與控制代碼釋放。files.write 保護儲存與復原清理。對於既有的外掛程式,writeText 在失敗時仍會拒絕;saveText 則傳回結構化的 { ok, value/error } 封裝,因為 Electron 會捨棄被拒絕之 Error 物件上的自訂屬性。舊式的三參數 writeText 可使用單一已開啟文件的基準。同一路徑上有多份作用中的文件時需要 editId;不明確的舊式呼叫會安全地失敗,而不會選用另一份文件的基準。外掛程式應釋放不再使用的控制代碼。
外部 UI 必須立即顯示儲存狀態、在失敗時讓緩衝區保持未儲存、僅在發生權限/驗證失敗後才提供 sudo、在不覆寫的情況下顯示衝突,並在連線中斷或結果未知後提供復原。它必須以捷克語、德語和英語翻譯這些狀態。建議的狀態文字:
| 狀態 | 捷克語 | 德語 | 英語 |
|---|---|---|---|
| 儲存中 | Ukládání… | Wird gespeichert… | Saving… |
| 已確認 | Uloženo | Gespeichert | Saved |
| 失敗 | Uložení selhalo | Speichern fehlgeschlagen | Save failed |
| 需要 sudo | Vyžadováno oprávnění sudo | sudo-Autorisierung erforderlich | Sudo authorization required |
| 衝突 | Původní soubor byl změněn | Originaldatei wurde geändert | Original file changed |
| 連線中斷 | Spojení bylo přerušeno | Verbindung unterbrochen | Connection lost |
| 未知 | Výsledek uložení není znám | Speicherergebnis unbekannt | Save result unknown |
| 復原 | Obnova je dostupná | Wiederherstellung verfügbar | Recovery available |
延遲的定義與生命週期
Ping 是向 SSH 所使用之已解析 IP 傳送 ICMP echo 要求、並由用戶端實際收到回覆所回報的延遲。Core 以固定的參數陣列非同步呼叫平台的 ping 執行檔,不使用 shell,也不要求以管理員身分啟動。Linux、macOS(包括 ping6)與 Windows 的參數會分別選擇。ICMP 遭封鎖、缺少執行檔、逾時、輸出無效,或 Windows 只提供上限值的回覆(例如 <1ms)時,都會產生「無法使用」的值,而不是編造一個數字。
SSH RTT 是透過既有的已驗證 SSH 用戶端執行固定 true exec 要求所花費的單調 performance.now() 時間長度。它包含通道建立、伺服器處理與回應傳遞;它既不是純粹的網路 RTT,也不是 SSH 登入所需的時間。翻譯後的指示器提示會說明這項差異。
終端機一連線,監控就會開始。各項探測會同時且非同步地執行,要求/程序逾時為五秒。下一次取樣會排定在上一次取樣完成後十二秒進行,以避免重疊。中斷連線會中止 ICMP、清除計時器、取消待處理的 SSH 作業並清空數值。紀錄身分檢查可防止舊連線在重新連線後繼續發布資料。ICMP 失敗絕不會改變 SSH 連線狀態。
延遲資料包含在既有的 state 事件 / ssh:state IPC 承載資料中:
latency: { pingMs: 24.1, sshRttMs: 31.25, measuredAt: "…" }
無法取得的數值為 null。精簡的終端機指示器使用毫秒、在地化的「無法使用」文字,以及捷克語/德語/英語標籤。中斷連線時會隱藏。不需要額外的轉譯器執行能力或 IPC 要求。
驗證與作業限制
- 在
app/中執行:npm ci,接著執行npm test。 - 最終本機結果:179 項測試;163 項通過、0 項失敗、16 項略過。所有略過的項目都是此 macOS 主機上的 Linux 檔案系統案例。
npm ci、Python 語法編譯與git diff --check皆已成功完成。 - 測試涵蓋 Core 的儲存/復原、sudo 通訊協定與密碼遮蔽、對惡意路徑的實際 POSIX shell 跳脫、衝突、上傳、清理失敗、不確定的結果、同時開啟的文件、IPC 權限與延遲生命週期。
- Linux 檔案系統測試會執行隨附的 Python 進入點,並使用私有的測試帳戶家目錄查詢:擁有者/模式/xattr、POSIX ACL、符號連結/硬連結、FIFO、上傳與中繼資料失敗、替換失敗、目標競爭、替換後結果未知以及復原。跨檔案系統的暫存會在可用時使用
/dev/shm與另一個檔案系統。這些測試在非 Linux 主機上會略過;缺少 ACL/獨立檔案系統功能時會明確略過。 - 新增的 Linux GitHub Actions 工作會執行完整的測試套件,並以 root 身分重複檔案系統案例。此工作流程尚未從此工作樹觸發執行。
- 本機驗證在 macOS 上進行。Python 原始碼語法使用 Command Line Tools 隨附的 Python 執行檔直接檢查。一次實際的 macOS 回送介面 ICMP 冒煙檢查傳回
0.071 ms。本機並未測試任何即時遠端 SSH 伺服器、Windows 執行環境、Linux 執行環境、SELinux 部署或外部 Monaco UI。 - 遠端文字編輯現在需要 Linux,以及支援以描述元為基準之相對檔案系統/xattr 操作的 Python 3。缺少直譯器時會明確回報錯誤。此 Core 會刻意拒絕不安全的中繼資料、連結與目錄設定。
- 鎖定僅具建議性質:在最後一次基準檢查與不可分割的重新命名之間的短暫時間內,不配合的程序仍可能產生競爭。Linux 並未提供通用的不可分割「內容雜湊比較並替換」操作。檢查會在該時間範圍之前偵測變更;保留的工作副本則支援之後的復原。
- 復原資料會保留在遠端帳戶中,直到完成經確認的清理為止。未完成、有衝突以及無法檢查的日誌絕不會在未告知的情況下被捨棄。復原檔案不會自動過期,也沒有強制覆寫的選項。
npm ci回報了 17 項相依套件稽核結果(9 項中度、8 項高度)。此次變更不會改變相依套件或應用程式的更新信任模型。
使用者要求在既有的工作樹(目前為 feature/hash-checksum-plugin)中保留變更;並未切換分支,也未進行無關的清理。
此工作中變更的檔案
src/main/files/remoteFileService.jssrc/main/files/remoteEditor.py(新增)src/main/sessions/sessionManager.jssrc/main/sessions/latencyMonitor.js(新增)src/main/plugins/pluginRuntime.jssrc/preload/pluginPreload.jssrc/renderer/app.jssrc/renderer/index.htmlsrc/renderer/styles.csssrc/renderer/i18n.jstest/remoteFileService.test.jstest/sessionManager.test.jstest/pluginRuntime.test.jstest/remoteEditor.test.js(新增)test/remoteEditorProtocol.test.js(新增)test/remoteEditorLinux.test.js(新增)test/fixtures/remoteEditorLinux.py(新增)test/latencyMonitor.test.js(新增)README.mddocs/remote-editor-latency.md(本文件)../.github/workflows/remote-editor-tests.yml(新增)