# PR-004: Runtime VFS Path Traversal Hardening ## Briefing Hoje o `VirtualFS` normaliza barras, mas nao canonicaliza nem rejeita segmentos `..`. Em seguida, o backend de host concatena o caminho recebido com a raiz montada. Isso abre espaco para escapar da raiz virtual com caminhos como `/user/../../outside.txt`. Este PR endurece a fronteira entre o filesystem virtual e o filesystem do host. O objetivo e garantir que toda operacao de `read/write/delete/list/exists` permaneça estritamente dentro da raiz montada. ## Problema - O `VirtualFS` aceita caminhos relativos e absolutos sem validacao estrutural suficiente. - O backend `HostDirBackend` faz `root.join(path)` sem bloquear traversal. - O problema afeta confidencialidade, integridade e isolamento do runtime. ## Escopo - Endurecer a normalizacao de caminhos no `VirtualFS`. - Endurecer a resolucao no `HostDirBackend`. - Garantir comportamento consistente para `read_file`, `write_file`, `delete`, `list_dir` e `exists`. - Cobrir casos de traversal em testes unitarios. ## Fora de Escopo - Suporte a links simbolicos com politicas avancadas. - Politicas de permissao por namespace (`/system`, `/user`, `/apps`, etc). - Refactor completo da API de filesystem. ## Abordagem 1. Introduzir uma regra unica de validacao de caminho virtual: - converter `\` para `/`; - exigir caminho absoluto virtual; - colapsar `.` quando aparecer; - rejeitar qualquer segmento vazio ambiguo ou `..`; - retornar erro explicito em vez de tentar "corrigir" traversal. 2. Fazer o `VirtualFS` operar apenas sobre caminhos validados. 3. Endurecer o `HostDirBackend` para nunca confiar apenas na normalizacao acima: - resolver o caminho relativo a partir da raiz; - rejeitar novamente qualquer tentativa de escapar; - manter defesa em profundidade mesmo se outro backend ou chamador evoluir errado. 4. Garantir que operacoes booleanas como `exists` nao silenciem traversal como se fosse "arquivo inexistente" sem distinguir erro estrutural quando isso for relevante para a API. ## Algoritmo ### Normalizacao de caminho virtual Entrada: `path: &str` Saida: caminho virtual sanitizado ou erro. Passos: 1. Substituir `\` por `/`. 2. Se o caminho nao comecar com `/`, prefixar `/`. 3. Separar por `/`. 4. Ignorar segmentos vazios e `.`. 5. Se algum segmento for `..`, falhar com `FsError`. 6. Reconstruir o caminho como `///...`. 7. Preservar `/` como raiz quando nao houver segmentos. ### Resolucao no backend do host Entrada: caminho virtual sanitizado. Saida: `PathBuf` dentro de `root` ou erro. Passos: 1. Remover o `/` inicial do caminho virtual. 2. Concatenar cada segmento validado manualmente em um `PathBuf` iniciado em `root`. 3. Nunca aceitar segmentos `..`, `.` ou componentes de prefixo/plataforma. 4. Antes de retornar, garantir que o caminho construido continua sob `root`. ## Plano de Execucao ### Gate arquitetural Antes de iniciar a implementacao, esta PR deve congelar a seguinte decisao: - `exists` permanece com retorno `bool` nesta PR. - Consequencia: caminho invalido ou tentativa de traversal em `exists` deve resultar em `false`, sem acesso ao host. - Justificativa: mudar `exists` para `Result` propaga alteracao de contrato para `FsBackend`, `VirtualFS`, runtime e syscall, o que expande o escopo desta PR. Se essa decisao nao for aceita, a PR deve voltar para discussao arquitetural antes de qualquer alteracao de codigo. ### Fase 1 - Endurecer contrato de erro Arquivos alvo: - `crates/console/prometeu-system/src/services/fs/fs_error.rs` Passos: 1. Introduzir um erro explicito para caminho invalido, por exemplo `FsError::InvalidPath(String)` ou equivalente. 2. Garantir mensagem clara para os casos: - caminho com `..`; - componente de plataforma/prefixo inesperado; - caminho vazio estruturalmente invalido, se a implementacao optar por rejeita-lo. Objetivo: - impedir que traversal seja reportado genericamente como `IOError` ou `Other`. ### Fase 2 - Endurecer o `VirtualFS` Arquivos alvo: - `crates/console/prometeu-system/src/services/fs/virtual_fs.rs` Passos: 1. Trocar `normalize_path(&self, path: &str) -> String` por uma funcao fallible: - `normalize_path(path: &str) -> Result`. 2. Implementar a normalizacao unica da camada virtual: - converter `\` para `/`; - garantir raiz virtual absoluta; - colapsar `.` e segmentos vazios internos irrelevantes; - rejeitar `..`. 3. Fazer `list_dir`, `read_file`, `write_file` e `delete` falharem antes de tocar no backend quando o caminho for invalido. 4. Manter `exists` como fronteira booleana: - se a normalizacao falhar, retornar `false`; - nao chamar o backend nesse caso. Objetivo: - estabelecer uma unica regra de caminho virtual para toda a API publica do VFS. ### Fase 3 - Endurecer o `HostDirBackend` Arquivos alvo: - `crates/host/prometeu-host-desktop-winit/src/fs_backend.rs` Passos: 1. Trocar `resolve(&self, path: &str) -> PathBuf` por uma resolucao fallible. 2. Iterar manualmente pelos componentes do caminho e rejeitar: - `ParentDir`; - `CurDir`; - `RootDir`; - `Prefix(_)` em plataformas que exponham prefixos. 3. Construir o `PathBuf` a partir de `root` apenas com segmentos normais. 4. Validar ao final que o caminho produzido continua estritamente sob `root`. 5. Fazer `list_dir`, `read_file`, `write_file` e `delete` retornarem erro estrutural em vez de acessar o host. 6. Fazer `exists` retornar `false` para caminhos invalidos, sem side effect. Objetivo: - manter defesa em profundidade mesmo que outra camada no futuro normalize errado. ### Fase 4 - Cobertura de testes no `prometeu-system` Arquivos alvo: - `crates/console/prometeu-system/src/services/fs/virtual_fs.rs` Passos: 1. Adicionar testes unitarios de normalizacao/rejeicao para: - `../x` - `/../x` - `/user/../../x` - `\\user\\..\\..\\x` 2. Adicionar um teste que prove que `exists("../x") == false`. 3. Adicionar um mock backend com contadores ou flags para comprovar que caminhos invalidos: - nao chegam em `read_file`; - nao chegam em `write_file`; - nao chegam em `delete`; - nao chegam em `list_dir`; - nao chegam em `exists`. Objetivo: - provar que a barreira virtual bloqueia traversal antes de qualquer backend. ### Fase 5 - Cobertura de testes no host Arquivos alvo: - `crates/host/prometeu-host-desktop-winit/src/fs_backend.rs` Passos: 1. Adicionar teste positivo de round-trip: - criar `/user/test.txt`; - ler; - verificar `exists`; - apagar. 2. Adicionar teste de traversal em leitura e escrita usando diretorio temporario: - garantir erro para `/user/../../outside.txt`; - garantir que nenhum arquivo e criado fora de `root`. 3. Adicionar teste para `exists` e `list_dir` com traversal: - `exists` retorna `false`; - `list_dir` retorna erro; - nenhum acesso fora da raiz e observado. Objetivo: - provar que a defesa em profundidade do backend funciona mesmo isoladamente. ### Fase 6 - Validacao final Comandos: - `cargo test -p prometeu-system` - `cargo test -p prometeu-host-desktop-winit` Checklist de saida: - nenhuma operacao acessa caminho fora da raiz montada; - caminhos validos continuam funcionais; - traversal falha de forma deterministica e explicita; - `exists` preserva contrato booleano sem vazar acesso ao host. ## Sequencia recomendada 1. Implementar `FsError` primeiro. 2. Endurecer `VirtualFS` e fechar os testes unitarios da camada virtual. 3. Endurecer `HostDirBackend` com defesa em profundidade. 4. Adicionar os testes de host. 5. Rodar os testes dos dois crates. ## Riscos de execucao - O comportamento atual aceita caminhos permissivos como `user/file.txt`; apos esta PR eles continuarao aceitos apenas se normalizarem para uma forma absoluta segura. - A maior fonte de expansao de escopo e tentar mudar o contrato de `exists`; esta PR nao deve fazer isso. - Se surgir necessidade de suportar links simbolicos ou canonicalizacao real de host, isso caracteriza outra PR. ## Criterios de Aceite - Qualquer tentativa de traversal com `..` e rejeitada em `read_file`. - Qualquer tentativa de traversal com `..` e rejeitada em `write_file`. - Qualquer tentativa de traversal com `..` e rejeitada em `delete`. - `exists` e `list_dir` nao acessam caminhos fora da raiz montada. - Caminhos normais como `/user/save.dat` continuam funcionando. - O backend de host continua criando subdiretorios validos dentro da raiz. ## Tests - Teste unitario no `VirtualFS` para rejeitar: - `../x` - `/../x` - `/user/../../x` - `\\user\\..\\..\\x` - Teste unitario no backend do host validando que um caminho de traversal nao resulta em acesso fora da raiz temporaria. - Teste positivo para operacoes validas: - criar arquivo em `/user/test.txt`; - ler o mesmo arquivo; - confirmar `exists`; - apagar o arquivo. - Rodar: - `cargo test -p prometeu-system` - `cargo test -p prometeu-host-desktop-winit` ## Risco Baixo para a arquitetura e medio para compatibilidade, porque caminhos hoje aceitos de forma permissiva podem passar a falhar explicitamente. Esse endurecimento e desejado.