Saltar al contenido

Fronteras de seguridad: el sandbox web frente al proceso nativo

La frontera arquitectónica entre el sandbox del navegador y el proceso del sistema operativo: acceso directo a rutas del filesystem, observadores del kernel, invocación de procesos hijos y redes sin restricciones de origen.

El modelo de amenazas del navegador frente al proceso nativo

Cuando un usuario navega por la web, el motor de renderizado descarga y ejecuta código JavaScript no verificado en cuestión de milisegundos. Para evitar que ese código comprometa el sistema del usuario, los motores modernos (Chromium, WebKit, Gecko) aíslan cada contexto en un proceso de renderizado sin privilegios, restringido mediante mecanismos del kernel como seccomp-bpf en Linux, AppContainer en Windows o Seatbelt (App Sandbox) en macOS.

Bajo este modelo:

  1. El origen es la unidad de aislamiento: Una página solo puede acceder a la memoria y al almacenamiento asociados a su tupla (esquema, dominio, puerto).
  2. Las llamadas al sistema están filtradas: El proceso del renderizador no puede invocar directamente open(), fork(), execve() ni abrir sockets de red crudos; cualquier interacción debe canalizarse a través del proceso broker del navegador mediante IPC interno.
  3. El consentimiento es transitorio: Cualquier excepción (como acceder a un fichero local mediante la File System Access API) exige la intervención explícita del usuario mediante un selector nativo, y el permiso puede expirar al recargar la pestaña.
Rust
// En el proceso Core de Tauri (Rust): acceso directo a llamadas al sistemause std::fs::File;use std::io::{self, Read};use std::path::Path;pub fn leer_config_host(ruta: &Path) -> io::Result<String> {    // Syscall directa open() + read() bajo los privilegios del proceso    let mut archivo = File::open(ruta)?;    let mut contenido = String::new();    archivo.read_to_string(&mut contenido)?;    Ok(contenido)}

En una aplicación de escritorio con Tauri, la arquitectura se divide: la interfaz corre dentro de una Webview (restringida), pero cuenta con un puente IPC hacia un proceso Core nativo en Rust. Este proceso Core corre como un proceso regular del sistema operativo bajo el identificador de usuario (UID) del host, lo que desbloquea las capacidades de bajo nivel que el navegador bloquea.

La matriz de capacidades bloqueadas por el sandbox web

Las tres trampas de las APIs web emergentes

Con frecuencia se argumenta que los nuevos estándares web están cerrando la brecha con el escritorio. Sin embargo, en la práctica de ingeniería de sistemas se observan tres limitaciones infranqueables:

  1. La trampa del consentimiento permanente: La File System Access API permite leer y escribir archivos, pero los manejadores (FileSystemHandle) no garantizan persistencia de permisos entre sesiones sin requerir que el usuario vuelva a interactuar con un diálogo nativo. Para un software que requiere sincronización continua en segundo plano, este modelo resulta inoperante.
  2. La trampa del soporte heterogéneo: APIs como WebHID, WebUSB o Web Serial solo están implementadas en motores Chromium. En WebKit (Safari en macOS/iOS) y Gecko (Firefox) han sido rechazadas formalmente por consideraciones de privacidad y superficie de ataque al hardware. Una aplicación que dependa de estas APIs para hablar con dispositivos locales sacrifica la compatibilidad multiplataforma.
  3. La trampa del ciclo de vida en Service Workers: Los Service Workers no son procesos demonio: son ejecutores de eventos efímeros. El motor web puede terminarlos en cualquier momento para liberar recursos de memoria. No es posible ejecutar una indexación pesada de 100.000 archivos locales en un Service Worker sin que el runtime interrumpa su ejecución.
TypeScript
// Ejemplo de limitación en la web: la necesidad de un gesto de usuarioasync function guardarArchivoWeb(contenido: string) {  // Obliga a mostrar un diálogo modal al usuario en cada llamada  const handle = await window.showSaveFilePicker({    suggestedName: 'datos.json',  });  const writable = await handle.createWritable();  await writable.write(contenido);  await writable.close();}
Rust
// Enfoque Tauri (Rust): persistencia determinista en la ruta del sistemause std::fs;use tauri::Manager;#[tauri::command]pub fn guardar_datos_locales(app: tauri::AppHandle, contenido: String) -> Result<(), String> {    let ruta_base = app.path().app_data_dir().map_err(|e| e.to_string())?;    fs::create_dir_all(&ruta_base).map_err(|e| e.to_string())?;        let archivo = ruta_base.join("datos.json");    fs::write(archivo, contenido).map_err(|e| e.to_string())?;    Ok(())}

Lo que no constituye una diferencia arquitectónica

Al evaluar la necesidad de una aplicación de escritorio, es común asumir como ventajas técnicas propiedades que en realidad son mitos:

  • Rendimiento bruto de la interfaz: La interfaz de Tauri es renderizada por el motor WebView del sistema operativo (WebKit en macOS, WebView2 en Windows). Si la capa de interfaz presenta cuellos de botella por recálculos de layout o JavaScript ineficiente en el navegador, presentará exactamente la misma latencia en el escritorio. La ganancia de rendimiento ocurre únicamente cuando los algoritmos pesados se descargan al runtime nativo de Rust.
  • Modo sin conexión (Offline): Una arquitectura web moderna basada en Service Workers y Local-First puede operar sin red de manera completa. El soporte offline por sí solo no justifica empaquetar una aplicación nativa si no se requieren primitivas del host.