Causas raíz
El editor anterior subió su archivo de trabajo en el directorio padre del destino, que a menudo no puede ser escrito por la cuenta SSH bajo /etc o /var. SFTP renombrado no proporcionó ninguna ruta de sudo autorizada, verificación de conflictos de línea base, verificación de metadatos de seguridad o sobreescritura semántica confiable. Su controlador de errores borró la copia temporal, eliminando el material de recuperación incluso cuando la finalización era incierta.
Estado de la sesión previamente expuesto sólo el estado de conexión. No hubo medición de la respuesta ICMP o SSH autenticada que alimentara el indicador terminal.
Guardar protocolo
- Un ayudante SSH fijo abre un archivo regular de un solo enlace usando descriptor-relativo
operaciones y
O_NOFOLLOWpara cada componente de ruta. Captura el contenido, SHA-256 hash, dispositivo/inodo, UID, GID, bits de permiso completos, tiempos de modificación/cambio nanosegundo y todos los atributos extendidos legibles. El núcleo verifica el hash y decodifica el estricto UTF-8, preservando un BOM. Cada documento obtiene uneditIdopaco, visorizado a su plugin, la sesión de terminal y la identidad del servidor. - Una operación de preparación sin privilegios resuelve la cuenta autenticada
inicio a través de
pwd.getpwuid(os.getuid()), crea$HOME/ch-j-sm/con el modo0700y crea un diario de recuperación privado y exclusivo. Un hogar inseguro, un espacio de trabajo de enlaces simbólicos o un espacio de espacio de trabajo propiedad de otra cuenta es rechazado. - El núcleo carga un archivo de trabajo oculto y aleatorio 128-bit
.tmpa través de SFTP con creación exclusiva (wx), modo0600y un tiempo de espera de sesenta segundos de canal/carga. La revista contiene el objetivo, la base de referencia original y el hash de trabajo esperado; no contiene credenciales. - One SSH exec realiza toda la finalización, opcionalmente a través de la executor de sudo existente. El ayudante abre y agiliza con seguridad la copia de trabajo privada y la descarga. Bloquea el inodo original, comprueba su línea de base, crea un archivo de destino exclusivo junto al objetivo, escribe todo el contenido, aplica y verifica metadatos, revisa el original nuevamente y descarga el reemplazo.
- Un mismo directorio
os.replacereemplaza atómicamente al objetivo. El padre el directorio se borra y la ruta final, el hash, el inodo y los metadatos se verifican. La ruta original permanece disponible hasta el reemplazo. El archivo de trabajo nunca se mueve a través de sistemas de archivos y nunca se elimina al finalizar. - Después de Core recibe un éxito verificado, una limpieza separada vuelve a comprobar el final
contenido y metadatos de seguridad antes de eliminar el archivo de trabajo y el diario. El fallo de limpieza sigue reportando un ahorro confirmado con
recoveryAvailable: true.
Una carga fallida, sudo fallido, conflicto, error de metadatos o sustitución fallida mantiene el material original y cualquier material escenificado. Las cargas incompletas se identifican como tales y no pueden ser recuperadas como un documento editado completo. La comunicación perdida de SSH, el estado de salida, los tiempos de espera y los resultados inesperados de finalización se reportan como unknown. El núcleo bloquea otro salvo en un documento incierto; no hay reintento de reemplazo automático.
Metadatos, privilegios y límites de seguridad
El ayudante preserva y verifica UID, GID y todos los bits de permiso POSIX, incluidos los bits ejecutables, setuid, setgid y pegajosos. Reproduce atributos extendidos Linux byte para byte, incluyendo ACL POSIX, capacidades y etiquetas SELinux. La propiedad y el contenido del archivo se establecen antes de los atributos de seguridad, porque escribe/cocina puede borrar las capacidades o bits de permiso especiales. Se eliminan los atributos de destino heredados que están ausentes en el original. El tiempo de modificación refleja la nueva escritura y no se copia del original.
Un atributo ilegible o irrepresentable aborta la operación; los metadatos no se eliminan silenciosamente. Las firmas IMA/EMV son rechazadas explícitamente porque copiar las firmas de integridad con contenido/inodo los invalidaría. Se rechazan los enlaces en la ruta de destino o inicio, los archivos enlazados de forma dura y los archivos no regulares. No se utiliza ningún retroceso de sobreescritura SFTP en su lugar o más débil.
Los archivos de escritura ordinarios se ejecutan como la cuenta SSH. Los guardados protegidos requieren { sudo: true } explícito; una contraseña de sudo es opcional para sudo sin contraseña. Las contraseñas son validadas y pasadas sólo en canal stdin, nunca dentro del comando, diario, archivo de trabajo, registrador o error remoto. Las sesiones de Root SSH utilizan el ejecutor raíz directo existente. La autorización fallida deja intacto el original y la copia de trabajo. Todos los salvas ejecutados como root (root directo SSH o sudo) también rechazan directorios de destino/antecedentes que poseen o pueden escribir otras cuentas, o que llevan un acceso ACL, en lugar de confiar en una ruta privilegiada controlada por otra cuenta.
El ayudante fijo se ejecuta con python3 -I -B: ignora las rutas del usuario Python, el directorio de trabajo y los paquetes del sitio del usuario, y no escribe ningún código de byte. Las rutas y los argumentos JSON se citan en Core. Los complementos nunca reciben mangos SSH exec o SFTP arbitrarios. El sandbox existente, CSP, los permisos de manifiesto, la verificación de la clave del host y la verificación de actualización permanecen en uso.
Plugin API y la integración restante de Monaco
La interfaz de usuario de Monaco/File Manager distribuida por separado no está presente en esta compra. Su integración de interfaz de usuario, sudo prompt, manejo de buffer sucio y traducciones no se pudo implementar o probar aquí. La fuente del plugin incluido es el complemento Hash & Checksum. No se inventó ninguna fuente de administrador de archivos sustituto.
Los métodos existentes siguen estando disponibles. Los métodos/opciones aditivos se exponen por el tiempo de ejecución precargado con caja de arena y comprobado con permiso:
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.
Los métodos de recuperación aceptan un argumento final opcional { sudo: true, sudoPassword } cuando se inspecciona un objetivo requiere elevación. Retienen el espacio de trabajo del usuario autenticado incluso cuando se ejecuta a través de sudo. Los diarios sobreviven al reinicio de la aplicación y a la reconexión de SSH; la recuperación nunca se repite automáticamente. Revise un búfer recuperado contra un objetivo recién abierto antes de guardarlo explícitamente.
Los guardias files.read leen, inspeccionan y manejan la liberación. files.write guarda y limpia la recuperación. writeText sigue rechazando el fallo de los plugins existentes; saveText devuelve un sobre { ok, value/error } estructurado porque Electron deja caer propiedades personalizadas en objetos de error rechazados. Un argumento heredado writeText puede utilizar la línea de base de un único documento abierto. Múltiples documentos en vivo para la misma ruta requieren editId; las llamadas ambiguas del legado fallan de forma segura en lugar de elegir la línea de base de otro documento. Los complementos deben liberar los mangos no utilizados.
La interfaz de usuario externa debe mostrar el ahorro inmediatamente, mantener los buffers sucios en caso de fallo, ofrecer sudo sólo después de fallos de permiso/autenticación, mostrar conflictos sin sobreescritura y ofrecer recuperación después de la pérdida de conexión o un resultado desconocido. Debe traducir estos estados en checo, alemán e inglés. Copia de estado sugerida:
| Estado | Checo | Alemán | Inglés |
|---|---|---|---|
| Guardando | Ukládání… | Wird gespeichert… | Saving… |
| Confirmado | Uloženo | Gespeichert | Saved |
| Error | Uložení selhalo | Speichern fehlgeschlagen | Save failed |
| Se requiere sudo | Vyžadováno oprávnění sudo | sudo-Autorisierung erforderlich | Sudo authorization required |
| Conflicto | Původní soubor byl změněn | Originaldatei wurde geändert | Original file changed |
| Desconectado | Spojení bylo přerušeno | Verbindung unterbrochen | Connection lost |
| Desconocido | Výsledek uložení není znám | Speicherergebnis unbekannt | Save result unknown |
| Recuperación | Obnova je dostupná | Wiederherstellung verfügbar | Recovery available |
Definiciones de latencia y ciclo de vida
Ping es la latencia reportada por una respuesta de eco ICMP del lado del cliente a la IP resuelta utilizada por SSH. Core invoca el ejecutable ping de la plataforma asíncronamente con un array de argumento fijo, sin necesidad de iniciar shell o administrador. Los argumentos Linux, macOS (incluyendo ping6) y Windows se seleccionan por separado. Bloqueado ICMP, un ejecutable que falta, un timeout, salida inválida o una respuesta Windows de sólo-último límite como <1ms produce un valor no disponible, no un número inventado.
SSH RTT es la duración monotónica de performance.now() de una solicitud exec true fija sobre el cliente SSH autenticado existente. Incluye configuración de canales, procesamiento de servidores y entrega de respuestas; no es pura red RTT o SSH duración de inicio de sesión. La sugerencia de indicador traducida explica esta distinción.
El monitoreo comienza tan pronto como el terminal se conecta. Las sondas se ejecutan simultáneamente y de manera asíncrona, con un tiempo de espera de cinco segundos para la solicitud/proceso. La siguiente muestra está programada doce segundos después de que la muestra anterior se complete, evitando solapamientos. Desconecte aborta ICMP, borra los temporizadores, cancela el trabajo pendiente de SSH y elimina los valores. Los controles de identidad del registro impiden que una conexión previa se publique después de reconectarse. El fallo de ICMP nunca cambia el estado de conexión SSH.
Latencia viaja dentro del evento state existente / carga útil IPC ssh:state:
latency: { pingMs: 24.1, sshRttMs: 31.25, measuredAt: "…" }
Los números no disponibles son null. El indicador de terminal compacto utiliza milisegundos, texto no disponible localizado y etiquetas Czech/German/English. Se esconde desconectado. No se necesitó ninguna capacidad adicional de ejecución del renderizador ni solicitud de IPC.
Validación y límites operativos
- Ejecutar desde
app/:npm ci, luegonpm test. - Resultado local final: pruebas 179; 163 pasado, 0 fallado, 16 skipped. Todos los saltos
son los casos del sistema de archivos Linux en este host macOS.
npm ci, compilación de sintaxis de Python ygit diff --checkcompletadas con éxito. - Las pruebas cubren Core salva/recuperación, protocolo de sudo y redacción de contraseña, real POSIX shell citando caminos hostiles, conflictos, cargas, limpieza fallida, resultados inciertos, documentos concurrentes, permisos IPC y ciclo de vida de latencia.
- Las pruebas del sistema de archivos Linux ejercitan el punto de entrada de Python enviado, usando un sistema privado
búsqueda de cuentas-hogar de prueba: propiedad/mode/xattrs, ACLs POSIX, simlinks/hardlinks, FIFOs, fallos de carga y metadatos, fallos de reemplazo, carreras de destino, resultados post-remplazo desconocidos y recuperación. La puesta en escena del sistema de archivos cruzados utiliza
/dev/shmy otro sistema de archivos cuando está disponible. Estas pruebas se saltan en hosts no-Linux; las instalaciones ACL/separate-filesystem que faltan se omiten explícitamente. - El trabajo añadido Linux GitHub Actions ejecuta el sistema de archivos completo y repite casos como raíz. Este flujo de trabajo no ha sido enviado desde este árbol de trabajo.
- La validación local fue en macOS. La sintaxis de origen de Python fue verificada con el
Herramientas de línea de comandos ejecutable en Python. Un verdadero macOS loopback ICMP control de humo devolvió
0.071 ms. No se probó localmente ningún servidor SSH, tiempo de ejecución Windows ni tiempo de ejecución de Linux en vivo, ni despliegue SELinux ni interfaz de usuario externa de Monaco. - La edición de texto remoto ahora requiere Linux y Python 3 con descriptor-relativo soporte para sistema de archivos/xattr. Un intérprete que falta falla explícitamente. Este Core rechaza intencionadamente metadatos, enlaces y configuraciones de directorios inseguros.
- El bloqueo es un consejo: los procesos que no cooperan todavía pueden competir en el estrecho intervalo entre la última comprobación de referencia y el cambio atómico. Linux no suministra ninguna operación general de comparación y sustitución de contenido atómico. Las comprobaciones detectan cambios antes de ese intervalo; las copias de trabajo retenidas soportan la recuperación después.
- Los datos de recuperación permanecen en la cuenta remota hasta una limpieza confirmada. Incompleta, revistas conflictivas e ininspectivas nunca se descartan silenciosamente. No hay una expiración automática del archivo de recuperación y ninguna opción de sobreescritura forzada.
npm ciinformó de las conclusiones de la auditoría de dependencia de 17 (9 moderada, 8 alta). Este cambio no cambia las dependencias o el modelo de confianza de actualización de la aplicación.
El usuario solicitó que se retuvieran los cambios en el árbol de trabajo existente, actualmente feature/hash-checksum-plugin; no se realizó ningún cambio de rama o limpieza no relacionada.
Archivos cambiados para esta tarea
src/main/files/remoteFileService.jssrc/main/files/remoteEditor.py(nuevo)src/main/sessions/sessionManager.jssrc/main/sessions/latencyMonitor.js(nuevo)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(nuevo)test/remoteEditorProtocol.test.js(nuevo)test/remoteEditorLinux.test.js(nuevo)test/fixtures/remoteEditorLinux.py(nuevo)test/latencyMonitor.test.js(nuevo)README.mddocs/remote-editor-latency.md(este archivo)../.github/workflows/remote-editor-tests.yml(nuevo)